mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
1074 Commits
2.5.0.0bet
...
2.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
955c9992d0 | ||
|
|
87606818cd | ||
|
|
ffcd264bac | ||
|
|
04c70cf99d | ||
|
|
0e0b6fca07 | ||
|
|
b24a789352 | ||
|
|
3d4f447037 | ||
|
|
7aa52ca00a | ||
|
|
a94c9050c5 | ||
|
|
9b18e71e12 | ||
|
|
d5ff048bd0 | ||
|
|
76f44f2573 | ||
|
|
9988f1a76f | ||
|
|
1559376bc2 | ||
|
|
4d0f252acd | ||
|
|
d5a84e66ad | ||
|
|
1dfe3453f4 | ||
|
|
a7dc9f31b9 | ||
|
|
d275f18b3c | ||
|
|
d770429401 | ||
|
|
501f0be99f | ||
|
|
a70122f873 | ||
|
|
1e1bd15a06 | ||
|
|
164fb1f4f5 | ||
|
|
0206514bdb | ||
|
|
ecf2b42fa4 | ||
|
|
df683aeea9 | ||
|
|
cdd05958f7 | ||
|
|
4ec5fc05fb | ||
|
|
4019cafac1 | ||
|
|
4333e179d1 | ||
|
|
87d67be428 | ||
|
|
350863adff | ||
|
|
d125fc46a0 | ||
|
|
5f4c079071 | ||
|
|
5ec576a76e | ||
|
|
9edb00ebbb | ||
|
|
59843e5464 | ||
|
|
4fe0ccf807 | ||
|
|
2ee012d3fe | ||
|
|
161b4cb5f5 | ||
|
|
f9bee68d71 | ||
|
|
e0d98aca1b | ||
|
|
d47518c00c | ||
|
|
58f97dc3ab | ||
|
|
c7706cb80c | ||
|
|
49bcc877ef | ||
|
|
4b81dd552e | ||
|
|
ff63aaf3f3 | ||
|
|
39cbd1477b | ||
|
|
3741cc558a | ||
|
|
2a314ca3c1 | ||
|
|
10f9564825 | ||
|
|
1ae87c9b18 | ||
|
|
24301ba462 | ||
|
|
ff6481274f | ||
|
|
866df585a2 | ||
|
|
416bec04a1 | ||
|
|
f747d6725e | ||
|
|
8846918e55 | ||
|
|
e347aefcd9 | ||
|
|
0a0676af3a | ||
|
|
7a69b63b4f | ||
|
|
fab5626741 | ||
|
|
c6832d6478 | ||
|
|
f451597746 | ||
|
|
a92e5b5156 | ||
|
|
786b3b26ea | ||
|
|
2e9fd2fd79 | ||
|
|
3be2b9893b | ||
|
|
7f6bed4f5f | ||
|
|
37322f284a | ||
|
|
4106bb1792 | ||
|
|
229db1242a | ||
|
|
8201b42135 | ||
|
|
24818575dc | ||
|
|
213050e7f2 | ||
|
|
1efaf4e3ea | ||
|
|
5532147992 | ||
|
|
550b43094d | ||
|
|
5e831837c8 | ||
|
|
480e88a088 | ||
|
|
81cbcbe8af | ||
|
|
5649634809 | ||
|
|
69a253f738 | ||
|
|
fbc8cfddb8 | ||
|
|
3e1024804c | ||
|
|
cf0b51be00 | ||
|
|
5b295a2a8f | ||
|
|
ebb3d7a149 | ||
|
|
73ab348d11 | ||
|
|
8ce17086f8 | ||
|
|
45cffc93b1 | ||
|
|
71cf4d5a34 | ||
|
|
b79a4154af | ||
|
|
ff818f8472 | ||
|
|
b540d32ca3 | ||
|
|
895a11d45f | ||
|
|
bdafae6132 | ||
|
|
f45d9ce6da | ||
|
|
7456e2c8f5 | ||
|
|
fc51b50fe6 | ||
|
|
b31aa26975 | ||
|
|
49801e1b14 | ||
|
|
1453464cbd | ||
|
|
b9be8ff13d | ||
|
|
9663c3cadd | ||
|
|
da5490bc0a | ||
|
|
df9b1b9fbb | ||
|
|
6a0a3ded13 | ||
|
|
ac7f4a08c3 | ||
|
|
e5ff5e3364 | ||
|
|
f0ac19fcc1 | ||
|
|
ce23d34923 | ||
|
|
bdbba87f5a | ||
|
|
fa8ef0477b | ||
|
|
3e9ec4cfb6 | ||
|
|
ad1c9d8129 | ||
|
|
7aaac4c13c | ||
|
|
a6c71c3d54 | ||
|
|
05714f38dc | ||
|
|
4ef5e4d8ae | ||
|
|
5fd0e1d6fe | ||
|
|
97faf5bef3 | ||
|
|
bd77128733 | ||
|
|
935debc6bf | ||
|
|
699ed19b6a | ||
|
|
126e5a94c2 | ||
|
|
792bd36ea7 | ||
|
|
04dfee1b43 | ||
|
|
6a08cfeea9 | ||
|
|
29a0f6c9f6 | ||
|
|
e2bdf26d82 | ||
|
|
1643949110 | ||
|
|
577dabd727 | ||
|
|
ed4bd0693f | ||
|
|
0f2982b34d | ||
|
|
0954094800 | ||
|
|
22dcfafc62 | ||
|
|
dd34051426 | ||
|
|
6feaf2cb8a | ||
|
|
603f64ea92 | ||
|
|
568f9ab0d0 | ||
|
|
a2ff1c33e6 | ||
|
|
84452e9fc0 | ||
|
|
5a6ae453cf | ||
|
|
cde3e3361d | ||
|
|
25f01192a4 | ||
|
|
bacb08ec65 | ||
|
|
36c905ee1c | ||
|
|
6b9280534a | ||
|
|
f94c6e850f | ||
|
|
f4cecf6906 | ||
|
|
a81d3766f8 | ||
|
|
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 | ||
|
|
f8134307f6 | ||
|
|
fe461f2e7c | ||
|
|
023c841747 | ||
|
|
af95c0903a | ||
|
|
0d756db8aa | ||
|
|
2c5dcc9b11 | ||
|
|
21c6ea73b2 | ||
|
|
51dc302bb0 | ||
|
|
87760ab4f6 | ||
|
|
88ebe58a88 | ||
|
|
fb023b81b5 | ||
|
|
2de6bbc6c0 | ||
|
|
4ef436629d | ||
|
|
9fd342f1e7 | ||
|
|
8988f17765 | ||
|
|
9d160db281 | ||
|
|
2e58c2f1b3 | ||
|
|
d1d2b99e09 | ||
|
|
def9744f75 | ||
|
|
214e2cf109 | ||
|
|
b25180c617 | ||
|
|
6a5263df77 | ||
|
|
2982f67717 | ||
|
|
b559670dff | ||
|
|
891d3142d2 | ||
|
|
2637788429 | ||
|
|
a21de3b892 | ||
|
|
e087e19120 | ||
|
|
721d61dda7 | ||
|
|
e0e7e431cf | ||
|
|
93948e7c61 | ||
|
|
b150c718a0 | ||
|
|
a71e4c3902 | ||
|
|
b7dc13d863 | ||
|
|
1e3c58e359 | ||
|
|
5f75599e9f | ||
|
|
b602f9b77d | ||
|
|
aa948c1ece | ||
|
|
e599a51152 | ||
|
|
ee6052f4d1 | ||
|
|
eba527f477 | ||
|
|
09e0d6d3cc | ||
|
|
9aefc984be | ||
|
|
2ce3b21f1b | ||
|
|
4d2f3cb4b1 | ||
|
|
e62b46c4c0 | ||
|
|
6472601170 | ||
|
|
89dd7bfefb | ||
|
|
fb2ea4c0ed | ||
|
|
8d84358d48 | ||
|
|
1d8661c633 | ||
|
|
48130eee45 | ||
|
|
2cf83962fe | ||
|
|
aecf7c0c39 | ||
|
|
39606e2676 | ||
|
|
6e00fa2d01 | ||
|
|
f79aa339e9 | ||
|
|
f412fce912 | ||
|
|
cc20b7503c | ||
|
|
2573434763 | ||
|
|
f153c26fef | ||
|
|
125f461cbe | ||
|
|
b705b4b712 | ||
|
|
c67b0bb858 | ||
|
|
ab1fc8c5d5 | ||
|
|
8477f4ba08 | ||
|
|
e6518ffdc8 | ||
|
|
99917c7f28 | ||
|
|
fcc29f67a3 | ||
|
|
7dd49f050c | ||
|
|
5f96de84b0 | ||
|
|
54c2f5a61f | ||
|
|
921c6f88aa | ||
|
|
a0cb579df4 | ||
|
|
d6a7c34ff3 | ||
|
|
bf2e61f149 | ||
|
|
eaf5dc5988 | ||
|
|
879ee013db | ||
|
|
e13d53eae4 | ||
|
|
d72c8184c9 | ||
|
|
c4d3c8cbfb | ||
|
|
02a3d85f80 | ||
|
|
19b0722f1f | ||
|
|
f14222b192 | ||
|
|
4f4f6d30d9 | ||
|
|
fdd329e982 | ||
|
|
55a4d388b3 | ||
|
|
5c6be448ec | ||
|
|
3e79ddcc21 | ||
|
|
5362758424 | ||
|
|
c10e3df2a7 | ||
|
|
166784021a | ||
|
|
5615c31e08 | ||
|
|
fb60dd5921 | ||
|
|
ff4c1b779b | ||
|
|
53a7b99567 | ||
|
|
a57103bafb | ||
|
|
2540f32dbf | ||
|
|
499ccd6b7c | ||
|
|
a4359560b9 | ||
|
|
149483cc2d | ||
|
|
a1d2022492 | ||
|
|
891036c35c | ||
|
|
4e16ba5f56 | ||
|
|
7137a2fadb | ||
|
|
9d90d0eaba | ||
|
|
94a9942db5 | ||
|
|
5f347fe106 | ||
|
|
a34a84ae16 | ||
|
|
40b0982298 | ||
|
|
4100258476 | ||
|
|
5f3f6661b7 | ||
|
|
75af97e0ae | ||
|
|
58f158c457 | ||
|
|
ce27eae1f0 | ||
|
|
0aa0b3e993 | ||
|
|
1cc5a08236 | ||
|
|
4c587eeb03 | ||
|
|
ab70c2d014 | ||
|
|
8413160ac5 | ||
|
|
5abc403171 | ||
|
|
9b891013b8 | ||
|
|
9413987355 | ||
|
|
f95b514b41 | ||
|
|
f6985c8944 | ||
|
|
4388d56c52 | ||
|
|
a70fe24c97 | ||
|
|
8e0392753c | ||
|
|
9c9980bba6 | ||
|
|
2226c15d29 | ||
|
|
82a859bd9c | ||
|
|
83873fab81 | ||
|
|
31f2be7b91 | ||
|
|
16458e6646 | ||
|
|
2b9678707d | ||
|
|
cdbb23d7f1 | ||
|
|
23fd1b83f4 | ||
|
|
40b0ebe49b | ||
|
|
7cd8682544 | ||
|
|
d0dd478ac8 | ||
|
|
ffb547c452 | ||
|
|
bd829f129f | ||
|
|
5ad3f62de5 | ||
|
|
b0ec4942bc | ||
|
|
2cbc9675f6 | ||
|
|
116643a45a | ||
|
|
2f0eb283ed | ||
|
|
6d46fccdcd | ||
|
|
f5dc94bfec | ||
|
|
94bdb0e3da | ||
|
|
65360c2a1e | ||
|
|
70d30bdbe6 | ||
|
|
66f7e6d1b1 | ||
|
|
a8ccb67a87 | ||
|
|
66051382f1 | ||
|
|
0fb3028c91 | ||
|
|
5e5baa4892 | ||
|
|
9d1257ed9d | ||
|
|
9d7546053d | ||
|
|
a1b692abe5 | ||
|
|
4e06842d0f | ||
|
|
f04c2ee1da | ||
|
|
9700dbcc3f | ||
|
|
4e344458b2 | ||
|
|
6f95cc7296 | ||
|
|
cd66f8f57e | ||
|
|
83f0eb9a33 | ||
|
|
ca89bba768 | ||
|
|
2d2bd5013e | ||
|
|
b93d7bbf41 | ||
|
|
000277705a | ||
|
|
088712e784 | ||
|
|
117592387e | ||
|
|
76bb1a369c | ||
|
|
9331c281fe | ||
|
|
f2666316e1 | ||
|
|
7c2ff5067d | ||
|
|
5fed641c7c | ||
|
|
18bd62ee5a | ||
|
|
bc57e6e257 | ||
|
|
69b1aba218 | ||
|
|
df04d998c2 | ||
|
|
e986fe5f60 | ||
|
|
e4ac0ee258 | ||
|
|
426aa0e7da | ||
|
|
6c0a48af48 | ||
|
|
d865da1613 | ||
|
|
6fa4c1e06e | ||
|
|
52a6b3e046 | ||
|
|
fa26d2f938 | ||
|
|
4777cdc7ae | ||
|
|
cd8d3cbf6a | ||
|
|
e6a6feb5c0 | ||
|
|
98d6fb9214 | ||
|
|
94244cd15b | ||
|
|
c5e2ca9907 | ||
|
|
e4fef44caf | ||
|
|
5bd9da9bb1 | ||
|
|
5f0e899679 | ||
|
|
4b1806900b | ||
|
|
30e2912885 | ||
|
|
fc3608ff69 | ||
|
|
6de47ec9b2 | ||
|
|
f7253764a2 | ||
|
|
c54b134c31 | ||
|
|
a6bdca52be | ||
|
|
29eec05f8f | ||
|
|
250aef9738 | ||
|
|
50097914a2 | ||
|
|
ecff4fb2c5 | ||
|
|
ab3d17f352 | ||
|
|
a75d237e53 | ||
|
|
a0c7786e1b | ||
|
|
0d32c38c79 | ||
|
|
b846eda410 | ||
|
|
7c33c9ec02 | ||
|
|
74c08340a6 | ||
|
|
82161536be | ||
|
|
752dbca356 | ||
|
|
9ef56f6fd8 | ||
|
|
0239f115ae | ||
|
|
9775e09221 | ||
|
|
c975a1bfc0 | ||
|
|
c263536078 | ||
|
|
b559eeaad0 | ||
|
|
63373083ab | ||
|
|
bf525807b0 | ||
|
|
921021078b | ||
|
|
e5b60a8413 | ||
|
|
e01402f2fa | ||
|
|
27f92e1bb5 | ||
|
|
63db6de30b | ||
|
|
7a038126cf | ||
|
|
edcfa8cf7b | ||
|
|
c9594948a2 | ||
|
|
66988ecb66 | ||
|
|
186ca30be8 | ||
|
|
027d581dcc | ||
|
|
adcc1c745a | ||
|
|
8682856c01 | ||
|
|
9ec976d246 | ||
|
|
9d17b49586 | ||
|
|
698496d37c | ||
|
|
2af8c4f3c8 | ||
|
|
b164099b6d | ||
|
|
c19357605f | ||
|
|
5a08fa0088 | ||
|
|
bff87d16b1 | ||
|
|
5b0afa447c | ||
|
|
5a882a954f | ||
|
|
47f340d576 | ||
|
|
41df139c17 | ||
|
|
469c267161 | ||
|
|
f336d4fe58 | ||
|
|
e1d997cc91 | ||
|
|
53cf4bba1b | ||
|
|
030417dbe1 | ||
|
|
59abcb115c | ||
|
|
598dbd3794 | ||
|
|
2c4a7e5576 | ||
|
|
02429d5790 | ||
|
|
d990cb24ea | ||
|
|
e549b16dce | ||
|
|
e11864a64f | ||
|
|
a3e74f8ee5 | ||
|
|
36cb683404 | ||
|
|
f7f0028033 | ||
|
|
f06088fa12 | ||
|
|
b62ef8a2ed | ||
|
|
622ba65841 | ||
|
|
3a48d20d12 | ||
|
|
8d6db78f55 | ||
|
|
f3ba6e800a | ||
|
|
1c4aaf9807 | ||
|
|
c65ed41efd | ||
|
|
1965336077 | ||
|
|
9b7095ad4c | ||
|
|
33767c2bf9 | ||
|
|
82f7e861e7 | ||
|
|
846b5fa449 | ||
|
|
baf4e676eb | ||
|
|
a9bf3e83c4 | ||
|
|
710a1b0996 | ||
|
|
c4671b84a0 | ||
|
|
c0c98d0299 | ||
|
|
ffdec77d11 | ||
|
|
868bbe2e70 | ||
|
|
4aac655a5d | ||
|
|
510244aa70 | ||
|
|
50840b04b4 | ||
|
|
4d5962f5ca | ||
|
|
c9456c771c | ||
|
|
41a7a583d4 | ||
|
|
2e5220fa8a | ||
|
|
b75475d785 | ||
|
|
362ef06bb5 | ||
|
|
4a80c9f9f9 | ||
|
|
f1fdb9fc84 | ||
|
|
5bb9168c29 | ||
|
|
0245dcd8e8 | ||
|
|
b31bfa1d4f | ||
|
|
0778f22b68 | ||
|
|
4808696398 | ||
|
|
0ea7b5b25f | ||
|
|
995785de9f | ||
|
|
5deef427c0 | ||
|
|
024c6631d8 | ||
|
|
78354e4736 | ||
|
|
b9a792e6bd | ||
|
|
c533d21250 | ||
|
|
74572c8102 | ||
|
|
b8de64fab0 | ||
|
|
877b909205 | ||
|
|
1b1dcc0f45 | ||
|
|
94e5988794 | ||
|
|
b22333fda5 | ||
|
|
400c6bef78 | ||
|
|
edf6c2ff07 | ||
|
|
5eec1a276c | ||
|
|
f707fd7649 | ||
|
|
75b028daf3 | ||
|
|
c6f259d18f | ||
|
|
954d522341 | ||
|
|
1f8d17d27e | ||
|
|
b737501d4d | ||
|
|
c303ffafb5 | ||
|
|
6f513b4920 | ||
|
|
a83c60583f | ||
|
|
cde8950257 | ||
|
|
0b4dd1e909 | ||
|
|
28e2600271 | ||
|
|
53cc4f74c8 | ||
|
|
9558fcaf21 |
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
** Keepass Database **
|
||||||
|
- Created with: [e.g Windows KeePass 2.42]
|
||||||
|
- Version: [e.g. 2]
|
||||||
|
- Location: [e.g. Remote file retrieved with GDrive app]
|
||||||
|
- Size: [e.g. 150Mo]
|
||||||
|
- Contains attachment: [e.g. Yes]
|
||||||
|
|
||||||
|
**KeePassDX (please complete the following information):**
|
||||||
|
- Version: [e.g. 2.5.0.0beta23]
|
||||||
|
- Build: [e.g. Free]
|
||||||
|
- Language: [e.g. French]
|
||||||
|
|
||||||
|
**Android (please complete the following information):**
|
||||||
|
- Device: [e.g. GalaxyS8]
|
||||||
|
- Version: [e.g. 8.1]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
|
- Browser for Autofill: [e.g. Chrome version X]
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: feature
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -38,6 +38,13 @@ proguard/
|
|||||||
# Android Studio captures folder
|
# Android Studio captures folder
|
||||||
captures/
|
captures/
|
||||||
|
|
||||||
|
# Eclipse/VS Code
|
||||||
|
.project
|
||||||
|
.settings/*
|
||||||
|
*/.project
|
||||||
|
*/.classpath
|
||||||
|
*/.settings/*
|
||||||
|
|
||||||
# Intellij
|
# Intellij
|
||||||
*.iml
|
*.iml
|
||||||
.idea/workspace.xml
|
.idea/workspace.xml
|
||||||
|
|||||||
148
CHANGELOG
148
CHANGELOG
@@ -1,4 +1,102 @@
|
|||||||
KeepassDX (2.5.0.0beta21)
|
KeePassDX(2.5)
|
||||||
|
* First stable version of KeePassDX
|
||||||
|
* Fork completely rewritten from the KeePassDroid project
|
||||||
|
* Fix small issues from the last Release Candidate
|
||||||
|
|
||||||
|
KeePassDX(2.5RC2)
|
||||||
|
* Replacement of Spongy Castle by Bouncy Castle
|
||||||
|
* Update Autofill compatibility
|
||||||
|
* Fix Magikeyboard "Go" action
|
||||||
|
* Fix KeeWeb database opening
|
||||||
|
* Fix default username
|
||||||
|
* Fix themes
|
||||||
|
* Fix small issues
|
||||||
|
|
||||||
|
KeePassDX(2.5RC1)
|
||||||
|
* Add write permission to keep compatibility with old file managers
|
||||||
|
* Fix autofill for apps
|
||||||
|
* Auto search for autofill
|
||||||
|
* 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)
|
||||||
|
* Open/Save database as service / Add persistent notification
|
||||||
|
* Fix settings / edit group / small bugs
|
||||||
|
|
||||||
|
KeePassDX (2.5.0.0beta23)
|
||||||
|
* New, more secure database creation workflow
|
||||||
|
* Recognize more database files
|
||||||
|
* Add alias for history files (WARNING: history is erased)
|
||||||
|
* New Biometric unlock (Fingerprint with new API)
|
||||||
|
* Fix entry references
|
||||||
|
* Fix OOM with KeyFile
|
||||||
|
* Fix small issues
|
||||||
|
|
||||||
|
KeePassDX (2.5.0.0beta22)
|
||||||
|
* Rebuild code for actions
|
||||||
|
* Add UUID as entry view
|
||||||
|
* Fix bug with natural order
|
||||||
|
* Fix number of entries in databaseV1
|
||||||
|
* New entry views
|
||||||
|
|
||||||
|
KeePassDX (2.5.0.0beta21)
|
||||||
* Fix nested groups no longer visible in V1 databases
|
* Fix nested groups no longer visible in V1 databases
|
||||||
* Improved data import algorithm for V1 databases
|
* Improved data import algorithm for V1 databases
|
||||||
* Add natural database sort
|
* Add natural database sort
|
||||||
@@ -6,10 +104,10 @@ KeepassDX (2.5.0.0beta21)
|
|||||||
* Fix button disabled with only KeyFile
|
* Fix button disabled with only KeyFile
|
||||||
* Show the number of entries in a group
|
* 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
|
* Fix a major bug that displays an entry history
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta19)
|
KeePassDX (2.5.0.0beta19)
|
||||||
* Add lock button always visible
|
* Add lock button always visible
|
||||||
* New connection workflow
|
* New connection workflow
|
||||||
* Code refactored in Kotlin
|
* Code refactored in Kotlin
|
||||||
@@ -20,7 +118,7 @@ KeepassDX (2.5.0.0beta19)
|
|||||||
* Fix memory when load database
|
* Fix memory when load database
|
||||||
* Fix small bugs
|
* Fix small bugs
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta18)
|
KeePassDX (2.5.0.0beta18)
|
||||||
* New recent databases views
|
* New recent databases views
|
||||||
* New information dialog
|
* New information dialog
|
||||||
* Custom fields for the Magikeyboard
|
* Custom fields for the Magikeyboard
|
||||||
@@ -29,10 +127,10 @@ KeepassDX (2.5.0.0beta18)
|
|||||||
* Fix memory when opening the database
|
* Fix memory when opening the database
|
||||||
* Memory management for attachments
|
* Memory management for attachments
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta17)
|
KeePassDX (2.5.0.0beta17)
|
||||||
* Fix font and search
|
* Fix font and search
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta16)
|
KeePassDX (2.5.0.0beta16)
|
||||||
* New search in a single fragment
|
* New search in a single fragment
|
||||||
* Search suggestions
|
* Search suggestions
|
||||||
* Added the display of usernames
|
* Added the display of usernames
|
||||||
@@ -40,20 +138,20 @@ KeepassDX (2.5.0.0beta16)
|
|||||||
* Fix read-only mode
|
* Fix read-only mode
|
||||||
* Fix parcelable / toolbar / back
|
* Fix parcelable / toolbar / back
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta15)
|
KeePassDX (2.5.0.0beta15)
|
||||||
* Read only mode
|
* Read only mode
|
||||||
* Best group recovery for the navigation fragment
|
* Best group recovery for the navigation fragment
|
||||||
* Fix copies in notifications
|
* Fix copies in notifications
|
||||||
* Fix orientation
|
* Fix orientation
|
||||||
* Added translations
|
* Added translations
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta14)
|
KeePassDX (2.5.0.0beta14)
|
||||||
* Optimize all the memory with parcelables / fix search
|
* 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)
|
* 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 the Magikeyboard to fill the forms (settings still in development)
|
||||||
* Added move and copy for groups and entries
|
* Added move and copy for groups and entries
|
||||||
* New navigation in a single screen / new animations between activities
|
* New navigation in a single screen / new animations between activities
|
||||||
@@ -66,10 +164,10 @@ KeepassDX (2.5.0.0beta12)
|
|||||||
* Fix the fingerprint recognition (WARNING : The keystore is reinit, you must delete the old keys)
|
* Fix the fingerprint recognition (WARNING : The keystore is reinit, you must delete the old keys)
|
||||||
* Fix small bugs
|
* Fix small bugs
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta11)
|
KeePassDX (2.5.0.0beta11)
|
||||||
* Fix crash in beta10 version
|
* Fix crash in beta10 version
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta10)
|
KeePassDX (2.5.0.0beta10)
|
||||||
* Dynamically change Algorithm and Key Derivation Function in settings
|
* Dynamically change Algorithm and Key Derivation Function in settings
|
||||||
* Upgrade translations
|
* Upgrade translations
|
||||||
* New red volcano theme, fix classic dark theme
|
* New red volcano theme, fix classic dark theme
|
||||||
@@ -77,7 +175,7 @@ KeepassDX (2.5.0.0beta10)
|
|||||||
* Update fingerprint state with checkbox
|
* Update fingerprint state with checkbox
|
||||||
* Fix bugs
|
* Fix bugs
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta9)
|
KeePassDX (2.5.0.0beta9)
|
||||||
* Education Screens to learn how to use the app
|
* Education Screens to learn how to use the app
|
||||||
* New designs
|
* New designs
|
||||||
* New custom font for character visibility
|
* New custom font for character visibility
|
||||||
@@ -86,9 +184,9 @@ KeepassDX (2.5.0.0beta9)
|
|||||||
* Change setting organisation
|
* Change setting organisation
|
||||||
* Pro version
|
* Pro version
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta8)
|
KeePassDX (2.5.0.0beta8)
|
||||||
* Hide custom entries protected
|
* 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
|
* Change database / default settings
|
||||||
* Add Autofill for search
|
* Add Autofill for search
|
||||||
* Add sorting by last access and by creation time
|
* Add sorting by last access and by creation time
|
||||||
@@ -96,7 +194,7 @@ KeepassDX (2.5.0.0beta8)
|
|||||||
* Refactor old code
|
* Refactor old code
|
||||||
* Fix bugs
|
* Fix bugs
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta7)
|
KeePassDX (2.5.0.0beta7)
|
||||||
* Rebuild Notifications
|
* Rebuild Notifications
|
||||||
* Change links to https
|
* Change links to https
|
||||||
* Add extended Ascii (ñæËÌÂÝÜ...)
|
* Add extended Ascii (ñæËÌÂÝÜ...)
|
||||||
@@ -105,10 +203,10 @@ KeepassDX (2.5.0.0beta7)
|
|||||||
* Add setting to prevent the password copy
|
* Add setting to prevent the password copy
|
||||||
* Fix bugs
|
* Fix bugs
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta6)
|
KeePassDX (2.5.0.0beta6)
|
||||||
* Fix crash
|
* Fix crash
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta5)
|
KeePassDX (2.5.0.0beta5)
|
||||||
* Autofill (Android O)
|
* Autofill (Android O)
|
||||||
* Deletion for group
|
* Deletion for group
|
||||||
* New sorts with (Asc/Dsc, Groups before or after)
|
* New sorts with (Asc/Dsc, Groups before or after)
|
||||||
@@ -129,7 +227,7 @@ KeepassDX (2.5.0.0beta5)
|
|||||||
* Fix many small bugs
|
* Fix many small bugs
|
||||||
* Add recycle bin setting (not yet accessible)
|
* Add recycle bin setting (not yet accessible)
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta4)
|
KeePassDX (2.5.0.0beta4)
|
||||||
* Show only file name
|
* Show only file name
|
||||||
* Setting for full path
|
* Setting for full path
|
||||||
* Add information for each database file
|
* Add information for each database file
|
||||||
@@ -138,7 +236,7 @@ KeepassDX (2.5.0.0beta4)
|
|||||||
* Delete view assignment for fingerprint opening
|
* Delete view assignment for fingerprint opening
|
||||||
* Merge KeePassDroid 2.2.1
|
* Merge KeePassDroid 2.2.1
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta3)
|
KeePassDX (2.5.0.0beta3)
|
||||||
* New database workflow with new screens and folder selection
|
* New database workflow with new screens and folder selection
|
||||||
* Settings for default password generation
|
* Settings for default password generation
|
||||||
* Fingerprint dialog for explanations
|
* Fingerprint dialog for explanations
|
||||||
@@ -149,17 +247,17 @@ KeepassDX (2.5.0.0beta3)
|
|||||||
* Merge KeePassDroid 2.2.0.9
|
* Merge KeePassDroid 2.2.0.9
|
||||||
* Add corruption fix mode
|
* Add corruption fix mode
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta2)
|
KeePassDX (2.5.0.0beta2)
|
||||||
* Remove libs for F-Droid
|
* Remove libs for F-Droid
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta1)
|
KeePassDX (2.5.0.0beta1)
|
||||||
* Fork KeepassDroid
|
* Fork KeePassDroid
|
||||||
* Add Material Design
|
* Add Material Design
|
||||||
* Add Light and Night theme
|
* Add Light and Night theme
|
||||||
* Min API is 14
|
* Min API is 14
|
||||||
* Solve bug for fingerprint
|
* Solve bug for fingerprint
|
||||||
* Update French translation
|
* 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)
|
KeePassDroid (2.2.1)
|
||||||
* Fix kdbx4 date corruption
|
* Fix kdbx4 date corruption
|
||||||
@@ -420,7 +518,7 @@ KeePassDroid (1.9.10)
|
|||||||
|
|
||||||
KeePassDroid (1.9.9)
|
KeePassDroid (1.9.9)
|
||||||
* Go back to explicitly storing blank fields in the database
|
* 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
|
* Add support for native code on MIPS architectures
|
||||||
* Adding Vibrate permission. On some devices notifications fail
|
* Adding Vibrate permission. On some devices notifications fail
|
||||||
without the vibrate permission.
|
without the vibrate permission.
|
||||||
|
|||||||
45
CONTRIBUTORS
45
CONTRIBUTORS
@@ -1,45 +0,0 @@
|
|||||||
Original author:
|
|
||||||
Brian Pellin
|
|
||||||
|
|
||||||
Achim Weimert
|
|
||||||
Johan Berts - search patches
|
|
||||||
Mike Mohr - Better native code for aes and sha
|
|
||||||
Tobias Selig - icon support
|
|
||||||
Tolga Onbay, Dirk Bergstrom - password generator
|
|
||||||
Space Cowboy - holo theme
|
|
||||||
josefwells
|
|
||||||
Nicholas FitzRoy-Dale - auto launch intents
|
|
||||||
yulin2 - responsiveness improvements
|
|
||||||
Tadashi Saito
|
|
||||||
vhschlenker
|
|
||||||
bumper314 - Samsung multiwindow support
|
|
||||||
Hans Cappelle - fingerprint sensor integration
|
|
||||||
Jeremy Jamet - Keepass DX Material Design - Patches
|
|
||||||
|
|
||||||
Translations:
|
|
||||||
Diego Pierotto - Italian
|
|
||||||
Laurent, Norman Obry, Nam, Bruno Parmentier, Credomo - French
|
|
||||||
Maciej Bieniek, cod3r - Polish
|
|
||||||
Максим Сёмочкин, i.nedoboy, filimonic, bboa - Russian
|
|
||||||
MaWi, rvs2008, meviox, MaDill, EdlerProgrammierer, Jan Thomas - German
|
|
||||||
yslandro - Norwegian Nynorsk
|
|
||||||
王科峰 - Chinese
|
|
||||||
Typhoon - Slovak
|
|
||||||
Masahiro Inamura - Japanese
|
|
||||||
Matsuu Takuto - Japanese
|
|
||||||
Carlos Schlyter - Portugese (Brazil)
|
|
||||||
YSmhXQDd6Z - Portugese (Portugal)
|
|
||||||
andriykopanytsia - Ukranian
|
|
||||||
intel, Zoltán Antal - Hungarian
|
|
||||||
H Vanek - Czech
|
|
||||||
jipanos - Spanish
|
|
||||||
Erik Fdevriendt, Erik Jan Meijer - Dutch
|
|
||||||
Frederik Svarre - Danish
|
|
||||||
Oriol Garrote - Catalan
|
|
||||||
Mika Takala - Finnish
|
|
||||||
Niclas Burgren - Swedish
|
|
||||||
Raimonds - Latvian
|
|
||||||
dgarciabad - Basque
|
|
||||||
Arthur Zamarin - Hebrew
|
|
||||||
RaptorTFX - Greek
|
|
||||||
zygimantus - Lithuanian
|
|
||||||
4
LICENSE
4
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
KeePass DX is free software: you can redistribute it and/or modify
|
KeePassDX is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
@@ -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
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
177
LICENSES/LICENSE_ANDROID_BACKUP_SERVICE
Normal file
177
LICENSES/LICENSE_ANDROID_BACKUP_SERVICE
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
Terms of Service
|
||||||
|
|
||||||
|
This is the terms of service for the Android Backup Service.
|
||||||
|
|
||||||
|
1. Your relationship with Google
|
||||||
|
|
||||||
|
1.1 Your use of the Android Backup Service (referred to as the "Service" in this document) is subject to the terms of a legal agreement between you and Google. "Google" means Google LLC, whose principal place of business is at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States. This document explains how the agreement is made up, and sets out some of the terms of that agreement.
|
||||||
|
|
||||||
|
1.2 Unless otherwise agreed in writing with Google, your agreement with Google will always include, at a minimum, the terms and conditions set out in this document. These are referred to below as the "Terms".
|
||||||
|
|
||||||
|
1.3 The Terms form a legally binding agreement between you and Google in relation to your use of the Service. It is important that you take the time to read them carefully.
|
||||||
|
|
||||||
|
2. Accepting the Terms
|
||||||
|
|
||||||
|
2.1 In order to use the Service, you must first agree to the Terms. You may not use the Service if you do not accept the Terms.
|
||||||
|
|
||||||
|
2.2 You can accept the Terms by clicking to accept or agree to the Terms, where this option is made available to you by Google.
|
||||||
|
|
||||||
|
2.3 You may not use the Service and may not accept the Terms if you are not of legal age to form a binding contract with Google.
|
||||||
|
|
||||||
|
2.4 You represent that you have full power, capacity and authority to accept these Terms. If you are accepting on behalf of your employer or another entity, you represent that you have full legal authority to bind your employer or such entity to these Terms. If you don't have the legal authority to bind, please ensure that an authorized person from your entity consents to and accepts these Terms.
|
||||||
|
|
||||||
|
3. Provision of the Service by Google
|
||||||
|
|
||||||
|
3.1 Google has subsidiaries and affiliated legal entities around the world ("Subsidiaries and Affiliates"). Sometimes, these companies will be providing the Service to you on behalf of Google itself. You acknowledge and agree that Subsidiaries and Affiliates will be entitled to provide the Service to you.
|
||||||
|
|
||||||
|
3.2 Google is constantly innovating in order to provide the best possible experience for its users. You acknowledge and agree that the form and nature of the Service which Google provides may change from time to time without prior notice to you.
|
||||||
|
|
||||||
|
3.3 As part of this continuing innovation, you acknowledge and agree that Google may stop (permanently or temporarily) providing the Service (or any features within the Service) to you or to users generally at Google's sole discretion, without prior notice to you. You may stop using the Service at any time. You do not need to specifically inform Google when you stop using the Service.
|
||||||
|
|
||||||
|
3.4 You acknowledge and agree that if Google disables your Backup Service Key, you and the Android application(s) you developed ("Application(s)") may be prevented from accessing the Service and any content that is stored with the Service.
|
||||||
|
|
||||||
|
3.5 You acknowledge and agree that Google may set a fixed upper limit on the number of backup transmissions you may send or receive through the Service or on the amount of storage space used for the provision of the Service at any time, at Google's discretion. You agree to abide by any such fixed upper limits.
|
||||||
|
|
||||||
|
4. Use of the Service by you
|
||||||
|
|
||||||
|
4.1 In order to access the Service, you must have a unique application identifier ("Package Name") for your Application as described in the documentation for the Service.
|
||||||
|
|
||||||
|
4.2 After supplying Google with the Package Name and accepting the Terms, you will be issued an alphanumeric key ("Backup Service Key") assigned to you by Google that is uniquely associated with your Application. Your Application must include this Backup Service Key as described in the documentation for the Service.
|
||||||
|
|
||||||
|
4.3 There is currently no limit to the number of Backup Service Keys you may obtain in this manner provided that you use a different Package Name for each Backup Service Key you obtain. You agree that each Backup Service Key is only valid for Applications with the corresponding Package Name. You agree that Google may, in its sole discretion, impose a limit on the number of Backup Service Keys that may be obtained in the future. You agree that your continued use of any of the Backup Service Keys assigned by Google, or distribution of any Applications using such Backup Service Keys, constitutes your continued agreement to these Terms.
|
||||||
|
|
||||||
|
4.4 You agree to use the Service only for purposes that are permitted by (a) the Terms and (b) any applicable law, regulation, third-party terms of service, or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries).
|
||||||
|
|
||||||
|
4.5 You agree not to access (or attempt to access) any of the Service by any means other than through the interfaces, methods, and APIs that are provided by Google, unless you have been specifically allowed to do so in a separate agreement with Google.
|
||||||
|
|
||||||
|
4.6 You agree that you will not engage in any activity that interferes with or disrupts the Service (or the servers and networks which are connected to the Service), or the servers or networks of any third-party.
|
||||||
|
|
||||||
|
4.7 You agree that your use of the Service will be in compliance with any documentation guidelines provided by Google and that failure to comply with the documentation guidelines may result in the disabling of the Backup Service Key(s) for your Application(s).
|
||||||
|
|
||||||
|
4.8 Unless you have been specifically permitted to do so in a separate agreement with Google, you agree that you will not reproduce, duplicate, copy, sell, trade or resell (a) use of the Service, or (b) access to the Service.
|
||||||
|
|
||||||
|
4.9 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) your and your Application's use of the Service, any breach of your obligations under the Terms, and for the consequences (including any loss or damage which Google may suffer) of any such breach.
|
||||||
|
|
||||||
|
4.10 You agree that in your use of the Service, you and your Applications will protect the privacy and legal rights of users. You must provide legally adequate privacy notice and protection for users whose data your Applications back up to the Service. Further, your Application may only use that information for the limited purpose of backing up the data to the Service unless the user has given you permission for further use. If the user has not given you permission to back up information to the Service, you may not transmit such information to the Service.
|
||||||
|
|
||||||
|
4.11 You agree that you and your Applications will not transmit or store sensitive user information, such as user names, passwords, or credit card numbers, through the Service.
|
||||||
|
|
||||||
|
5. Security
|
||||||
|
|
||||||
|
5.1 You agree and understand that you are responsible for maintaining the security associated with any information you provide to access the Service as well as of the Backup Service Key(s) assigned to you by Google. You agree that only you are authorized to use the Backup Service Key(s) assigned to you.
|
||||||
|
|
||||||
|
5.2 Accordingly, you agree that you will be solely responsible to Google for all activities that occur in connection with your access to the Service, as well as the Backup Service Key.
|
||||||
|
|
||||||
|
5.3 If you become aware of any unauthorized use of your Backup Service Key(s) you agree to notify Google immediately.
|
||||||
|
|
||||||
|
6. Privacy and your personal information
|
||||||
|
|
||||||
|
6.1 For information about Google's data protection practices, please read Google's privacy policy at http://www.google.com/privacy.html. This policy explains how Google treats your personal information when you use the Service.
|
||||||
|
|
||||||
|
6.2 You agree to the use of your data in accordance with Google's privacy policies.
|
||||||
|
|
||||||
|
7. Content in the Service
|
||||||
|
|
||||||
|
7.1 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any Content that you or your Applications transmit or store through the Service and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so. You agree that you are solely responsible for (A) any Content that is transmitted through the Service by your Applications, and (B) any Content that Devices retrieve from the Service by virtue of your Applications. For purposes of the Terms, "Content" means information such as data, messages, settings information, written text, computer software, music, audio files or other sounds, photographs, videos or other images. "Device(s)" means device(s) powered by the Android operating system.
|
||||||
|
|
||||||
|
7.2 You agree that you will not transmit any Content through the Service that is copyrighted, protected by trade secret or otherwise subject to third party proprietary rights, including patent, privacy and publicity rights, unless you are the owner of such rights or have permission from their rightful owner to transmit the Content through the Service.
|
||||||
|
|
||||||
|
8. Proprietary rights
|
||||||
|
|
||||||
|
8.1 You acknowledge and agree that Google (or Google's licensors) own all legal right, title and interest in and to the Service, including any intellectual property rights which subsist in the Service (whether those rights happen to be registered or not, and wherever in the world those rights may exist).
|
||||||
|
|
||||||
|
8.2 Unless you have agreed otherwise in writing with Google, nothing in the Terms gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, and other distinctive brand features.
|
||||||
|
|
||||||
|
8.3 If you have been given an explicit right to use any of these brand features in a separate written agreement with Google, then you agree that your use of such features shall be in compliance with that agreement, any applicable provisions of the Terms, and Google's brand feature use guidelines as updated from time to time. These guidelines can be viewed online at http://www.google.com/permissions/ guidelines.html (or such other URL as Google may provide for this purpose from time to time).
|
||||||
|
|
||||||
|
8.4 You agree that you shall not remove, obscure, or alter any proprietary rights notices (including copyright, trade mark notices) which may be affixed to or contained within the Service.
|
||||||
|
|
||||||
|
9. License from Google
|
||||||
|
|
||||||
|
9.1 Subject to terms and conditions of these Terms, Google gives you a personal, worldwide, royalty-free, non-assignable and non-exclusive license to use the Service as provided to you by Google. This license is for the sole purpose of enabling you to use and enjoy the benefit of the Service as provided by Google, in the manner permitted by the Terms.
|
||||||
|
|
||||||
|
9.2 You may not (and you may not permit anyone else to) copy, modify, create a derivative work of, reverse engineer, decompile or otherwise attempt to extract the source code from the Service or any part thereof, unless this is expressly permitted or required by law, or unless you have been specifically told that you may do so by Google, in writing.
|
||||||
|
|
||||||
|
9.3 Unless Google has given you specific written permission to do so, you may not assign (or grant a sub-license of) your rights to use the Service, grant a security interest in or over your rights to use the Service, or otherwise transfer any part of your rights to use the Service.
|
||||||
|
|
||||||
|
10. Your code
|
||||||
|
|
||||||
|
10.1 Google claims no ownership or control over any source code written by you to be used with the Service. You retain copyright and any other rights you already hold in this code, and you are responsible for protecting those rights, as appropriate.
|
||||||
|
|
||||||
|
11. Ending your relationship with Google
|
||||||
|
|
||||||
|
11.1 The Terms will continue to apply until terminated by either you or Google as set out below.
|
||||||
|
|
||||||
|
11.2 You may terminate your legal agreement with Google by discontinuing your use of the Service at any time.
|
||||||
|
|
||||||
|
11.3 Google may, at any time, terminate its legal agreement with you if:
|
||||||
|
|
||||||
|
(A) you have breached any provision of the Terms (or have acted in manner which clearly shows that you do not intend to, or are unable to comply with the provisions of the Terms); or
|
||||||
|
|
||||||
|
(B) Google is required to do so by law (for example, where the provision of the Service to you is, or becomes, unlawful); or
|
||||||
|
|
||||||
|
(C) Google is transitioning to no longer providing the Service; or
|
||||||
|
|
||||||
|
(D) your Application fails to meet the documentation guidelines provided by Google.
|
||||||
|
|
||||||
|
11.4 Nothing in this Section shall affect Google's rights regarding provision of the Service under Section 3 of the Terms.
|
||||||
|
|
||||||
|
11.5 When these Terms come to an end, all of the legal rights, obligations and liabilities that you and Google have benefited from, been subject to (or which have accrued over time whilst the Terms have been in force) or which are expressed to continue indefinitely, shall be unaffected by this cessation, and the provisions of Sections 12, 13 and Paragraph 16 shall continue to apply to such rights, obligations and liabilities indefinitely.
|
||||||
|
|
||||||
|
12. EXCLUSION OF WARRANTIES
|
||||||
|
|
||||||
|
12.1 NOTHING IN THESE TERMS, INCLUDING SECTIONS 12 AND 13, SHALL EXCLUDE OR LIMIT GOOGLE'S WARRANTY OR LIABILITY FOR LOSSES WHICH MAY NOT BE LAWFULLY EXCLUDED OR LIMITED BY APPLICABLE LAW. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR CONDITIONS OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR LOSS OR DAMAGE CAUSED BY NEGLIGENCE, BREACH OF CONTRACT OR BREACH OF IMPLIED TERMS, OR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, ONLY THE LIMITATIONS WHICH ARE LAWFUL IN YOUR JURISDICTION WILL APPLY TO YOU AND OUR LIABILITY WILL BE LIMITED TO THE MAXIMUM EXTENT PERMITTED BY LAW.
|
||||||
|
|
||||||
|
12.2 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE SERVICE IS AT YOUR SOLE RISK AND THAT THE SERVICE AND CONTENT ARE PROVIDED "AS IS" AND "AS AVAILABLE".
|
||||||
|
|
||||||
|
12.3 IN PARTICULAR, GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS DO NOT REPRESENT OR WARRANT TO YOU THAT:
|
||||||
|
|
||||||
|
(A) YOUR USE OF THE SERVICE WILL MEET YOUR REQUIREMENTS,
|
||||||
|
|
||||||
|
(B) YOUR USE OF THE SERVICE WILL BE UNINTERRUPTED, TIMELY, SECURE OR FREE FROM ERROR, AND
|
||||||
|
|
||||||
|
(C) THAT DEFECTS IN THE OPERATION OR FUNCTIONALITY OF ANY SOFTWARE PROVIDED TO YOU AS PART OF THE SERVICE WILL BE CORRECTED.
|
||||||
|
|
||||||
|
12.4 NO ADVICE OR INFORMATION, WHETHER ORAL OR WRITTEN, OBTAINED BY YOU FROM GOOGLE OR THROUGH OR FROM THE SERVICE SHALL CREATE ANY WARRANTY NOT EXPRESSLY STATED IN THE TERMS.
|
||||||
|
|
||||||
|
12.5 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
|
||||||
|
|
||||||
|
13. LIMITATION OF LIABILITY
|
||||||
|
|
||||||
|
13.1 SUBJECT TO OVERALL PROVISION IN PARAGRAPH 12.1 ABOVE, YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU FOR:
|
||||||
|
|
||||||
|
(A) ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL CONSEQUENTIAL OR EXEMPLARY DAMAGES WHICH MAY BE INCURRED BY YOU, HOWEVER CAUSED AND UNDER ANY THEORY OF LIABILITY. THIS SHALL INCLUDE, BUT NOT BE LIMITED TO, ANY LOSS OF PROFIT (WHETHER INCURRED DIRECTLY OR INDIRECTLY), ANY LOSS OF GOODWILL OR BUSINESS REPUTATION, ANY LOSS OF DATA SUFFERED, COST OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICE, OR OTHER INTANGIBLE LOSS;
|
||||||
|
|
||||||
|
(B) ANY LOSS OR DAMAGE WHICH MAY BE INCURRED BY YOU, INCLUDING BUT NOT LIMITED TO LOSS OR DAMAGE AS A RESULT OF:
|
||||||
|
|
||||||
|
(I) ANY CHANGES WHICH GOOGLE MAY MAKE TO THE SERVICE, OR FOR ANY PERMANENT OR TEMPORARY CESSATION IN THE PROVISION OF THE SERVICE (OR ANY FEATURES WITHIN THE SERVICE);
|
||||||
|
|
||||||
|
(II) THE DELETION OF, CORRUPTION OF, OR FAILURE TO STORE, ANY CONTENT AND OTHER COMMUNICATIONS DATA MAINTAINED OR TRANSMITTED BY OR THROUGH YOUR USE OF THE SERVICE;
|
||||||
|
|
||||||
|
(III) YOUR FAILURE TO PROVIDE GOOGLE WITH ACCURATE ACCOUNT INFORMATION; OR
|
||||||
|
|
||||||
|
(IV) YOUR FAILURE TO KEEP YOUR PASSWORD OR ACCOUNT DETAILS SECURE AND CONFIDENTIAL.
|
||||||
|
|
||||||
|
13.2 THE LIMITATIONS ON GOOGLE'S LIABILITY TO YOU IN PARAGRAPH 13.1 ABOVE SHALL APPLY WHETHER OR NOT GOOGLE HAS BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING.
|
||||||
|
|
||||||
|
14. Indemnification
|
||||||
|
|
||||||
|
14.1 You agree to hold harmless and indemnify Google, and its subsidiaries, affiliates, officers, agents, employees, or licensors from and against any third party claim arising from or in any way related to (a) your breach of the Terms, (b) your use of the Service, or (c) your violation of applicable laws, rules or regulations in connection with the Service, including any liability or expense arising from all claims, losses, damages (actual and consequential), suits, judgments, litigation costs and attorneys' fees, of every kind and nature. In such a case, Google will provide you with written notice of such claim, suit or action.
|
||||||
|
|
||||||
|
15. Changes to the Terms
|
||||||
|
|
||||||
|
15.1 Due to things like changes to the law or changes to functionality offered through the Service, Google may need to change these Terms from time to time. You should look at the Terms regularly. We'll post notice of the modified Terms within, or through, the Service. Once the modified Terms are posted, the changes will become effective immediately, and you are deemed to have accepted the modified Terms if you continue to use the Service. If you do not agree to the modified Terms for the Service, please stop using the Service.
|
||||||
|
|
||||||
|
16. General legal terms
|
||||||
|
|
||||||
|
16.1 The Terms constitute the whole legal agreement between you and Google and govern your use of the Service (but excluding any service which Google may provide to you under a separate written agreement), and completely replace any prior agreements between you and Google in relation to the Service.
|
||||||
|
|
||||||
|
16.2 You agree that Google may provide you with notices, including those regarding changes to the Terms, by email, regular mail, or postings on the Service.
|
||||||
|
|
||||||
|
16.3 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in the Terms (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google.
|
||||||
|
|
||||||
|
16.4 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of these Terms is invalid, then that provision will be removed from the Terms without affecting the rest of the Terms. The remaining provisions of the Terms will continue to be valid and enforceable.
|
||||||
|
|
||||||
|
16.5 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to the Terms and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the Terms which confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to the Terms.
|
||||||
|
|
||||||
|
16.6 The Terms, and your relationship with Google under the Terms, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from the Terms. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction.
|
||||||
91
README.md
Normal file
91
README.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# Android KeepassDX
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> KeepassDX is a **multi-format KeePass manager for Android devices**. The app allows creating keys and passwords in a secure way by integrating with the Android design standards.
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen.jpg" width="220">
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Create database files / entries and groups.
|
||||||
|
- Support for **.kdb** and **.kdbx** files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm.
|
||||||
|
- **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePassXC, …).
|
||||||
|
- Allows opening and **copying URI / URL fields quickly**.
|
||||||
|
- **Biometric recognition** for fast unlocking *(fingerprint / face unlock / …)*.
|
||||||
|
- **One-Time Password** management *(HOTP / TOTP)* for Two-factor authentication (2FA).
|
||||||
|
- Material design with **themes**.
|
||||||
|
- **Auto-Fill** and Integration.
|
||||||
|
- Field filling **keyboard**.
|
||||||
|
- **History** of each entry.
|
||||||
|
- Precise management of **settings**.
|
||||||
|
- Code written in **native languages** *(Kotlin / Java / JNI / C)*.
|
||||||
|
|
||||||
|
KeepassDX is **open source** and **ad-free**.
|
||||||
|
|
||||||
|
## What is KeePassDX?
|
||||||
|
|
||||||
|
An alternative to remembering an endless list of passwords manually. This is made more difficult by **using different passwords for each account**. If you use one password everywhere and security fails only one of those places, it grants access to your e-mail account, website, etc, and you may not know about it or notice, before bad things happen.
|
||||||
|
|
||||||
|
KeePassDX is a **password manager for Android**, which helps you **manage your passwords in a secure way**. You can put all your passwords in one database, locked with a **master key** and/or a **keyfile**. You **only have to remember one single master password and/or select the keyfile** to unlock the whole database. The databases are encrypted using the best and **most secure encryption algorithms** currently known.
|
||||||
|
|
||||||
|
## Small print?
|
||||||
|
|
||||||
|
KeePassDX is under **open source GPL3 license**, meaning you can use, study, change and share it at will. Copyleft ensures it stays that way.
|
||||||
|
From the full source, anyone can build, fork, and check whether for example the encryption algorithms are implemented correctly.
|
||||||
|
There is **no advertising**.
|
||||||
|
|
||||||
|
Do not worry, **the main features remain completely free**.
|
||||||
|
|
||||||
|
Optional visual styles are accessible after a contribution (and a congratulatory message (Ո‿Ո) ) or the purchase of an extended version to encourage contribution to the work of open source projects!
|
||||||
|
*If you contribute to the project and do not have access to the styles, do not hesitate to contact the author at [contact@kunzisoft.com](contact@kunzisoft.com).*
|
||||||
|
|
||||||
|
## Contributions
|
||||||
|
|
||||||
|
* Add features by making a **[pull request](https://help.github.com/articles/about-pull-requests/)**.
|
||||||
|
* Help to **[translate](https://hosted.weblate.org/projects/keepass-dx/strings/)** KeePassDX to your language (on [Weblate](https://hosted.weblate.org/projects/keepass-dx/) or by sending a [pull request](https://help.github.com/articles/about-pull-requests/)).
|
||||||
|
* **[Donate](https://www.kunzisoft.com/donation)** 人◕ ‿‿ ◕人Y for a better service and a quick development of your features.
|
||||||
|
* Buy the **[Pro version](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro)** of KeePassDX.
|
||||||
|
|
||||||
|
## Download
|
||||||
|
|
||||||
|
*[F-Droid](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/) is the recommended way of installing, a libre software project that verifies that all the libraries and app code is libre software.*
|
||||||
|
|
||||||
|
[<img src="https://f-droid.org/badge/get-it-on.png"
|
||||||
|
alt="Get it on F-Droid"
|
||||||
|
height="80">](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/)
|
||||||
|
|
||||||
|
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
|
||||||
|
alt="Get it on Google Play"
|
||||||
|
height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free)
|
||||||
|
|
||||||
|
## Frequently Asked Questions
|
||||||
|
|
||||||
|
Other questions? You can read the [FAQ](https://github.com/Kunzisoft/KeePassDX/wiki/FAQ)
|
||||||
|
|
||||||
|
## Other devices
|
||||||
|
|
||||||
|
- [KeePass](https://keepass.info/) (https://keepass.info/) is the original and official project for the desktop, with technical documentation for standardized database files. It is updated regularly with active maintenance (written in C#).
|
||||||
|
|
||||||
|
- [KeePassXC](https://keepassxc.org/) (https://keepassxc.org/) is an alternative integration of KeePass written in C++.
|
||||||
|
|
||||||
|
- [KeeWeb](https://keeweb.info/) (https://keeweb.info/) is a web version that is also compatible with KeePass files.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright © 2020 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
|
||||||
|
|
||||||
|
This file is part of KeePassDX.
|
||||||
|
|
||||||
|
[KeePassDX](https://www.keepassdx.com) is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
*This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.*
|
||||||
84
ReadMe.md
84
ReadMe.md
@@ -1,84 +0,0 @@
|
|||||||
# Android Keepass DX
|
|
||||||
|
|
||||||
<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/screen.jpg" width="220">
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Create database files / entries and groups
|
|
||||||
* Support for **.kdb** and **.kdbx** files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm
|
|
||||||
* **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePass XC...)
|
|
||||||
* Allows **fast copy** of fields and opening of URI / URL
|
|
||||||
* **Fingerprint** for fast unlocking
|
|
||||||
* 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**.
|
|
||||||
|
|
||||||
## What is KeePass DX?
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
*Note : If you access the application from a store, visual features may not be available to incentivize the contribution to the work of open source projects. These optional visuals are accessible after a donation (and a small congratulation message :) or the purchase of an extended version, but do not worry, the main features remain completely free. If you contribute to the project and you do not have access to the themes, do not hesitate to contact me at [contact@kunzisoft.com](contact@kunzisoft.com), I will give you the procedure.*
|
|
||||||
|
|
||||||
## Contributions
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
## Download
|
|
||||||
|
|
||||||
*We recommend the installation from [F-Droid](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/) which verifies that all libraries and application code are open source.*
|
|
||||||
|
|
||||||
[<img src="https://f-droid.org/badge/get-it-on.png"
|
|
||||||
alt="Get it on F-Droid"
|
|
||||||
height="80">](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/)
|
|
||||||
|
|
||||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
|
|
||||||
alt="Get it on Google Play"
|
|
||||||
height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free)
|
|
||||||
|
|
||||||
## F.A.Q.
|
|
||||||
|
|
||||||
Other questions? You can read the [F.A.Q.](https://www.keepassdx.com/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 historical project, with good technical documentation for standardized database files but only running on **Windows**.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Copyright (c) 2019 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
|
|
||||||
|
|
||||||
This file is part of KeePass DX.
|
|
||||||
|
|
||||||
[KeePass DX](https://www.keepassdx.com) is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
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/>.
|
|
||||||
|
|
||||||
*This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.*
|
|
||||||
1
_config.yml
Normal file
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
theme: jekyll-theme-cayman
|
||||||
1
app/.gitignore
vendored
1
app/.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
|
.cxx
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
|||||||
@@ -4,21 +4,29 @@ apply plugin: 'kotlin-android-extensions'
|
|||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 27
|
compileSdkVersion 29
|
||||||
buildToolsVersion '28.0.3'
|
buildToolsVersion '29.0.3'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.kunzisoft.keepass"
|
applicationId "com.kunzisoft.keepass"
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 27
|
targetSdkVersion 29
|
||||||
versionCode = 21
|
versionCode = 33
|
||||||
versionName = "2.5.0.0beta21"
|
versionName = "2.5"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||||
testInstrumentationRunner = "android.test.InstrumentationTestRunner"
|
testInstrumentationRunner = "android.test.InstrumentationTestRunner"
|
||||||
|
|
||||||
buildConfigField "String[]", "ICON_PACKS", "{\"classic\",\"material\"}"
|
buildConfigField "String[]", "ICON_PACKS", "{\"classic\",\"material\"}"
|
||||||
|
manifestPlaceholders = [ googleAndroidBackupAPIKey:"" ]
|
||||||
|
|
||||||
|
kapt {
|
||||||
|
arguments {
|
||||||
|
arg("room.incremental", "true")
|
||||||
|
arg("room.schemaLocation", "$projectDir/schemas".toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
@@ -27,7 +35,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled = false
|
minifyEnabled = false
|
||||||
@@ -38,32 +45,36 @@ android {
|
|||||||
dexOptions {
|
dexOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "tier"
|
flavorDimensions "version"
|
||||||
productFlavors {
|
productFlavors {
|
||||||
libre {
|
libre {
|
||||||
|
dimension "version"
|
||||||
applicationIdSuffix = ".libre"
|
applicationIdSuffix = ".libre"
|
||||||
buildConfigField "String", "BUILD_VERSION", "\"libre\""
|
buildConfigField "String", "BUILD_VERSION", "\"libre\""
|
||||||
buildConfigField "boolean", "FULL_VERSION", "true"
|
buildConfigField "boolean", "FULL_VERSION", "true"
|
||||||
buildConfigField "boolean", "CLOSED_STORE", "false"
|
buildConfigField "boolean", "CLOSED_STORE", "false"
|
||||||
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
|
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
|
||||||
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
|
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
|
||||||
|
|
||||||
}
|
}
|
||||||
pro {
|
pro {
|
||||||
|
dimension "version"
|
||||||
applicationIdSuffix = ".pro"
|
applicationIdSuffix = ".pro"
|
||||||
buildConfigField "String", "BUILD_VERSION", "\"pro\""
|
buildConfigField "String", "BUILD_VERSION", "\"pro\""
|
||||||
buildConfigField "boolean", "FULL_VERSION", "true"
|
buildConfigField "boolean", "FULL_VERSION", "true"
|
||||||
buildConfigField "boolean", "CLOSED_STORE", "true"
|
buildConfigField "boolean", "CLOSED_STORE", "true"
|
||||||
buildConfigField "String[]", "STYLES_DISABLED", "{}"
|
buildConfigField "String[]", "STYLES_DISABLED", "{}"
|
||||||
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
|
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
|
||||||
|
manifestPlaceholders = [ googleAndroidBackupAPIKey:"AEdPqrEAAAAIZiXvrQCzSV9LNI6-p7cjTKENZLHIrz_zaqZuQQ" ]
|
||||||
}
|
}
|
||||||
free {
|
free {
|
||||||
|
dimension "version"
|
||||||
applicationIdSuffix = ".free"
|
applicationIdSuffix = ".free"
|
||||||
buildConfigField "String", "BUILD_VERSION", "\"free\""
|
buildConfigField "String", "BUILD_VERSION", "\"free\""
|
||||||
buildConfigField "boolean", "FULL_VERSION", "false"
|
buildConfigField "boolean", "FULL_VERSION", "false"
|
||||||
buildConfigField "boolean", "CLOSED_STORE", "true"
|
buildConfigField "boolean", "CLOSED_STORE", "true"
|
||||||
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Blue\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
|
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Blue\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
|
||||||
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
|
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
|
||||||
|
manifestPlaceholders = [ googleAndroidBackupAPIKey:"AEdPqrEAAAAIbRfbV8fHLItXo8OcHwrO0sSNblqhPwkc0DPTqg" ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,43 +90,40 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def supportVersion = "27.1.1"
|
def room_version = "2.2.5"
|
||||||
def spongycastleVersion = "1.58.0.0"
|
|
||||||
def permissionDispatcherVersion = "3.3.1"
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
implementation "com.android.support:appcompat-v7:$supportVersion"
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation "com.android.support:design:$supportVersion"
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
implementation "com.android.support:preference-v7:$supportVersion"
|
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
||||||
implementation "com.android.support:preference-v14:$supportVersion"
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation "com.android.support:cardview-v7:$supportVersion"
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation "com.madgag.spongycastle:core:$spongycastleVersion"
|
implementation 'androidx.biometric:biometric:1.0.1'
|
||||||
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
|
implementation "androidx.core:core-ktx:1.2.0"
|
||||||
// Expandable view
|
// To upgrade with style
|
||||||
implementation 'net.cachapa.expandablelayout:expandablelayout:2.9.2'
|
implementation 'com.google.android.material:material:1.0.0'
|
||||||
|
// Database
|
||||||
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
|
// Crypto
|
||||||
|
implementation 'org.bouncycastle:bcprov-jdk15on:1.65'
|
||||||
// Time
|
// Time
|
||||||
implementation 'joda-time:joda-time:2.9.9'
|
implementation 'joda-time:joda-time:2.9.9'
|
||||||
implementation 'org.sufficientlysecure:html-textview:3.5'
|
// Color
|
||||||
implementation 'com.nononsenseapps:filepicker:4.1.0'
|
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.3'
|
||||||
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0'
|
// Education
|
||||||
// Permissions
|
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.0'
|
||||||
implementation("com.github.hotchemi:permissionsdispatcher:$permissionDispatcherVersion") {
|
|
||||||
// if you don't use android.app.Fragment you can exclude support for them
|
|
||||||
exclude module: "support-v13"
|
|
||||||
}
|
|
||||||
kapt "com.github.hotchemi:permissionsdispatcher-processor:$permissionDispatcherVersion"
|
|
||||||
// Apache Commons Collections
|
// Apache Commons Collections
|
||||||
implementation 'commons-collections:commons-collections:3.2.1'
|
implementation 'commons-collections:commons-collections:3.2.1'
|
||||||
implementation 'org.apache.commons:commons-io:1.3.2'
|
implementation 'org.apache.commons:commons-io:1.3.2'
|
||||||
// Base64
|
// Apache Commons Codec
|
||||||
implementation 'biz.source_code:base64coder:2010-12-19'
|
implementation 'commons-codec:commons-codec:1.11'
|
||||||
// IO-Extras
|
|
||||||
implementation 'com.github.davidmoten:io-extras:0.1'
|
|
||||||
implementation 'com.google.code.gson:gson:2.8.4'
|
|
||||||
implementation 'com.google.guava:guava:23.0-android'
|
|
||||||
// Icon pack
|
// Icon pack
|
||||||
implementation project(path: ':icon-pack-classic')
|
implementation project(path: ':icon-pack-classic')
|
||||||
implementation project(path: ':icon-pack-material')
|
implementation project(path: ':icon-pack-material')
|
||||||
|
|
||||||
|
// Tests
|
||||||
|
androidTestImplementation 'junit:junit:4.12'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests;
|
|
||||||
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.tests.database.TestData;
|
|
||||||
|
|
||||||
public class AccentTest extends AndroidTestCase {
|
|
||||||
|
|
||||||
private static final String KEYFILE = "";
|
|
||||||
private static final String PASSWORD = "é";
|
|
||||||
private static final String ASSET = "accent.kdb";
|
|
||||||
private static final String FILENAME = "/sdcard/accent.kdb";
|
|
||||||
|
|
||||||
public void testOpen() {
|
|
||||||
|
|
||||||
/*
|
|
||||||
try {
|
|
||||||
TestData.GetDb(getContext(), ASSET, PASSWORD, KEYFILE, FILENAME);
|
|
||||||
} catch (Exception e) {
|
|
||||||
assertTrue("Failed to open database", false);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests;
|
|
||||||
|
|
||||||
import junit.framework.Test;
|
|
||||||
import junit.framework.TestSuite;
|
|
||||||
|
|
||||||
import android.test.suitebuilder.TestSuiteBuilder;
|
|
||||||
|
|
||||||
public class AllTests extends TestSuite {
|
|
||||||
|
|
||||||
public static Test suite() {
|
|
||||||
return new TestSuiteBuilder(AllTests.class)
|
|
||||||
.includeAllPackagesUnderHere()
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests;
|
|
||||||
|
|
||||||
import junit.framework.Test;
|
|
||||||
import junit.framework.TestSuite;
|
|
||||||
|
|
||||||
import android.test.suitebuilder.TestSuiteBuilder;
|
|
||||||
|
|
||||||
public class OutputTests extends TestSuite {
|
|
||||||
|
|
||||||
public static Test suite() {
|
|
||||||
|
|
||||||
return new TestSuiteBuilder(AllTests.class)
|
|
||||||
.includePackages("com.kunzisoft.keepass.tests.output")
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests
|
|
||||||
|
|
||||||
import junit.framework.TestCase
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.PwDate
|
|
||||||
import org.junit.Assert
|
|
||||||
|
|
||||||
class PwDateTest : TestCase() {
|
|
||||||
|
|
||||||
fun testDate() {
|
|
||||||
val jDate = PwDate(System.currentTimeMillis())
|
|
||||||
val intermediate = PwDate(jDate)
|
|
||||||
val cDate = PwDate(intermediate.byteArrayDate!!, 0)
|
|
||||||
|
|
||||||
Assert.assertTrue("jDate and intermediate not equal", jDate == intermediate)
|
|
||||||
Assert.assertTrue("jDate and cDate not equal", cDate == jDate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.util.Calendar;
|
|
||||||
|
|
||||||
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.PwEntryV3;
|
|
||||||
import com.kunzisoft.keepass.tests.database.TestData;
|
|
||||||
|
|
||||||
public class PwEntryTestV3 extends AndroidTestCase {
|
|
||||||
PwEntryV3 mPE;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
// mPE = (PwEntryV3) TestData.GetTest1(getContext()).getEntryAt(0);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testName() {
|
|
||||||
assertTrue("Name was " + mPE.getTitle(), mPE.getTitle().equals("Amazon"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPassword() throws UnsupportedEncodingException {
|
|
||||||
String sPass = "12345";
|
|
||||||
byte[] password = sPass.getBytes("UTF-8");
|
|
||||||
|
|
||||||
assertArrayEquals(password, mPE.getPasswordBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testCreation() {
|
|
||||||
Calendar cal = Calendar.getInstance();
|
|
||||||
cal.setTime(mPE.getCreationTime().getDate());
|
|
||||||
|
|
||||||
assertEquals("Incorrect year.", cal.get(Calendar.YEAR), 2009);
|
|
||||||
assertEquals("Incorrect month.", cal.get(Calendar.MONTH), 3);
|
|
||||||
assertEquals("Incorrect day.", cal.get(Calendar.DAY_OF_MONTH), 23);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
public class PwEntryTestV4 extends TestCase {
|
|
||||||
public void testAssign() {
|
|
||||||
/*
|
|
||||||
TODO Test
|
|
||||||
PwEntryV4 entry = new PwEntryV4();
|
|
||||||
|
|
||||||
entry.setAdditional("test223");
|
|
||||||
|
|
||||||
entry.setAutoType(new AutoType());
|
|
||||||
entry.getAutoType().defaultSequence = "1324";
|
|
||||||
entry.getAutoType().enabled = true;
|
|
||||||
entry.getAutoType().obfuscationOptions = 123412432109L;
|
|
||||||
entry.getAutoType().put("key", "value");
|
|
||||||
|
|
||||||
entry.setBackgroundColor("blue");
|
|
||||||
entry.putProtectedBinary("key1", new ProtectedBinary(false, new byte[] {0,1}));
|
|
||||||
entry.setIconCustom(new PwIconCustom(UUID.randomUUID(), new byte[0]));
|
|
||||||
entry.setForegroundColor("red");
|
|
||||||
entry.addToHistory(new PwEntryV4());
|
|
||||||
entry.setIconStandard(new PwIconStandard(5));
|
|
||||||
entry.setOverrideURL("override");
|
|
||||||
entry.setParent(new PwGroupV4());
|
|
||||||
entry.addExtraField("key2", new ProtectedString(false, "value2"));
|
|
||||||
entry.setUrl("http://localhost");
|
|
||||||
entry.setNodeId(UUID.randomUUID());
|
|
||||||
|
|
||||||
PwEntryV4 target = new PwEntryV4();
|
|
||||||
target.updateWith(entry);
|
|
||||||
|
|
||||||
/* This test is not so useful now that I am not implementing value equality for Entries
|
|
||||||
assertTrue("Entries do not match.", entry.equals(target));
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests;
|
|
||||||
|
|
||||||
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.PwGroupV3;
|
|
||||||
import com.kunzisoft.keepass.tests.database.TestData;
|
|
||||||
|
|
||||||
public class PwGroupTest extends AndroidTestCase {
|
|
||||||
|
|
||||||
PwGroupV3 mPG;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
//mPG = (PwGroupV3) TestData.GetTest1(getContext()).getGroups().get(0);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testGroupName() {
|
|
||||||
//assertTrue("Name was " + mPG.getTitle(), mPG.getTitle().equals("Internet"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.AssetManager;
|
|
||||||
import android.os.Environment;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
public class TestUtil {
|
|
||||||
private static final File sdcard = Environment.getExternalStorageDirectory();
|
|
||||||
|
|
||||||
public static void extractKey(Context ctx, String asset, String target) throws Exception {
|
|
||||||
|
|
||||||
InputStream key = ctx.getAssets().open(asset, AssetManager.ACCESS_STREAMING);
|
|
||||||
|
|
||||||
FileOutputStream keyFile = new FileOutputStream(target);
|
|
||||||
while (true) {
|
|
||||||
byte[] buf = new byte[1024];
|
|
||||||
int read = key.read(buf);
|
|
||||||
if ( read == -1 ) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
keyFile.write(buf, 0, read);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keyFile.close();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getSdPath(String filename) {
|
|
||||||
File file = new File(sdcard, filename);
|
|
||||||
return file.getAbsolutePath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.PwDate;
|
|
||||||
import com.kunzisoft.keepass.stream.LEDataInputStream;
|
|
||||||
import com.kunzisoft.keepass.stream.LEDataOutputStream;
|
|
||||||
import com.kunzisoft.keepass.utils.Types;
|
|
||||||
|
|
||||||
public class TypesTest extends TestCase {
|
|
||||||
|
|
||||||
public void testReadWriteLongZero() {
|
|
||||||
testReadWriteLong((byte) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteLongMax() {
|
|
||||||
testReadWriteLong(Byte.MAX_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteLongMin() {
|
|
||||||
testReadWriteLong(Byte.MIN_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteLongRnd() {
|
|
||||||
Random rnd = new Random();
|
|
||||||
byte[] buf = new byte[1];
|
|
||||||
rnd.nextBytes(buf);
|
|
||||||
|
|
||||||
testReadWriteLong(buf[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testReadWriteLong(byte value) {
|
|
||||||
byte[] orig = new byte[8];
|
|
||||||
byte[] dest = new byte[8];
|
|
||||||
|
|
||||||
setArray(orig, value, 0, 8);
|
|
||||||
|
|
||||||
long one = LEDataInputStream.readLong(orig, 0);
|
|
||||||
LEDataOutputStream.writeLong(one, dest, 0);
|
|
||||||
|
|
||||||
assertArrayEquals(orig, dest);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteIntZero() {
|
|
||||||
testReadWriteInt((byte) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteIntMin() {
|
|
||||||
testReadWriteInt(Byte.MIN_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteIntMax() {
|
|
||||||
testReadWriteInt(Byte.MAX_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testReadWriteInt(byte value) {
|
|
||||||
byte[] orig = new byte[4];
|
|
||||||
byte[] dest = new byte[4];
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++ ) {
|
|
||||||
orig[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
setArray(orig, value, 0, 4);
|
|
||||||
|
|
||||||
int one = LEDataInputStream.readInt(orig, 0);
|
|
||||||
|
|
||||||
LEDataOutputStream.writeInt(one, dest, 0);
|
|
||||||
|
|
||||||
assertArrayEquals(orig, dest);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setArray(byte[] buf, byte value, int offset, int size) {
|
|
||||||
for (int i = offset; i < offset + size; i++) {
|
|
||||||
buf[i] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteShortOne() {
|
|
||||||
byte[] orig = new byte[2];
|
|
||||||
byte[] dest = new byte[2];
|
|
||||||
|
|
||||||
orig[0] = 0;
|
|
||||||
orig[1] = 1;
|
|
||||||
|
|
||||||
int one = LEDataInputStream.readUShort(orig, 0);
|
|
||||||
dest = LEDataOutputStream.writeUShortBuf(one);
|
|
||||||
|
|
||||||
assertArrayEquals(orig, dest);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteShortMin() {
|
|
||||||
testReadWriteShort(Byte.MIN_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteShortMax() {
|
|
||||||
testReadWriteShort(Byte.MAX_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testReadWriteShort(byte value) {
|
|
||||||
byte[] orig = new byte[2];
|
|
||||||
byte[] dest = new byte[2];
|
|
||||||
|
|
||||||
setArray(orig, value, 0, 2);
|
|
||||||
|
|
||||||
int one = LEDataInputStream.readUShort(orig, 0);
|
|
||||||
LEDataOutputStream.writeUShort(one, dest, 0);
|
|
||||||
|
|
||||||
assertArrayEquals(orig, dest);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteByteZero() {
|
|
||||||
testReadWriteByte((byte) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteByteMin() {
|
|
||||||
testReadWriteByte(Byte.MIN_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReadWriteByteMax() {
|
|
||||||
testReadWriteShort(Byte.MAX_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testReadWriteByte(byte value) {
|
|
||||||
byte[] orig = new byte[1];
|
|
||||||
byte[] dest = new byte[1];
|
|
||||||
|
|
||||||
setArray(orig, value, 0, 1);
|
|
||||||
|
|
||||||
int one = Types.readUByte(orig, 0);
|
|
||||||
Types.writeUByte(one, dest, 0);
|
|
||||||
|
|
||||||
assertArrayEquals(orig, dest);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testDate() {
|
|
||||||
Calendar cal = Calendar.getInstance();
|
|
||||||
|
|
||||||
Calendar expected = Calendar.getInstance();
|
|
||||||
expected.set(2008, 1, 2, 3, 4, 5);
|
|
||||||
|
|
||||||
byte[] buf = PwDate.Companion.writeTime(expected.getTime(), cal);
|
|
||||||
Calendar actual = Calendar.getInstance();
|
|
||||||
actual.setTime(PwDate.Companion.readTime(buf, 0, cal));
|
|
||||||
|
|
||||||
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR));
|
|
||||||
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH));
|
|
||||||
assertEquals("Day mismatch: ", 1, actual.get(Calendar.DAY_OF_MONTH));
|
|
||||||
assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY));
|
|
||||||
assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE));
|
|
||||||
assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testUUID() {
|
|
||||||
Random rnd = new Random();
|
|
||||||
byte[] bUUID = new byte[16];
|
|
||||||
rnd.nextBytes(bUUID);
|
|
||||||
|
|
||||||
UUID uuid = Types.bytestoUUID(bUUID);
|
|
||||||
byte[] eUUID = Types.UUIDtoBytes(uuid);
|
|
||||||
|
|
||||||
assertArrayEquals("UUID match failed", bUUID, eUUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testULongMax() throws Exception {
|
|
||||||
byte[] ulongBytes = new byte[8];
|
|
||||||
for (int i = 0; i < ulongBytes.length; i++) {
|
|
||||||
ulongBytes[i] = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
LEDataOutputStream leos = new LEDataOutputStream(bos);
|
|
||||||
leos.writeLong(Types.ULONG_MAX_VALUE);
|
|
||||||
leos.close();
|
|
||||||
|
|
||||||
byte[] uLongMax = bos.toByteArray();
|
|
||||||
|
|
||||||
assertArrayEquals(ulongBytes, uLongMax);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.tests.crypto
|
||||||
|
|
||||||
|
import org.junit.Assert.assertArrayEquals
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.Random
|
||||||
|
|
||||||
|
import junit.framework.TestCase
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.crypto.finalkey.AndroidAESKeyTransformer
|
||||||
|
import com.kunzisoft.keepass.crypto.finalkey.NativeAESKeyTransformer
|
||||||
|
|
||||||
|
class AESKeyTest : TestCase() {
|
||||||
|
private lateinit var mRand: Random
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
override fun setUp() {
|
||||||
|
super.setUp()
|
||||||
|
|
||||||
|
mRand = Random()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun testAES() {
|
||||||
|
// Test both an old and an even number to test my flip variable
|
||||||
|
testAESFinalKey(5)
|
||||||
|
testAESFinalKey(6)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun testAESFinalKey(rounds: Long) {
|
||||||
|
val seed = ByteArray(32)
|
||||||
|
val key = ByteArray(32)
|
||||||
|
val nativeKey: ByteArray?
|
||||||
|
val androidKey: ByteArray?
|
||||||
|
|
||||||
|
mRand.nextBytes(seed)
|
||||||
|
mRand.nextBytes(key)
|
||||||
|
|
||||||
|
val androidAESKey = AndroidAESKeyTransformer()
|
||||||
|
androidKey = androidAESKey.transformMasterKey(seed, key, rounds)
|
||||||
|
|
||||||
|
val nativeAESKey = NativeAESKeyTransformer()
|
||||||
|
nativeKey = nativeAESKey.transformMasterKey(seed, key, rounds)
|
||||||
|
|
||||||
|
assertArrayEquals("Does not match", androidKey, nativeKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.crypto;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.CipherFactory;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
|
||||||
|
|
||||||
public class AESTest extends TestCase {
|
|
||||||
|
|
||||||
private Random mRand = new Random();
|
|
||||||
|
|
||||||
public void testEncrypt() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
|
|
||||||
// Test above below and at the blocksize
|
|
||||||
testFinal(15);
|
|
||||||
testFinal(16);
|
|
||||||
testFinal(17);
|
|
||||||
|
|
||||||
// Test random larger sizes
|
|
||||||
int size = mRand.nextInt(494) + 18;
|
|
||||||
testFinal(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testFinal(int dataSize) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
|
||||||
|
|
||||||
// Generate some input
|
|
||||||
byte[] input = new byte[dataSize];
|
|
||||||
mRand.nextBytes(input);
|
|
||||||
|
|
||||||
// Generate key
|
|
||||||
byte[] keyArray = new byte[32];
|
|
||||||
mRand.nextBytes(keyArray);
|
|
||||||
SecretKeySpec key = new SecretKeySpec(keyArray, "AES");
|
|
||||||
|
|
||||||
// Generate IV
|
|
||||||
byte[] ivArray = new byte[16];
|
|
||||||
mRand.nextBytes(ivArray);
|
|
||||||
IvParameterSpec iv = new IvParameterSpec(ivArray);
|
|
||||||
|
|
||||||
Cipher android = CipherFactory.INSTANCE.getInstance("AES/CBC/PKCS5Padding", true);
|
|
||||||
android.init(Cipher.ENCRYPT_MODE, key, iv);
|
|
||||||
byte[] outAndroid = android.doFinal(input, 0, dataSize);
|
|
||||||
|
|
||||||
Cipher nat = CipherFactory.INSTANCE.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
nat.init(Cipher.ENCRYPT_MODE, key, iv);
|
|
||||||
byte[] outNative = nat.doFinal(input, 0, dataSize);
|
|
||||||
|
|
||||||
assertArrayEquals("Arrays differ on size: " + dataSize, outAndroid, outNative);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.tests.crypto
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.crypto.CipherFactory
|
||||||
|
|
||||||
|
import junit.framework.TestCase
|
||||||
|
|
||||||
|
import java.security.InvalidAlgorithmParameterException
|
||||||
|
import java.security.InvalidKeyException
|
||||||
|
import java.security.NoSuchAlgorithmException
|
||||||
|
import java.util.Random
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.IllegalBlockSizeException
|
||||||
|
import javax.crypto.NoSuchPaddingException
|
||||||
|
import javax.crypto.spec.IvParameterSpec
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
import org.junit.Assert.assertArrayEquals
|
||||||
|
|
||||||
|
class AESTest : TestCase() {
|
||||||
|
|
||||||
|
private val mRand = Random()
|
||||||
|
|
||||||
|
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, IllegalBlockSizeException::class, BadPaddingException::class, InvalidAlgorithmParameterException::class)
|
||||||
|
fun testEncrypt() {
|
||||||
|
// Test above below and at the blocksize
|
||||||
|
testFinal(15)
|
||||||
|
testFinal(16)
|
||||||
|
testFinal(17)
|
||||||
|
|
||||||
|
// Test random larger sizes
|
||||||
|
val size = mRand.nextInt(494) + 18
|
||||||
|
testFinal(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, IllegalBlockSizeException::class, BadPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
|
||||||
|
private fun testFinal(dataSize: Int) {
|
||||||
|
|
||||||
|
// Generate some input
|
||||||
|
val input = ByteArray(dataSize)
|
||||||
|
mRand.nextBytes(input)
|
||||||
|
|
||||||
|
// Generate key
|
||||||
|
val keyArray = ByteArray(32)
|
||||||
|
mRand.nextBytes(keyArray)
|
||||||
|
val key = SecretKeySpec(keyArray, "AES")
|
||||||
|
|
||||||
|
// Generate IV
|
||||||
|
val ivArray = ByteArray(16)
|
||||||
|
mRand.nextBytes(ivArray)
|
||||||
|
val iv = IvParameterSpec(ivArray)
|
||||||
|
|
||||||
|
val android = CipherFactory.getInstance("AES/CBC/PKCS5Padding", true)
|
||||||
|
android.init(Cipher.ENCRYPT_MODE, key, iv)
|
||||||
|
val outAndroid = android.doFinal(input, 0, dataSize)
|
||||||
|
|
||||||
|
val nat = CipherFactory.getInstance("AES/CBC/PKCS5Padding")
|
||||||
|
nat.init(Cipher.ENCRYPT_MODE, key, iv)
|
||||||
|
val outNative = nat.doFinal(input, 0, dataSize)
|
||||||
|
|
||||||
|
assertArrayEquals("Arrays differ on size: $dataSize", outAndroid, outNative)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.crypto;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.CipherOutputStream;
|
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.CipherFactory;
|
|
||||||
import com.kunzisoft.keepass.crypto.engine.AesEngine;
|
|
||||||
import com.kunzisoft.keepass.crypto.engine.CipherEngine;
|
|
||||||
import com.kunzisoft.keepass.stream.BetterCipherInputStream;
|
|
||||||
import com.kunzisoft.keepass.stream.LEDataInputStream;
|
|
||||||
|
|
||||||
public class CipherTest extends TestCase {
|
|
||||||
private Random rand = new Random();
|
|
||||||
|
|
||||||
public void testCipherFactory() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
|
||||||
byte[] key = new byte[32];
|
|
||||||
byte[] iv = new byte[16];
|
|
||||||
|
|
||||||
byte[] plaintext = new byte[1024];
|
|
||||||
|
|
||||||
rand.nextBytes(key);
|
|
||||||
rand.nextBytes(iv);
|
|
||||||
rand.nextBytes(plaintext);
|
|
||||||
|
|
||||||
CipherEngine aes = CipherFactory.INSTANCE.getInstance(AesEngine.CIPHER_UUID);
|
|
||||||
Cipher encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv);
|
|
||||||
Cipher decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv);
|
|
||||||
|
|
||||||
byte[] secrettext = encrypt.doFinal(plaintext);
|
|
||||||
byte[] decrypttext = decrypt.doFinal(secrettext);
|
|
||||||
|
|
||||||
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testCipherStreams() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException {
|
|
||||||
final int MESSAGE_LENGTH = 1024;
|
|
||||||
|
|
||||||
byte[] key = new byte[32];
|
|
||||||
byte[] iv = new byte[16];
|
|
||||||
|
|
||||||
byte[] plaintext = new byte[MESSAGE_LENGTH];
|
|
||||||
|
|
||||||
rand.nextBytes(key);
|
|
||||||
rand.nextBytes(iv);
|
|
||||||
rand.nextBytes(plaintext);
|
|
||||||
|
|
||||||
CipherEngine aes = CipherFactory.INSTANCE.getInstance(AesEngine.CIPHER_UUID);
|
|
||||||
Cipher encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv);
|
|
||||||
Cipher decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv);
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
CipherOutputStream cos = new CipherOutputStream(bos, encrypt);
|
|
||||||
cos.write(plaintext);
|
|
||||||
cos.close();
|
|
||||||
|
|
||||||
byte[] secrettext = bos.toByteArray();
|
|
||||||
|
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(secrettext);
|
|
||||||
BetterCipherInputStream cis = new BetterCipherInputStream(bis, decrypt);
|
|
||||||
LEDataInputStream lis = new LEDataInputStream(cis);
|
|
||||||
|
|
||||||
byte[] decrypttext = lis.readBytes(MESSAGE_LENGTH);
|
|
||||||
|
|
||||||
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.tests.crypto
|
||||||
|
|
||||||
|
import org.junit.Assert.assertArrayEquals
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.security.InvalidAlgorithmParameterException
|
||||||
|
import java.security.InvalidKeyException
|
||||||
|
import java.security.NoSuchAlgorithmException
|
||||||
|
import java.util.Random
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.CipherOutputStream
|
||||||
|
import javax.crypto.IllegalBlockSizeException
|
||||||
|
import javax.crypto.NoSuchPaddingException
|
||||||
|
|
||||||
|
import junit.framework.TestCase
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.crypto.CipherFactory
|
||||||
|
import com.kunzisoft.keepass.crypto.engine.AesEngine
|
||||||
|
import com.kunzisoft.keepass.stream.BetterCipherInputStream
|
||||||
|
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
|
||||||
|
|
||||||
|
class CipherTest : TestCase() {
|
||||||
|
private val rand = Random()
|
||||||
|
|
||||||
|
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidAlgorithmParameterException::class, IllegalBlockSizeException::class, BadPaddingException::class)
|
||||||
|
fun testCipherFactory() {
|
||||||
|
val key = ByteArray(32)
|
||||||
|
val iv = ByteArray(16)
|
||||||
|
|
||||||
|
val plaintext = ByteArray(1024)
|
||||||
|
|
||||||
|
rand.nextBytes(key)
|
||||||
|
rand.nextBytes(iv)
|
||||||
|
rand.nextBytes(plaintext)
|
||||||
|
|
||||||
|
val aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID)
|
||||||
|
val encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv)
|
||||||
|
val decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv)
|
||||||
|
|
||||||
|
val secrettext = encrypt.doFinal(plaintext)
|
||||||
|
val decrypttext = decrypt.doFinal(secrettext)
|
||||||
|
|
||||||
|
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidAlgorithmParameterException::class, IllegalBlockSizeException::class, BadPaddingException::class, IOException::class)
|
||||||
|
fun testCipherStreams() {
|
||||||
|
val MESSAGE_LENGTH = 1024
|
||||||
|
|
||||||
|
val key = ByteArray(32)
|
||||||
|
val iv = ByteArray(16)
|
||||||
|
|
||||||
|
val plaintext = ByteArray(MESSAGE_LENGTH)
|
||||||
|
|
||||||
|
rand.nextBytes(key)
|
||||||
|
rand.nextBytes(iv)
|
||||||
|
rand.nextBytes(plaintext)
|
||||||
|
|
||||||
|
val aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID)
|
||||||
|
val encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv)
|
||||||
|
val decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv)
|
||||||
|
|
||||||
|
val bos = ByteArrayOutputStream()
|
||||||
|
val cos = CipherOutputStream(bos, encrypt)
|
||||||
|
cos.write(plaintext)
|
||||||
|
cos.close()
|
||||||
|
|
||||||
|
val secrettext = bos.toByteArray()
|
||||||
|
|
||||||
|
val bis = ByteArrayInputStream(secrettext)
|
||||||
|
val cis = BetterCipherInputStream(bis, decrypt)
|
||||||
|
val lis = LittleEndianDataInputStream(cis)
|
||||||
|
|
||||||
|
val decrypttext = lis.readBytes(MESSAGE_LENGTH)
|
||||||
|
|
||||||
|
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.crypto;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.finalkey.AndroidFinalKey;
|
|
||||||
import com.kunzisoft.keepass.crypto.finalkey.NativeFinalKey;
|
|
||||||
|
|
||||||
public class FinalKeyTest extends TestCase {
|
|
||||||
private Random mRand;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
mRand = new Random();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testNativeAndroid() throws IOException {
|
|
||||||
// Test both an old and an even number to test my flip variable
|
|
||||||
testNativeFinalKey(5);
|
|
||||||
testNativeFinalKey(6);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testNativeFinalKey(int rounds) throws IOException {
|
|
||||||
byte[] seed = new byte[32];
|
|
||||||
byte[] key = new byte[32];
|
|
||||||
byte[] nativeKey;
|
|
||||||
byte[] androidKey;
|
|
||||||
|
|
||||||
mRand.nextBytes(seed);
|
|
||||||
mRand.nextBytes(key);
|
|
||||||
|
|
||||||
AndroidFinalKey aKey = new AndroidFinalKey();
|
|
||||||
androidKey = aKey.transformMasterKey(seed, key, rounds);
|
|
||||||
|
|
||||||
NativeFinalKey nKey = new NativeFinalKey();
|
|
||||||
nativeKey = nKey.transformMasterKey(seed, key, rounds);
|
|
||||||
|
|
||||||
assertArrayEquals("Does not match", androidKey, nativeKey);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.database;
|
|
||||||
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
import com.kunzisoft.keepass.database.element.GroupVersioned;
|
|
||||||
import com.kunzisoft.keepass.database.element.PwDatabase;
|
|
||||||
import com.kunzisoft.keepass.database.element.PwDatabaseV3;
|
|
||||||
import com.kunzisoft.keepass.database.element.PwEntryV3;
|
|
||||||
|
|
||||||
public class DeleteEntry extends AndroidTestCase {
|
|
||||||
private static final String GROUP1_NAME = "Group1";
|
|
||||||
private static final String ENTRY1_NAME = "Test1";
|
|
||||||
private static final String ENTRY2_NAME = "Test2";
|
|
||||||
private static final String KEYFILE = "";
|
|
||||||
private static final String PASSWORD = "12345";
|
|
||||||
private static final String ASSET = "delete.kdb";
|
|
||||||
private static final String FILENAME = "/sdcard/delete.kdb";
|
|
||||||
|
|
||||||
public void testDelete() {
|
|
||||||
|
|
||||||
/*
|
|
||||||
Database db;
|
|
||||||
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
try {
|
|
||||||
db = TestData.GetDb(ctx, ASSET, PASSWORD, KEYFILE, FILENAME);
|
|
||||||
} catch (Exception e) {
|
|
||||||
assertTrue("Failed to open database: " + e.getMessage(), false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PwDatabaseV3 pm = (PwDatabaseV3) db.getPwDatabase();
|
|
||||||
GroupVersioned group1 = getGroup(pm, GROUP1_NAME);
|
|
||||||
assertNotNull("Could not find group1", group1);
|
|
||||||
|
|
||||||
// Delete the group
|
|
||||||
DeleteGroupRunnable task = new DeleteGroupRunnable(null, db, group1, null, true);
|
|
||||||
task.run();
|
|
||||||
|
|
||||||
// Verify the entries were deleted
|
|
||||||
PwEntryInterface entry1 = getEntry(pm, ENTRY1_NAME);
|
|
||||||
assertNull("Entry 1 was not removed", entry1);
|
|
||||||
|
|
||||||
PwEntryInterface entry2 = getEntry(pm, ENTRY2_NAME);
|
|
||||||
assertNull("Entry 2 was not removed", entry2);
|
|
||||||
|
|
||||||
// Verify the entries were removed from the search index
|
|
||||||
SearchDbHelper dbHelp = new SearchDbHelper(ctx);
|
|
||||||
GroupVersioned results1 = dbHelp.search(db.getPwDatabase(), ENTRY1_NAME, 100);
|
|
||||||
GroupVersioned results2 = dbHelp.search(db.getPwDatabase(), ENTRY2_NAME, 100);
|
|
||||||
|
|
||||||
assertEquals("Entry1 was not removed from the search results", 0, results1.numbersOfChildEntries());
|
|
||||||
assertEquals("Entry2 was not removed from the search results", 0, results2.numbersOfChildEntries());
|
|
||||||
|
|
||||||
// Verify the group was deleted
|
|
||||||
group1 = getGroup(pm, GROUP1_NAME);
|
|
||||||
assertNull("Group 1 was not removed.", group1);
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private PwEntryV3 getEntry(PwDatabaseV3 pm, String name) {
|
|
||||||
/*
|
|
||||||
TODO test
|
|
||||||
List<PwEntryV3> entries = pm.getEntries();
|
|
||||||
for ( int i = 0; i < entries.size(); i++ ) {
|
|
||||||
PwEntryV3 entry = entries.get(i);
|
|
||||||
if ( entry.getTitle().equals(name) ) {
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private GroupVersioned getGroup(PwDatabase pm, String name) {
|
|
||||||
/*
|
|
||||||
List<GroupVersioned> groups = pm.getGroups();
|
|
||||||
for ( int i = 0; i < groups.size(); i++ ) {
|
|
||||||
GroupVersioned group = groups.get(i);
|
|
||||||
if ( group.getTitle().equals(name) ) {
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.database;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.PwDatabaseV4;
|
|
||||||
import com.kunzisoft.keepass.database.element.PwEntryV4;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
public class EntryV4 extends TestCase {
|
|
||||||
|
|
||||||
public void testBackup() {
|
|
||||||
/*
|
|
||||||
PwDatabaseV4 db = new PwDatabaseV4();
|
|
||||||
|
|
||||||
db.setHistoryMaxItems(2);
|
|
||||||
|
|
||||||
PwEntryV4 entry = new PwEntryV4();
|
|
||||||
entry.startToManageFieldReferences(db);
|
|
||||||
entry.setTitle("Title1");
|
|
||||||
entry.setUsername("User1");
|
|
||||||
entry.createBackup(db);
|
|
||||||
|
|
||||||
entry.setTitle("Title2");
|
|
||||||
entry.setUsername("User2");
|
|
||||||
entry.createBackup(db);
|
|
||||||
|
|
||||||
entry.setTitle("Title3");
|
|
||||||
entry.setUsername("User3");
|
|
||||||
entry.createBackup(db);
|
|
||||||
|
|
||||||
PwEntryV4 backup = entry.getHistory().get(0);
|
|
||||||
entry.stopToManageFieldReferences();
|
|
||||||
assertEquals("Title2", backup.getTitle());
|
|
||||||
assertEquals("User2", backup.getUsername());
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.database;
|
|
||||||
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
public class Kdb3 extends AndroidTestCase {
|
|
||||||
|
|
||||||
private void testKeyfile(String dbAsset, String keyAsset, String password) throws Exception {
|
|
||||||
/*
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
File sdcard = Environment.getExternalStorageDirectory();
|
|
||||||
String keyPath = sdcard.getAbsolutePath() + "/key";
|
|
||||||
|
|
||||||
TestUtil.extractKey(ctx, keyAsset, keyPath);
|
|
||||||
|
|
||||||
AssetManager am = ctx.getAssets();
|
|
||||||
InputStream is = am.open(dbAsset, AssetManager.ACCESS_STREAMING);
|
|
||||||
|
|
||||||
ImporterV3 importer = new ImporterV3();
|
|
||||||
importer.openDatabase(is, password, TestUtil.getKeyFileInputStream(ctx, keyPath));
|
|
||||||
|
|
||||||
is.close();
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testXMLKeyFile() throws Exception {
|
|
||||||
testKeyfile("kdb_with_xml_keyfile.kdb", "keyfile.key", "12345");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testBinary64KeyFile() throws Exception {
|
|
||||||
testKeyfile("binary-key.kdb", "binary.key", "12345");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.database;
|
|
||||||
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
public class Kdb3Twofish extends AndroidTestCase {
|
|
||||||
public void testReadTwofish() throws Exception {
|
|
||||||
/*
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
AssetManager am = ctx.getAssets();
|
|
||||||
InputStream is = am.open("twofish.kdb", AssetManager.ACCESS_STREAMING);
|
|
||||||
|
|
||||||
ImporterV3 importer = new ImporterV3();
|
|
||||||
|
|
||||||
PwDatabaseV3 db = importer.openDatabase(is, "12345", null);
|
|
||||||
|
|
||||||
assertTrue(db.getEncryptionAlgorithm() == PwEncryptionAlgorithm.Twofish);
|
|
||||||
|
|
||||||
is.close();
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.database;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.AssetManager;
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.exception.InvalidDBException;
|
|
||||||
import com.kunzisoft.keepass.database.exception.PwDbOutputException;
|
|
||||||
import com.kunzisoft.keepass.tests.TestUtil;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
public class Kdb4 extends AndroidTestCase {
|
|
||||||
|
|
||||||
public void testDetection() throws IOException, InvalidDBException {
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
AssetManager am = ctx.getAssets();
|
|
||||||
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
|
|
||||||
|
|
||||||
/*
|
|
||||||
TODO Test
|
|
||||||
Importer importer = ImporterFactory.createImporter(is);
|
|
||||||
|
|
||||||
assertTrue(importer instanceof ImporterV4);
|
|
||||||
is.close();
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testParsing() throws IOException, InvalidDBException {
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
AssetManager am = ctx.getAssets();
|
|
||||||
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
|
|
||||||
|
|
||||||
/*
|
|
||||||
TODO Test
|
|
||||||
ImporterV4 importer = new ImporterV4();
|
|
||||||
importer.openDatabase(is, "12345", null);
|
|
||||||
|
|
||||||
is.close();
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testSavingKDBXV3() throws IOException, InvalidDBException, PwDbOutputException {
|
|
||||||
testSaving("test.kdbx", "12345", "test-out.kdbx");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testSavingKDBXV4() throws IOException, InvalidDBException, PwDbOutputException {
|
|
||||||
testSaving("test-kdbxv4.kdbx", "1", "test-kdbxv4-out.kdbx");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testSaving(String inputFile, String password, String outputFile) throws IOException, InvalidDBException, PwDbOutputException {
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
AssetManager am = ctx.getAssets();
|
|
||||||
InputStream is = am.open(inputFile, AssetManager.ACCESS_STREAMING);
|
|
||||||
|
|
||||||
/*
|
|
||||||
TODO Test
|
|
||||||
ImporterV4 importer = new ImporterV4();
|
|
||||||
PwDatabaseV4 db = importer.openDatabase(is, password, null);
|
|
||||||
is.close();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
PwDbV4Output output = (PwDbV4Output) PwDbOutput.getInstance(db, bos);
|
|
||||||
output.output();
|
|
||||||
|
|
||||||
byte[] data = bos.toByteArray();
|
|
||||||
|
|
||||||
FileOutputStream fos = new FileOutputStream(TestUtil.getSdPath(outputFile), false);
|
|
||||||
|
|
||||||
InputStream bis = new ByteArrayInputStream(data);
|
|
||||||
bis = new CopyInputStream(bis, fos);
|
|
||||||
importer = new ImporterV4();
|
|
||||||
db = importer.openDatabase(bis, password, null);
|
|
||||||
bis.close();
|
|
||||||
|
|
||||||
fos.close();
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
TestUtil.extractKey(getContext(), "keyfile.key", TestUtil.getSdPath("key"));
|
|
||||||
TestUtil.extractKey(getContext(), "binary.key", TestUtil.getSdPath("key-binary"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testComposite() throws IOException, InvalidDBException {
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
AssetManager am = ctx.getAssets();
|
|
||||||
InputStream is = am.open("keyfile.kdbx", AssetManager.ACCESS_STREAMING);
|
|
||||||
|
|
||||||
/*
|
|
||||||
TODO Test
|
|
||||||
ImporterV4 importer = new ImporterV4();
|
|
||||||
importer.openDatabase(is, "12345", TestUtil.getKeyFileInputStream(ctx, TestUtil.getSdPath("key")));
|
|
||||||
|
|
||||||
is.close();
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testCompositeBinary() throws IOException, InvalidDBException {
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
AssetManager am = ctx.getAssets();
|
|
||||||
InputStream is = am.open("keyfile-binary.kdbx", AssetManager.ACCESS_STREAMING);
|
|
||||||
|
|
||||||
/*
|
|
||||||
TODO Test
|
|
||||||
ImporterV4 importer = new ImporterV4();
|
|
||||||
importer.openDatabase(is, "12345", TestUtil.getKeyFileInputStream(ctx,TestUtil.getSdPath("key-binary")));
|
|
||||||
|
|
||||||
is.close();
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testKeyfile() throws IOException, InvalidDBException {
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
AssetManager am = ctx.getAssets();
|
|
||||||
InputStream is = am.open("key-only.kdbx", AssetManager.ACCESS_STREAMING);
|
|
||||||
/*
|
|
||||||
TODO Test
|
|
||||||
ImporterV4 importer = new ImporterV4();
|
|
||||||
importer.openDatabase(is, "", TestUtil.getKeyFileInputStream(ctx, TestUtil.getSdPath("key")));
|
|
||||||
|
|
||||||
is.close();
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testNoGzip() throws IOException, InvalidDBException {
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
AssetManager am = ctx.getAssets();
|
|
||||||
InputStream is = am.open("no-encrypt.kdbx", AssetManager.ACCESS_STREAMING);
|
|
||||||
/*
|
|
||||||
TODO Test
|
|
||||||
ImporterV4 importer = new ImporterV4();
|
|
||||||
importer.openDatabase(is, "12345", null);
|
|
||||||
|
|
||||||
is.close();
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.database;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.AssetManager;
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
public class Kdb4Header extends AndroidTestCase {
|
|
||||||
public void testReadHeader() throws Exception {
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
AssetManager am = ctx.getAssets();
|
|
||||||
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
|
|
||||||
|
|
||||||
/*
|
|
||||||
TODO Test
|
|
||||||
ImporterV4 importer = new ImporterV4();
|
|
||||||
|
|
||||||
PwDatabaseV4 db = importer.openDatabase(is, "12345", null);
|
|
||||||
|
|
||||||
assertEquals(6000, db.getNumberKeyEncryptionRounds());
|
|
||||||
|
|
||||||
assertTrue(db.getDataCipher().equals(AesEngine.CIPHER_UUID));
|
|
||||||
|
|
||||||
is.close();
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.database;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.AssetManager;
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.PwDatabaseV4;
|
|
||||||
import com.kunzisoft.keepass.database.element.SprEngineV4;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
public class SprEngineTest extends AndroidTestCase {
|
|
||||||
private PwDatabaseV4 db;
|
|
||||||
private SprEngineV4 spr;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
Context ctx = getContext();
|
|
||||||
|
|
||||||
AssetManager am = ctx.getAssets();
|
|
||||||
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
|
|
||||||
|
|
||||||
/*
|
|
||||||
TODO Test
|
|
||||||
ImporterV4 importer = new ImporterV4();
|
|
||||||
db = importer.openDatabase(is, "12345", null);
|
|
||||||
|
|
||||||
is.close();
|
|
||||||
|
|
||||||
spr = new SprEngineV4();
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String REF = "{REF:P@I:2B1D56590D961F48A8CE8C392CE6CD35}";
|
|
||||||
private final String ENCODE_UUID = "IN7RkON49Ui1UZ2ddqmLcw==";
|
|
||||||
private final String RESULT = "Password";
|
|
||||||
public void testRefReplace() {
|
|
||||||
/*
|
|
||||||
TODO TEST
|
|
||||||
UUID entryUUID = decodeUUID(ENCODE_UUID);
|
|
||||||
|
|
||||||
PwEntryV4 entry = (PwEntryV4) db.getEntryById(entryUUID);
|
|
||||||
|
|
||||||
|
|
||||||
assertEquals(RESULT, spr.compile(REF, entry, db));
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.database;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.Database;
|
|
||||||
|
|
||||||
public class TestData {
|
|
||||||
private static final String TEST1_KEYFILE = "";
|
|
||||||
private static final String TEST1_KDB = "test1.kdb";
|
|
||||||
private static final String TEST1_PASSWORD = "12345";
|
|
||||||
|
|
||||||
private static Database mDb1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
public static Database GetDb1(Context ctx) throws Exception {
|
|
||||||
return GetDb1(ctx, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Database GetDb1(Context ctx, boolean forceReload) throws Exception {
|
|
||||||
if ( mDb1 == null || forceReload ) {
|
|
||||||
mDb1 = GetDb(ctx, TEST1_KDB, TEST1_PASSWORD, TEST1_KEYFILE, "/sdcard/test1.kdb");
|
|
||||||
}
|
|
||||||
|
|
||||||
return mDb1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Database GetDb(Context ctx, String asset, String password, String keyfile, String filename) throws Exception {
|
|
||||||
AssetManager am = ctx.getAssets();
|
|
||||||
InputStream is = am.open(asset, AssetManager.ACCESS_STREAMING);
|
|
||||||
|
|
||||||
Database Db = new Database();
|
|
||||||
|
|
||||||
InputStream keyIs = TestUtil.getKeyFileInputStream(ctx, keyfile);
|
|
||||||
|
|
||||||
Db.loadData(ctx, is, password, keyIs, Importer.DEBUG);
|
|
||||||
Uri.Builder b = new Uri.Builder();
|
|
||||||
|
|
||||||
Db.setUri(b.scheme("file").path(filename).build());
|
|
||||||
|
|
||||||
return Db;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PwDatabaseV3Debug GetTest1(Context ctx) throws Exception {
|
|
||||||
if ( mDb1 == null ) {
|
|
||||||
GetDb1(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
//return (PwDatabaseV3Debug) mDb1.getPwDatabase();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.output;
|
|
||||||
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
public class PwManagerOutputTest extends AndroidTestCase {
|
|
||||||
// PwDatabaseV3Debug mPM;
|
|
||||||
|
|
||||||
/*
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
mPM = TestData.GetTest1(getContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPlainContent() throws IOException, PwDbOutputException {
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
PwDbV3Output pos = new PwDbV3OutputDebug(mPM, bos, true);
|
|
||||||
pos.outputPlanGroupAndEntries(bos);
|
|
||||||
|
|
||||||
assertTrue("No output", bos.toByteArray().length > 0);
|
|
||||||
assertArrayEquals("Group and entry output doesn't match.", mPM.getPostHeader(), bos.toByteArray());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testChecksum() throws NoSuchAlgorithmException, IOException, PwDbOutputException {
|
|
||||||
//FileOutputStream fos = new FileOutputStream("/dev/null");
|
|
||||||
NullOutputStream nos = new NullOutputStream();
|
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
|
||||||
|
|
||||||
DigestOutputStream dos = new DigestOutputStream(nos, md);
|
|
||||||
|
|
||||||
PwDbV3Output pos = new PwDbV3OutputDebug(mPM, dos, true);
|
|
||||||
pos.outputPlanGroupAndEntries(dos);
|
|
||||||
dos.close();
|
|
||||||
|
|
||||||
byte[] digest = md.digest();
|
|
||||||
assertTrue("No output", digest.length > 0);
|
|
||||||
assertArrayEquals("Hash of groups and entries failed.", mPM.getDbHeader().contentsHash, digest);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertHeadersEquals(PwDbHeaderV3 expected, PwDbHeaderV3 actual) {
|
|
||||||
assertEquals("Flags unequal", expected.flags, actual.flags);
|
|
||||||
assertEquals("Entries unequal", expected.numEntries, actual.numEntries);
|
|
||||||
assertEquals("Groups unequal", expected.numGroups, actual.numGroups);
|
|
||||||
assertEquals("Key Rounds unequal", expected.numKeyEncRounds, actual.numKeyEncRounds);
|
|
||||||
assertEquals("Signature1 unequal", expected.signature1, actual.signature1);
|
|
||||||
assertEquals("Signature2 unequal", expected.signature2, actual.signature2);
|
|
||||||
assertTrue("Version incompatible", PwDbHeaderV3.compatibleHeaders(expected.version, actual.version));
|
|
||||||
assertArrayEquals("Hash unequal", expected.contentsHash, actual.contentsHash);
|
|
||||||
assertArrayEquals("IV unequal", expected.encryptionIV, actual.encryptionIV);
|
|
||||||
assertArrayEquals("Seed unequal", expected.masterSeed, actual.masterSeed);
|
|
||||||
assertArrayEquals("Seed2 unequal", expected.transformSeed, actual.transformSeed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testHeader() throws PwDbOutputException, IOException {
|
|
||||||
ByteArrayOutputStream bActual = new ByteArrayOutputStream();
|
|
||||||
PwDbV3Output pActual = new PwDbV3OutputDebug(mPM, bActual, true);
|
|
||||||
PwDbHeaderV3 header = pActual.outputHeader(bActual);
|
|
||||||
|
|
||||||
ByteArrayOutputStream bExpected = new ByteArrayOutputStream();
|
|
||||||
PwDbHeaderOutputV3 outExpected = new PwDbHeaderOutputV3(mPM.getDbHeader(), bExpected);
|
|
||||||
outExpected.output();
|
|
||||||
|
|
||||||
assertHeadersEquals(mPM.getDbHeader(), header);
|
|
||||||
assertTrue("No output", bActual.toByteArray().length > 0);
|
|
||||||
assertArrayEquals("Header does not match.", bExpected.toByteArray(), bActual.toByteArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testFinalKey() throws PwDbOutputException {
|
|
||||||
ByteArrayOutputStream bActual = new ByteArrayOutputStream();
|
|
||||||
PwDbV3Output pActual = new PwDbV3OutputDebug(mPM, bActual, true);
|
|
||||||
PwDbHeader hActual = pActual.outputHeader(bActual);
|
|
||||||
byte[] finalKey = pActual.getFinalKey(hActual);
|
|
||||||
|
|
||||||
assertArrayEquals("Keys mismatched", mPM.getFinalKey(), finalKey);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testFullWrite() throws IOException, PwDbOutputException {
|
|
||||||
AssetManager am = getContext().getAssets();
|
|
||||||
InputStream is = am.open("test1.kdb");
|
|
||||||
|
|
||||||
// Pull file into byte array (for streaming fun)
|
|
||||||
ByteArrayOutputStream bExpected = new ByteArrayOutputStream();
|
|
||||||
while (true) {
|
|
||||||
int data = is.read();
|
|
||||||
if ( data == -1 ) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
bExpected.write(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteArrayOutputStream bActual = new ByteArrayOutputStream();
|
|
||||||
PwDbV3Output pActual = new PwDbV3OutputDebug(mPM, bActual, true);
|
|
||||||
pActual.output();
|
|
||||||
//pActual.close();
|
|
||||||
|
|
||||||
FileOutputStream fos = new FileOutputStream(TestUtil.getSdPath("test1_out.kdb"));
|
|
||||||
fos.write(bActual.toByteArray());
|
|
||||||
fos.close();
|
|
||||||
assertArrayEquals("Databases do not match.", bExpected.toByteArray(), bActual.toByteArray());
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.search;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
import com.kunzisoft.keepass.database.element.Database;
|
|
||||||
import com.kunzisoft.keepass.database.element.GroupVersioned;
|
|
||||||
|
|
||||||
public class SearchTest extends AndroidTestCase {
|
|
||||||
|
|
||||||
private Database mDb;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
//mDb = TestData.GetDb1(getContext(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testSearch() {
|
|
||||||
GroupVersioned results = mDb.search("Amazon");
|
|
||||||
//assertTrue("Search result not found.", results.numbersOfChildEntries() > 0);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testBackupIncluded() {
|
|
||||||
updateOmitSetting(false);
|
|
||||||
GroupVersioned results = mDb.search("BackupOnly");
|
|
||||||
|
|
||||||
//assertTrue("Search result not found.", results.numbersOfChildEntries() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testBackupExcluded() {
|
|
||||||
updateOmitSetting(true);
|
|
||||||
GroupVersioned results = mDb.search("BackupOnly");
|
|
||||||
|
|
||||||
//assertFalse("Search result found, but should not have been.", results.numbersOfChildEntries() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateOmitSetting(boolean setting) {
|
|
||||||
Context ctx = getContext();
|
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
|
||||||
SharedPreferences.Editor editor = prefs.edit();
|
|
||||||
|
|
||||||
editor.putBoolean("settings_omitbackup_key", setting);
|
|
||||||
editor.commit();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.stream;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.zip.GZIPInputStream;
|
|
||||||
import java.util.zip.GZIPOutputStream;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.stream.HashedBlockInputStream;
|
|
||||||
import com.kunzisoft.keepass.stream.HashedBlockOutputStream;
|
|
||||||
|
|
||||||
public class HashedBlock extends TestCase {
|
|
||||||
|
|
||||||
private static Random rand = new Random();
|
|
||||||
|
|
||||||
public void testBlockAligned() throws IOException {
|
|
||||||
testSize(1024, 1024);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testOffset() throws IOException {
|
|
||||||
testSize(1500, 1024);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testSize(int blockSize, int bufferSize) throws IOException {
|
|
||||||
byte[] orig = new byte[blockSize];
|
|
||||||
|
|
||||||
rand.nextBytes(orig);
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
HashedBlockOutputStream output = new HashedBlockOutputStream(bos, bufferSize);
|
|
||||||
output.write(orig);
|
|
||||||
output.close();
|
|
||||||
|
|
||||||
byte[] encoded = bos.toByteArray();
|
|
||||||
|
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
|
|
||||||
HashedBlockInputStream input = new HashedBlockInputStream(bis);
|
|
||||||
|
|
||||||
ByteArrayOutputStream decoded = new ByteArrayOutputStream();
|
|
||||||
while ( true ) {
|
|
||||||
byte[] buf = new byte[1024];
|
|
||||||
int read = input.read(buf);
|
|
||||||
if ( read == -1 ) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
decoded.write(buf, 0, read);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] out = decoded.toByteArray();
|
|
||||||
|
|
||||||
assertArrayEquals(orig, out);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testGZIPStream() throws IOException {
|
|
||||||
final int testLength = 32000;
|
|
||||||
|
|
||||||
byte[] orig = new byte[testLength];
|
|
||||||
rand.nextBytes(orig);
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
HashedBlockOutputStream hos = new HashedBlockOutputStream(bos);
|
|
||||||
GZIPOutputStream zos = new GZIPOutputStream(hos);
|
|
||||||
|
|
||||||
zos.write(orig);
|
|
||||||
zos.close();
|
|
||||||
|
|
||||||
byte[] compressed = bos.toByteArray();
|
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(compressed);
|
|
||||||
HashedBlockInputStream his = new HashedBlockInputStream(bis);
|
|
||||||
GZIPInputStream zis = new GZIPInputStream(his);
|
|
||||||
|
|
||||||
byte[] uncompressed = new byte[testLength];
|
|
||||||
|
|
||||||
int read = 0;
|
|
||||||
while (read != -1 && testLength - read > 0) {
|
|
||||||
read += zis.read(uncompressed, read, testLength - read);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
assertArrayEquals("Output not equal to input", orig, uncompressed);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.tests.stream
|
||||||
|
|
||||||
|
import org.junit.Assert.assertArrayEquals
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.Random
|
||||||
|
import java.util.zip.GZIPInputStream
|
||||||
|
import java.util.zip.GZIPOutputStream
|
||||||
|
|
||||||
|
import junit.framework.TestCase
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.stream.HashedBlockInputStream
|
||||||
|
import com.kunzisoft.keepass.stream.HashedBlockOutputStream
|
||||||
|
|
||||||
|
class HashedBlockTest : TestCase() {
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun testBlockAligned() {
|
||||||
|
testSize(1024, 1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun testOffset() {
|
||||||
|
testSize(1500, 1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun testSize(blockSize: Int, bufferSize: Int) {
|
||||||
|
val orig = ByteArray(blockSize)
|
||||||
|
|
||||||
|
rand.nextBytes(orig)
|
||||||
|
|
||||||
|
val bos = ByteArrayOutputStream()
|
||||||
|
val output = HashedBlockOutputStream(bos, bufferSize)
|
||||||
|
output.write(orig)
|
||||||
|
output.close()
|
||||||
|
|
||||||
|
val encoded = bos.toByteArray()
|
||||||
|
|
||||||
|
val bis = ByteArrayInputStream(encoded)
|
||||||
|
val input = HashedBlockInputStream(bis)
|
||||||
|
|
||||||
|
val decoded = ByteArrayOutputStream()
|
||||||
|
while (true) {
|
||||||
|
val buf = ByteArray(1024)
|
||||||
|
val read = input.read(buf)
|
||||||
|
if (read == -1) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded.write(buf, 0, read)
|
||||||
|
}
|
||||||
|
|
||||||
|
val out = decoded.toByteArray()
|
||||||
|
|
||||||
|
assertArrayEquals(orig, out)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun testGZIPStream() {
|
||||||
|
val testLength = 32000
|
||||||
|
|
||||||
|
val orig = ByteArray(testLength)
|
||||||
|
rand.nextBytes(orig)
|
||||||
|
|
||||||
|
val bos = ByteArrayOutputStream()
|
||||||
|
val hos = HashedBlockOutputStream(bos)
|
||||||
|
val zos = GZIPOutputStream(hos)
|
||||||
|
|
||||||
|
zos.write(orig)
|
||||||
|
zos.close()
|
||||||
|
|
||||||
|
val compressed = bos.toByteArray()
|
||||||
|
val bis = ByteArrayInputStream(compressed)
|
||||||
|
val his = HashedBlockInputStream(bis)
|
||||||
|
val zis = GZIPInputStream(his)
|
||||||
|
|
||||||
|
val uncompressed = ByteArray(testLength)
|
||||||
|
|
||||||
|
var read = 0
|
||||||
|
while (read != -1 && testLength - read > 0) {
|
||||||
|
read += zis.read(uncompressed, read, testLength - read)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
assertArrayEquals("Output not equal to input", orig, uncompressed)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val rand = Random()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.utils;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.utils.StringUtil;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
public class StringUtilTest extends TestCase {
|
|
||||||
private final String text = "AbCdEfGhIj";
|
|
||||||
private final String search = "BcDe";
|
|
||||||
private final String badSearch = "Ed";
|
|
||||||
|
|
||||||
public void testIndexOfIgnoreCase1() {
|
|
||||||
assertEquals(1, StringUtil.INSTANCE.indexOfIgnoreCase(text, search, Locale.ENGLISH));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testIndexOfIgnoreCase2() {
|
|
||||||
assertEquals(-1, StringUtil.INSTANCE.indexOfIgnoreCase(text, search, Locale.ENGLISH), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testIndexOfIgnoreCase3() {
|
|
||||||
assertEquals(-1, StringUtil.INSTANCE.indexOfIgnoreCase(text, badSearch, Locale.ENGLISH));
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String repText = "AbCtestingaBc";
|
|
||||||
private final String repSearch = "ABc";
|
|
||||||
private final String repSearchBad = "CCCCCC";
|
|
||||||
private final String repNew = "12345";
|
|
||||||
private final String repResult = "12345testing12345";
|
|
||||||
public void testReplaceAllIgnoresCase1() {
|
|
||||||
assertEquals(repResult, StringUtil.INSTANCE.replaceAllIgnoresCase(repText, repSearch, repNew, Locale.ENGLISH));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReplaceAllIgnoresCase2() {
|
|
||||||
assertEquals(repText, StringUtil.INSTANCE.replaceAllIgnoresCase(repText, repSearchBad, repNew, Locale.ENGLISH));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.kunzisoft.keepass.tests.utils
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
|
import junit.framework.TestCase
|
||||||
|
|
||||||
|
class UnsignedIntTest: TestCase() {
|
||||||
|
|
||||||
|
fun testUInt() {
|
||||||
|
val standardInt = UnsignedInt(15).toInt()
|
||||||
|
assertEquals(15, standardInt)
|
||||||
|
val unsignedInt = UnsignedInt(-1).toLong()
|
||||||
|
assertEquals(4294967295L, unsignedInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testMaxValue() {
|
||||||
|
val maxValue = UnsignedInt.MAX_VALUE.toLong()
|
||||||
|
assertEquals(4294967295L, maxValue)
|
||||||
|
val longValue = UnsignedInt.fromLong(4294967295L).toLong()
|
||||||
|
assertEquals(longValue, maxValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testLong() {
|
||||||
|
val longValue = UnsignedInt.fromLong(50L).toInt()
|
||||||
|
assertEquals(50, longValue)
|
||||||
|
val uIntLongValue = UnsignedInt.fromLong(4294967290).toLong()
|
||||||
|
assertEquals(4294967290, uIntLongValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.tests.utils
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.database.element.DateInstant
|
||||||
|
import com.kunzisoft.keepass.stream.*
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||||
|
import junit.framework.TestCase
|
||||||
|
import org.junit.Assert.assertArrayEquals
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class ValuesTest : TestCase() {
|
||||||
|
|
||||||
|
fun testReadWriteLongZero() {
|
||||||
|
testReadWriteLong(0.toByte())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReadWriteLongMax() {
|
||||||
|
testReadWriteLong(java.lang.Byte.MAX_VALUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReadWriteLongMin() {
|
||||||
|
testReadWriteLong(java.lang.Byte.MIN_VALUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReadWriteLongRnd() {
|
||||||
|
val rnd = Random()
|
||||||
|
val buf = ByteArray(1)
|
||||||
|
rnd.nextBytes(buf)
|
||||||
|
|
||||||
|
testReadWriteLong(buf[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun testReadWriteLong(value: Byte) {
|
||||||
|
val orig = ByteArray(8)
|
||||||
|
setArray(orig, value, 8)
|
||||||
|
|
||||||
|
assertArrayEquals(orig, longTo8Bytes(bytes64ToLong(orig)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReadWriteIntZero() {
|
||||||
|
testReadWriteInt(0.toByte())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReadWriteIntMin() {
|
||||||
|
testReadWriteInt(java.lang.Byte.MIN_VALUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReadWriteIntMax() {
|
||||||
|
testReadWriteInt(java.lang.Byte.MAX_VALUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun testReadWriteInt(value: Byte) {
|
||||||
|
val orig = ByteArray(4)
|
||||||
|
|
||||||
|
for (i in 0..3) {
|
||||||
|
orig[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
setArray(orig, value, 4)
|
||||||
|
|
||||||
|
val one = bytes4ToUInt(orig)
|
||||||
|
val dest = uIntTo4Bytes(one)
|
||||||
|
|
||||||
|
assertArrayEquals(orig, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setArray(buf: ByteArray, value: Byte, size: Int) {
|
||||||
|
for (i in 0 until size) {
|
||||||
|
buf[i] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReadWriteShortOne() {
|
||||||
|
val orig = ByteArray(2)
|
||||||
|
|
||||||
|
orig[0] = 0
|
||||||
|
orig[1] = 1
|
||||||
|
|
||||||
|
val one = bytes2ToUShort(orig)
|
||||||
|
val dest = uShortTo2Bytes(one)
|
||||||
|
|
||||||
|
assertArrayEquals(orig, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReadWriteShortMin() {
|
||||||
|
testReadWriteShort(java.lang.Byte.MIN_VALUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReadWriteShortMax() {
|
||||||
|
testReadWriteShort(java.lang.Byte.MAX_VALUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun testReadWriteShort(value: Byte) {
|
||||||
|
val orig = ByteArray(2)
|
||||||
|
setArray(orig, value, 2)
|
||||||
|
|
||||||
|
val one = bytes2ToUShort(orig)
|
||||||
|
val dest = uShortTo2Bytes(one)
|
||||||
|
|
||||||
|
assertArrayEquals(orig, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReadWriteByteZero() {
|
||||||
|
testReadWriteByte(0.toByte())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReadWriteByteMin() {
|
||||||
|
testReadWriteByte(java.lang.Byte.MIN_VALUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReadWriteByteMax() {
|
||||||
|
testReadWriteShort(java.lang.Byte.MAX_VALUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun testReadWriteByte(value: Byte) {
|
||||||
|
val dest: Byte = UnsignedInt(UnsignedInt.fromByte(value)).toByte()
|
||||||
|
assert(value == dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testDate() {
|
||||||
|
val cal = Calendar.getInstance()
|
||||||
|
|
||||||
|
val expected = Calendar.getInstance()
|
||||||
|
expected.set(2008, 1, 2, 3, 4, 5)
|
||||||
|
|
||||||
|
val actual = Calendar.getInstance()
|
||||||
|
dateTo5Bytes(expected.time, cal)?.let { buf ->
|
||||||
|
actual.time = bytes5ToDate(buf, cal).date
|
||||||
|
}
|
||||||
|
|
||||||
|
val jDate = DateInstant(System.currentTimeMillis())
|
||||||
|
val intermediate = DateInstant(jDate)
|
||||||
|
val cDate = bytes5ToDate(dateTo5Bytes(intermediate.date)!!)
|
||||||
|
|
||||||
|
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR))
|
||||||
|
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH))
|
||||||
|
assertEquals("Day mismatch: ", 2, actual.get(Calendar.DAY_OF_MONTH))
|
||||||
|
assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY))
|
||||||
|
assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE))
|
||||||
|
assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND))
|
||||||
|
assertTrue("jDate and intermediate not equal", jDate == intermediate)
|
||||||
|
assertTrue("jDate $jDate and cDate $cDate not equal", cDate == jDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testUUID() {
|
||||||
|
val bUUID = ByteArray(16)
|
||||||
|
Random().nextBytes(bUUID)
|
||||||
|
|
||||||
|
val uuid = bytes16ToUuid(bUUID)
|
||||||
|
val eUUID = uuidTo16Bytes(uuid)
|
||||||
|
|
||||||
|
val lUUID = bytes16ToUuid(bUUID)
|
||||||
|
val leUUID = uuidTo16Bytes(lUUID)
|
||||||
|
|
||||||
|
assertArrayEquals("UUID match failed", bUUID, eUUID)
|
||||||
|
assertArrayEquals("UUID match failed", bUUID, leUUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun testULongMax() {
|
||||||
|
val ulongBytes = ByteArray(8)
|
||||||
|
for (i in ulongBytes.indices) {
|
||||||
|
ulongBytes[i] = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
val bos = ByteArrayOutputStream()
|
||||||
|
val leos = LittleEndianDataOutputStream(bos)
|
||||||
|
leos.writeLong(UnsignedLong.MAX_VALUE)
|
||||||
|
leos.close()
|
||||||
|
|
||||||
|
val uLongMax = bos.toByteArray()
|
||||||
|
|
||||||
|
assertArrayEquals(ulongBytes, uLongMax)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,9 +8,14 @@
|
|||||||
android:normalScreens="true"
|
android:normalScreens="true"
|
||||||
android:largeScreens="true"
|
android:largeScreens="true"
|
||||||
android:anyDensity="true" />
|
android:anyDensity="true" />
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
<uses-permission
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
<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
|
<application
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
@@ -18,14 +23,15 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:name="com.kunzisoft.keepass.app.App"
|
android:name="com.kunzisoft.keepass.app.App"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="@xml/backup"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
|
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
|
||||||
android:theme="@style/KeepassDXStyle.Night">
|
android:largeHeap="true"
|
||||||
<!-- TODO backup API Key -->
|
android:resizeableActivity="true"
|
||||||
|
android:theme="@style/KeepassDXStyle.Night"
|
||||||
|
tools:targetApi="n">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.backup.api_key"
|
android:name="com.google.android.backup.api_key"
|
||||||
android:value="" />
|
android:value="${googleAndroidBackupAPIKey}" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.kunzisoft.keepass.activities.FileDatabaseSelectActivity"
|
android:name="com.kunzisoft.keepass.activities.FileDatabaseSelectActivity"
|
||||||
android:theme="@style/KeepassDXStyle.SplashScreen"
|
android:theme="@style/KeepassDXStyle.SplashScreen"
|
||||||
@@ -48,7 +54,7 @@
|
|||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="file" />
|
<data android:scheme="file" />
|
||||||
<data android:scheme="content" />
|
<data android:scheme="content" />
|
||||||
<data android:mimeType="application/octet-stream" />
|
<data android:mimeType="*/*" />
|
||||||
<data android:host="*" />
|
<data android:host="*" />
|
||||||
<data android:pathPattern=".*\\.kdb" />
|
<data android:pathPattern=".*\\.kdb" />
|
||||||
<data android:pathPattern=".*\\..*\\.kdb" />
|
<data android:pathPattern=".*\\..*\\.kdb" />
|
||||||
@@ -71,29 +77,28 @@
|
|||||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
|
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
|
||||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
|
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter tools:ignore="AppLinkUrlError">
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
<category android:name="android.intent.category.BROWSABLE"/>
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
<data android:mimeType="application/octet-stream"/>
|
<data android:scheme="file" />
|
||||||
</intent-filter>
|
<data android:scheme="content" />
|
||||||
</activity>
|
<data android:mimeType="application/octet-stream" />
|
||||||
<!-- Folder picker -->
|
<data android:mimeType="application/x-kdb" />
|
||||||
<provider
|
<data android:mimeType="application/x-kdbx" />
|
||||||
android:name="android.support.v4.content.FileProvider"
|
<data android:mimeType="application/x-keepass" />
|
||||||
android:authorities="${applicationId}.provider"
|
<data android:host="*" />
|
||||||
android:exported="false"
|
<data android:pathPattern=".*" />
|
||||||
android:grantUriPermissions="true">
|
<data android:pathPattern=".*\\.*" />
|
||||||
<meta-data
|
<data android:pathPattern=".*\\..*\\.*" />
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
<data android:pathPattern=".*\\..*\\..*\\.*" />
|
||||||
android:resource="@xml/nnf_provider_paths" />
|
<data android:pathPattern=".*\\..*\\..*\\..*\\.*" />
|
||||||
</provider>
|
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.*" />
|
||||||
<activity
|
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||||
android:name=".activities.stylish.FilePickerStylishActivity"
|
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||||
android:label="@string/app_name">
|
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||||
<intent-filter>
|
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||||
<action android:name="android.intent.action.GET_CONTENT" />
|
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Main Activity -->
|
<!-- Main Activity -->
|
||||||
@@ -125,26 +130,35 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="com.kunzisoft.keepass.activities.AboutActivity"
|
android:name="com.kunzisoft.keepass.activities.AboutActivity"
|
||||||
android:launchMode="singleTask"
|
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.settings.SettingsActivity" />
|
||||||
<activity android:name="com.kunzisoft.keepass.autofill.AutofillLauncherActivity"
|
<activity android:name="com.kunzisoft.keepass.autofill.AutofillLauncherActivity"
|
||||||
android:configChanges="keyboardHidden" />
|
android:configChanges="keyboardHidden" />
|
||||||
<activity android:name="com.kunzisoft.keepass.settings.SettingsAutofillActivity" />
|
<activity android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
|
||||||
|
<activity android:name="com.kunzisoft.keepass.settings.AutofillSettingsActivity" />
|
||||||
<activity android:name="com.kunzisoft.keepass.magikeyboard.KeyboardLauncherActivity"
|
<activity android:name="com.kunzisoft.keepass.magikeyboard.KeyboardLauncherActivity"
|
||||||
android:label="@string/keyboard_name">
|
android:label="@string/keyboard_name"
|
||||||
|
android:exported="true">
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name="com.kunzisoft.keepass.settings.MagikIMESettings"
|
<activity android:name="com.kunzisoft.keepass.settings.MagikeyboardSettingsActivity"
|
||||||
android:label="@string/keyboard_setting_label">
|
android:label="@string/keyboard_setting_label">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" />
|
||||||
<service
|
<service
|
||||||
android:name="com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService"
|
android:name="com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
<service
|
||||||
|
android:name=".notifications.AttachmentFileNotificationService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" />
|
||||||
<service
|
<service
|
||||||
android:name="com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService"
|
android:name="com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
v7<EFBFBD><07>gx<67><78><EFBFBD>"<04>Dm<44>]tIWRP<52>g<18>y<15>/˰1<CBB0><31><13>X<0B><>fW[<5B>F%<25><1E>\<5C>up4
|
|
||||||
<EFBFBD><EFBFBD>-t;<3B>z<EFBFBD>
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<KeyFile>
|
|
||||||
<Meta>
|
|
||||||
<Version>1.00</Version>
|
|
||||||
</Meta>
|
|
||||||
<Key>
|
|
||||||
<Data>zaTWphVNtRbspnwkqjy8FGTy5IqCUx9+FNb5H+VdB24=</Data>
|
|
||||||
</Key>
|
|
||||||
</KeyFile>
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,35 +1,35 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities
|
||||||
|
|
||||||
import android.content.pm.PackageManager.NameNotFoundException
|
import android.content.pm.PackageManager.NameNotFoundException
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.widget.Toolbar
|
import android.text.method.LinkMovementMethod
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
import com.kunzisoft.keepass.BuildConfig
|
import com.kunzisoft.keepass.BuildConfig
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||||
|
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
|
|
||||||
class AboutActivity : StylishActivity() {
|
class AboutActivity : StylishActivity() {
|
||||||
@@ -40,7 +40,7 @@ class AboutActivity : StylishActivity() {
|
|||||||
setContentView(R.layout.activity_about)
|
setContentView(R.layout.activity_about)
|
||||||
|
|
||||||
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
||||||
toolbar.title = getString(R.string.menu_about)
|
toolbar.title = getString(R.string.about)
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
@@ -64,9 +64,17 @@ class AboutActivity : StylishActivity() {
|
|||||||
val buildTextView = findViewById<TextView>(R.id.activity_about_build)
|
val buildTextView = findViewById<TextView>(R.id.activity_about_build)
|
||||||
buildTextView.text = 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)
|
findViewById<TextView>(R.id.activity_about_contribution_text).apply {
|
||||||
disclaimerText.text = getString(R.string.disclaimer_formal, DateTime().year)
|
movementMethod = LinkMovementMethod.getInstance()
|
||||||
|
text = HtmlCompat.fromHtml(getString(R.string.html_about_contribution),
|
||||||
|
HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
|||||||
@@ -1,68 +1,93 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.support.design.widget.CollapsingToolbarLayout
|
|
||||||
import android.support.v7.app.AlertDialog
|
|
||||||
import android.support.v7.widget.Toolbar
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import android.widget.ProgressBar
|
||||||
import android.widget.Toast
|
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.R
|
||||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
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.Database
|
||||||
import com.kunzisoft.keepass.database.element.EntryVersioned
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
import com.kunzisoft.keepass.database.element.PwNodeId
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.education.EntryActivityEducation
|
import com.kunzisoft.keepass.education.EntryActivityEducation
|
||||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
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.ClipboardEntryNotificationService
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields
|
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
|
||||||
import com.kunzisoft.keepass.settings.SettingsAutofillActivity
|
|
||||||
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
import com.kunzisoft.keepass.utils.MenuUtil
|
import com.kunzisoft.keepass.utils.MenuUtil
|
||||||
import com.kunzisoft.keepass.utils.Util
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
|
import com.kunzisoft.keepass.utils.createDocument
|
||||||
|
import com.kunzisoft.keepass.utils.onCreateDocumentResult
|
||||||
import com.kunzisoft.keepass.view.EntryContentsView
|
import 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 collapsingToolbarLayout: CollapsingToolbarLayout? = null
|
||||||
private var titleIconView: ImageView? = null
|
private var titleIconView: ImageView? = null
|
||||||
|
private var historyView: View? = null
|
||||||
private var entryContentsView: EntryContentsView? = null
|
private var entryContentsView: EntryContentsView? = null
|
||||||
|
private var entryProgress: ProgressBar? = null
|
||||||
|
private var lockView: View? = null
|
||||||
private var toolbar: Toolbar? = null
|
private var toolbar: Toolbar? = null
|
||||||
|
|
||||||
private var mEntry: EntryVersioned? = null
|
private var mDatabase: Database? = null
|
||||||
|
|
||||||
|
private var mEntry: Entry? = null
|
||||||
|
|
||||||
|
private var mIsHistory: Boolean = false
|
||||||
|
private var mEntryLastVersion: Entry? = null
|
||||||
|
private var mEntryHistoryPosition: Int = -1
|
||||||
|
|
||||||
private var mShowPassword: Boolean = false
|
private var mShowPassword: Boolean = false
|
||||||
|
|
||||||
|
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
||||||
|
private var mAttachmentsToDownload: HashMap<Int, EntryAttachment> = HashMap()
|
||||||
|
|
||||||
private var clipboardHelper: ClipboardHelper? = null
|
private var clipboardHelper: ClipboardHelper? = null
|
||||||
private var firstLaunchOfActivity: Boolean = false
|
private var firstLaunchOfActivity: Boolean = false
|
||||||
|
|
||||||
@@ -78,19 +103,81 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
|
|
||||||
val currentDatabase = Database.getInstance()
|
mDatabase = Database.getInstance()
|
||||||
readOnly = currentDatabase.isReadOnly || readOnly
|
mReadOnly = mDatabase!!.isReadOnly || mReadOnly
|
||||||
|
|
||||||
mShowPassword = !PreferencesUtil.isPasswordMask(this)
|
mShowPassword = !PreferencesUtil.isPasswordMask(this)
|
||||||
|
|
||||||
|
// Retrieve the textColor to tint the icon
|
||||||
|
val taIconColor = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
|
||||||
|
iconColor = taIconColor.getColor(0, Color.BLACK)
|
||||||
|
taIconColor.recycle()
|
||||||
|
|
||||||
|
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
|
||||||
|
// Get views
|
||||||
|
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
|
// Get Entry from UUID
|
||||||
try {
|
try {
|
||||||
val keyEntry: PwNodeId<*> = intent.getParcelableExtra(KEY_ENTRY)
|
val keyEntry: NodeId<UUID>? = intent.getParcelableExtra(KEY_ENTRY)
|
||||||
mEntry = currentDatabase.getEntryById(keyEntry)
|
if (keyEntry != null) {
|
||||||
|
mEntry = mDatabase?.getEntryById(keyEntry)
|
||||||
|
mEntryLastVersion = mEntry
|
||||||
|
}
|
||||||
} catch (e: ClassCastException) {
|
} catch (e: ClassCastException) {
|
||||||
Log.e(TAG, "Unable to retrieve the entry key")
|
Log.e(TAG, "Unable to retrieve the entry key")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, mEntryHistoryPosition)
|
||||||
|
mEntryHistoryPosition = historyPosition
|
||||||
|
if (historyPosition >= 0) {
|
||||||
|
mIsHistory = true
|
||||||
|
mEntry = mEntry?.getHistory()?.get(historyPosition)
|
||||||
|
}
|
||||||
|
|
||||||
if (mEntry == null) {
|
if (mEntry == null) {
|
||||||
Toast.makeText(this, R.string.entry_not_found, Toast.LENGTH_LONG).show()
|
Toast.makeText(this, R.string.entry_not_found, Toast.LENGTH_LONG).show()
|
||||||
finish()
|
finish()
|
||||||
@@ -100,28 +187,6 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
// Update last access time.
|
// Update last access time.
|
||||||
mEntry?.touch(modified = false, touchParents = false)
|
mEntry?.touch(modified = false, touchParents = false)
|
||||||
|
|
||||||
// Retrieve the textColor to tint the icon
|
|
||||||
val taIconColor = theme.obtainStyledAttributes(intArrayOf(R.attr.textColorInverse))
|
|
||||||
iconColor = taIconColor.getColor(0, Color.WHITE)
|
|
||||||
taIconColor.recycle()
|
|
||||||
|
|
||||||
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
|
|
||||||
invalidateOptionsMenu()
|
|
||||||
|
|
||||||
// Get views
|
|
||||||
collapsingToolbarLayout = findViewById(R.id.toolbar_layout)
|
|
||||||
titleIconView = findViewById(R.id.entry_icon)
|
|
||||||
entryContentsView = findViewById(R.id.entry_contents)
|
|
||||||
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
|
|
||||||
|
|
||||||
// Init the clipboard helper
|
|
||||||
clipboardHelper = ClipboardHelper(this)
|
|
||||||
firstLaunchOfActivity = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
|
|
||||||
mEntry?.let { entry ->
|
mEntry?.let { entry ->
|
||||||
// Fill data in resume to update from EntryEditActivity
|
// Fill data in resume to update from EntryEditActivity
|
||||||
fillEntryDataInContentsView(entry)
|
fillEntryDataInContentsView(entry)
|
||||||
@@ -141,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
|
firstLaunchOfActivity = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fillEntryDataInContentsView(entry: EntryVersioned) {
|
override fun onPause() {
|
||||||
|
mAttachmentFileBinderManager?.unregisterProgressTask()
|
||||||
|
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fillEntryDataInContentsView(entry: Entry) {
|
||||||
|
|
||||||
val database = Database.getInstance()
|
val database = Database.getInstance()
|
||||||
database.startManageEntry(entry)
|
database.startManageEntry(entry)
|
||||||
@@ -152,95 +232,164 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
titleIconView?.assignDatabaseIcon(database.drawFactory, entry.icon, iconColor)
|
titleIconView?.assignDatabaseIcon(database.drawFactory, entry.icon, iconColor)
|
||||||
|
|
||||||
// Assign title text
|
// Assign title text
|
||||||
val entryTitle = entry.getVisualTitle()
|
val entryTitle = entry.title
|
||||||
collapsingToolbarLayout?.title = entryTitle
|
collapsingToolbarLayout?.title = entryTitle
|
||||||
toolbar?.title = entryTitle
|
toolbar?.title = entryTitle
|
||||||
|
|
||||||
// Assign basic fields
|
// Assign basic fields
|
||||||
entryContentsView?.assignUserName(entry.username)
|
entryContentsView?.assignUserName(entry.username)
|
||||||
entryContentsView?.assignUserNameCopyListener(View.OnClickListener {
|
entryContentsView?.assignUserNameCopyListener(View.OnClickListener {
|
||||||
|
database.startManageEntry(entry)
|
||||||
clipboardHelper?.timeoutCopyToClipboard(entry.username,
|
clipboardHelper?.timeoutCopyToClipboard(entry.username,
|
||||||
getString(R.string.copy_field,
|
getString(R.string.copy_field,
|
||||||
getString(R.string.entry_user_name)))
|
getString(R.string.entry_user_name)))
|
||||||
|
database.stopManageEntry(entry)
|
||||||
})
|
})
|
||||||
|
|
||||||
val allowCopyPassword = PreferencesUtil.allowCopyPasswordAndProtectedFields(this)
|
val isFirstTimeAskAllowCopyPasswordAndProtectedFields =
|
||||||
entryContentsView?.assignPassword(entry.password, allowCopyPassword)
|
PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)
|
||||||
if (allowCopyPassword) {
|
val allowCopyPasswordAndProtectedFields =
|
||||||
|
PreferencesUtil.allowCopyPasswordAndProtectedFields(this)
|
||||||
|
|
||||||
|
val showWarningClipboardDialogOnClickListener = View.OnClickListener {
|
||||||
|
AlertDialog.Builder(this@EntryActivity)
|
||||||
|
.setMessage(getString(R.string.allow_copy_password_warning) +
|
||||||
|
"\n\n" +
|
||||||
|
getString(R.string.clipboard_warning))
|
||||||
|
.create().apply {
|
||||||
|
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) { dialog, _ ->
|
||||||
|
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true)
|
||||||
|
dialog.dismiss()
|
||||||
|
fillEntryDataInContentsView(entry)
|
||||||
|
}
|
||||||
|
setButton(AlertDialog.BUTTON_NEGATIVE, getText(R.string.disable)) { dialog, _ ->
|
||||||
|
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, false)
|
||||||
|
dialog.dismiss()
|
||||||
|
fillEntryDataInContentsView(entry)
|
||||||
|
}
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entryContentsView?.assignPassword(entry.password, allowCopyPasswordAndProtectedFields)
|
||||||
|
if (allowCopyPasswordAndProtectedFields) {
|
||||||
entryContentsView?.assignPasswordCopyListener(View.OnClickListener {
|
entryContentsView?.assignPasswordCopyListener(View.OnClickListener {
|
||||||
|
database.startManageEntry(entry)
|
||||||
clipboardHelper?.timeoutCopyToClipboard(entry.password,
|
clipboardHelper?.timeoutCopyToClipboard(entry.password,
|
||||||
getString(R.string.copy_field,
|
getString(R.string.copy_field,
|
||||||
getString(R.string.entry_password)))
|
getString(R.string.entry_password)))
|
||||||
|
database.stopManageEntry(entry)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// If dialog not already shown
|
// If dialog not already shown
|
||||||
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)) {
|
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
|
||||||
entryContentsView?.assignPasswordCopyListener(View.OnClickListener {
|
entryContentsView?.assignPasswordCopyListener(showWarningClipboardDialogOnClickListener)
|
||||||
val message = getString(R.string.allow_copy_password_warning) +
|
|
||||||
"\n\n" +
|
|
||||||
getString(R.string.clipboard_warning)
|
|
||||||
val warningDialog = AlertDialog.Builder(this@EntryActivity)
|
|
||||||
.setMessage(message).create()
|
|
||||||
warningDialog.setButton(AlertDialog.BUTTON_POSITIVE, getText(android.R.string.ok)
|
|
||||||
) { dialog, _ ->
|
|
||||||
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true)
|
|
||||||
dialog.dismiss()
|
|
||||||
fillEntryDataInContentsView(entry)
|
|
||||||
}
|
|
||||||
warningDialog.setButton(AlertDialog.BUTTON_NEGATIVE, getText(android.R.string.cancel)
|
|
||||||
) { dialog, _ ->
|
|
||||||
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, false)
|
|
||||||
dialog.dismiss()
|
|
||||||
fillEntryDataInContentsView(entry)
|
|
||||||
}
|
|
||||||
warningDialog.show()
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
entryContentsView?.assignPasswordCopyListener(null)
|
entryContentsView?.assignPasswordCopyListener(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Assign OTP field
|
||||||
|
entryContentsView?.assignOtp(entry.getOtpElement(), entryProgress,
|
||||||
|
View.OnClickListener {
|
||||||
|
entry.getOtpElement()?.let { otpElement ->
|
||||||
|
clipboardHelper?.timeoutCopyToClipboard(
|
||||||
|
otpElement.token,
|
||||||
|
getString(R.string.copy_field, getString(R.string.entry_otp))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
entryContentsView?.assignURL(entry.url)
|
entryContentsView?.assignURL(entry.url)
|
||||||
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
|
|
||||||
entryContentsView?.assignComment(entry.notes)
|
entryContentsView?.assignComment(entry.notes)
|
||||||
|
|
||||||
// Assign custom fields
|
// Assign custom fields
|
||||||
if (entry.allowExtraFields()) {
|
if (entry.allowCustomFields()) {
|
||||||
entryContentsView?.clearExtraFields()
|
entryContentsView?.clearExtraFields()
|
||||||
|
|
||||||
entry.fields.doActionToAllCustomProtectedField { label, value ->
|
for (element in entry.customFields.entries) {
|
||||||
val showAction = !value.isProtected || PreferencesUtil.allowCopyPasswordAndProtectedFields(this@EntryActivity)
|
val label = element.key
|
||||||
entryContentsView?.addExtraField(label, value, showAction, View.OnClickListener {
|
val value = element.value
|
||||||
clipboardHelper?.timeoutCopyToClipboard(
|
|
||||||
value.toString(),
|
val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
|
||||||
getString(R.string.copy_field, label)
|
if (allowCopyProtectedField) {
|
||||||
)
|
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, View.OnClickListener {
|
||||||
})
|
clipboardHelper?.timeoutCopyToClipboard(
|
||||||
|
value.toString(),
|
||||||
|
getString(R.string.copy_field, label)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// If dialog not already shown
|
||||||
|
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
|
||||||
|
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, showWarningClipboardDialogOnClickListener)
|
||||||
|
} else {
|
||||||
|
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
|
||||||
|
|
||||||
|
// Manage attachments
|
||||||
|
val attachments = entry.getAttachments()
|
||||||
|
val showAttachmentsView = attachments.isNotEmpty()
|
||||||
|
entryContentsView?.showAttachments(showAttachmentsView)
|
||||||
|
if (showAttachmentsView) {
|
||||||
|
entryContentsView?.assignAttachments(attachments)
|
||||||
|
entryContentsView?.onAttachmentClick { attachmentItem, _ ->
|
||||||
|
when (attachmentItem.downloadState) {
|
||||||
|
AttachmentState.NULL, AttachmentState.ERROR, AttachmentState.COMPLETE -> {
|
||||||
|
createDocument(this, attachmentItem.name)?.let { requestCode ->
|
||||||
|
mAttachmentsToDownload[requestCode] = attachmentItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// TODO Stop download
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entryContentsView?.refreshAttachments()
|
||||||
|
|
||||||
// Assign dates
|
// Assign dates
|
||||||
entry.creationTime.date?.let {
|
entryContentsView?.assignCreationDate(entry.creationTime)
|
||||||
entryContentsView?.assignCreationDate(it)
|
entryContentsView?.assignModificationDate(entry.lastModificationTime)
|
||||||
}
|
entryContentsView?.assignLastAccessDate(entry.lastAccessTime)
|
||||||
entry.lastModificationTime.date?.let {
|
entryContentsView?.setExpires(entry.isCurrentlyExpires)
|
||||||
entryContentsView?.assignModificationDate(it)
|
if (entry.expires) {
|
||||||
}
|
entryContentsView?.assignExpiresDate(entry.expiryTime)
|
||||||
entry.lastAccessTime.date?.let {
|
|
||||||
entryContentsView?.assignLastAccessDate(it)
|
|
||||||
}
|
|
||||||
val expires = entry.expiryTime.date
|
|
||||||
if (entry.isExpires && expires != null) {
|
|
||||||
entryContentsView?.assignExpiresDate(expires)
|
|
||||||
} else {
|
} else {
|
||||||
entryContentsView?.assignExpiresDate(getString(R.string.never))
|
entryContentsView?.assignExpiresDate(getString(R.string.never))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manage history
|
||||||
|
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
|
||||||
|
if (mIsHistory) {
|
||||||
|
val taColorAccent = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
|
||||||
|
collapsingToolbarLayout?.contentScrim = ColorDrawable(taColorAccent.getColor(0, Color.BLACK))
|
||||||
|
taColorAccent.recycle()
|
||||||
|
}
|
||||||
|
val entryHistory = entry.getHistory()
|
||||||
|
val showHistoryView = entryHistory.isNotEmpty()
|
||||||
|
entryContentsView?.showHistory(showHistoryView)
|
||||||
|
if (showHistoryView) {
|
||||||
|
entryContentsView?.assignHistory(entryHistory)
|
||||||
|
entryContentsView?.onHistoryClick { historyItem, position ->
|
||||||
|
launch(this, historyItem, mReadOnly, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entryContentsView?.refreshHistory()
|
||||||
|
|
||||||
|
// Assign special data
|
||||||
|
entryContentsView?.assignUUID(entry.nodeId.id)
|
||||||
|
|
||||||
database.stopManageEntry(entry)
|
database.stopManageEntry(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE ->
|
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE ->
|
||||||
// Not directly get the entry from intent data but from database
|
// Not directly get the entry from intent data but from database
|
||||||
@@ -248,6 +397,15 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
fillEntryDataInContentsView(it)
|
fillEntryDataInContentsView(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
|
||||||
|
if (createdFileUri != null) {
|
||||||
|
mAttachmentsToDownload[requestCode]?.let { attachmentToDownload ->
|
||||||
|
mAttachmentFileBinderManager
|
||||||
|
?.startDownloadAttachment(createdFileUri, attachmentToDownload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun changeShowPasswordIcon(togglePassword: MenuItem?) {
|
private fun changeShowPasswordIcon(togglePassword: MenuItem?) {
|
||||||
@@ -266,9 +424,12 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
val inflater = menuInflater
|
val inflater = menuInflater
|
||||||
MenuUtil.contributionMenuInflater(inflater, menu)
|
MenuUtil.contributionMenuInflater(inflater, menu)
|
||||||
inflater.inflate(R.menu.entry, menu)
|
inflater.inflate(R.menu.entry, menu)
|
||||||
inflater.inflate(R.menu.database_lock, menu)
|
inflater.inflate(R.menu.database, menu)
|
||||||
|
if (mIsHistory && !mReadOnly) {
|
||||||
if (readOnly) {
|
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
|
menu.findItem(R.id.menu_edit)?.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +464,7 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
|
|
||||||
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
|
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
|
||||||
menu: Menu) {
|
menu: Menu) {
|
||||||
if (entryContentsView?.isUserNamePresent == true
|
val entryCopyEducationPerformed = entryContentsView?.isUserNamePresent == true
|
||||||
&& entryActivityEducation.checkAndPerformedEntryCopyEducation(
|
&& entryActivityEducation.checkAndPerformedEntryCopyEducation(
|
||||||
findViewById(R.id.entry_user_name_action_image),
|
findViewById(R.id.entry_user_name_action_image),
|
||||||
{
|
{
|
||||||
@@ -312,40 +473,40 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
getString(R.string.entry_user_name)))
|
getString(R.string.entry_user_name)))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Launch autofill settings
|
performedNextEducation(entryActivityEducation, menu)
|
||||||
startActivity(Intent(this@EntryActivity, SettingsAutofillActivity::class.java))
|
})
|
||||||
}))
|
|
||||||
else if (toolbar?.findViewById<View>(R.id.menu_edit) != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
|
if (!entryCopyEducationPerformed) {
|
||||||
toolbar!!.findViewById(R.id.menu_edit),
|
// entryEditEducationPerformed
|
||||||
{
|
toolbar?.findViewById<View>(R.id.menu_edit) != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
|
||||||
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
|
toolbar!!.findViewById(R.id.menu_edit),
|
||||||
},
|
{
|
||||||
{
|
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
|
||||||
// Open Keepass doc to create field references
|
},
|
||||||
startActivity(Intent(Intent.ACTION_VIEW,
|
{
|
||||||
Uri.parse(getString(R.string.field_references_url))))
|
performedNextEducation(entryActivityEducation, menu)
|
||||||
}))
|
})
|
||||||
;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.menu_contribute -> return MenuUtil.onContributionItemSelected(this)
|
R.id.menu_contribute -> {
|
||||||
|
MenuUtil.onContributionItemSelected(this)
|
||||||
|
return true
|
||||||
|
}
|
||||||
R.id.menu_toggle_pass -> {
|
R.id.menu_toggle_pass -> {
|
||||||
mShowPassword = !mShowPassword
|
mShowPassword = !mShowPassword
|
||||||
changeShowPasswordIcon(item)
|
changeShowPasswordIcon(item)
|
||||||
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
|
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_edit -> {
|
R.id.menu_edit -> {
|
||||||
mEntry?.let {
|
mEntry?.let {
|
||||||
EntryEditActivity.launch(this@EntryActivity, it)
|
EntryEditActivity.launch(this@EntryActivity, it)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_goto_url -> {
|
R.id.menu_goto_url -> {
|
||||||
var url: String = mEntry?.url ?: ""
|
var url: String = mEntry?.url ?: ""
|
||||||
|
|
||||||
@@ -354,48 +515,56 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
url = "http://$url"
|
url = "http://$url"
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
UriUtil.gotoUrl(this, url)
|
||||||
Util.gotoUrl(this, url)
|
return true
|
||||||
} catch (e: ActivityNotFoundException) {
|
}
|
||||||
Toast.makeText(this, R.string.no_url_handler, Toast.LENGTH_LONG).show()
|
R.id.menu_restore_entry_history -> {
|
||||||
|
mEntryLastVersion?.let { mainEntry ->
|
||||||
|
mProgressDialogThread?.startDatabaseRestoreEntryHistory(
|
||||||
|
mainEntry,
|
||||||
|
mEntryHistoryPosition,
|
||||||
|
!mReadOnly && mAutoSaveEnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
R.id.menu_delete_entry_history -> {
|
||||||
R.id.menu_lock -> {
|
mEntryLastVersion?.let { mainEntry ->
|
||||||
lockAndExit()
|
mProgressDialogThread?.startDatabaseDeleteEntryHistory(
|
||||||
return true
|
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)
|
android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item)
|
return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun finish() {
|
override fun finish() {
|
||||||
// Transit data in previous Activity after an update
|
// Transit data in previous Activity after an update
|
||||||
/*
|
Intent().apply {
|
||||||
TODO Slowdown when add entry as result
|
putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry)
|
||||||
Intent intent = new Intent();
|
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, this)
|
||||||
intent.putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry);
|
}
|
||||||
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
|
|
||||||
*/
|
|
||||||
super.finish()
|
super.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG = EntryActivity::class.java.name
|
private val TAG = EntryActivity::class.java.name
|
||||||
|
|
||||||
const val KEY_ENTRY = "entry"
|
const val KEY_ENTRY = "KEY_ENTRY"
|
||||||
|
const val KEY_ENTRY_HISTORY_POSITION = "KEY_ENTRY_HISTORY_POSITION"
|
||||||
|
|
||||||
fun launch(activity: Activity, pw: EntryVersioned, readOnly: Boolean) {
|
fun launch(activity: Activity, entry: Entry, readOnly: Boolean, historyPosition: Int? = null) {
|
||||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||||
val intent = Intent(activity, EntryActivity::class.java)
|
val intent = Intent(activity, EntryActivity::class.java)
|
||||||
intent.putExtra(KEY_ENTRY, pw.nodeId)
|
intent.putExtra(KEY_ENTRY, entry.nodeId)
|
||||||
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
|
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
|
||||||
|
if (historyPosition != null)
|
||||||
|
intent.putExtra(KEY_ENTRY_HISTORY_POSITION, historyPosition)
|
||||||
activity.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
|
activity.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +1,87 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.DatePickerDialog
|
||||||
|
import android.app.TimePickerDialog
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.support.v7.widget.Toolbar
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
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.R
|
||||||
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
|
import com.kunzisoft.keepass.activities.dialogs.*
|
||||||
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
|
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||||
import com.kunzisoft.keepass.activities.lock.LockingHideActivity
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.action.ProgressDialogSaveDatabaseThread
|
import com.kunzisoft.keepass.database.element.DateInstant
|
||||||
import com.kunzisoft.keepass.database.action.node.ActionNodeValues
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
import com.kunzisoft.keepass.database.action.node.AddEntryRunnable
|
import com.kunzisoft.keepass.database.element.Group
|
||||||
import com.kunzisoft.keepass.database.action.node.AfterActionNodeFinishRunnable
|
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||||
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.database.element.*
|
|
||||||
import com.kunzisoft.keepass.education.EntryEditActivityEducation
|
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.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
import com.kunzisoft.keepass.utils.MenuUtil
|
import com.kunzisoft.keepass.utils.MenuUtil
|
||||||
import com.kunzisoft.keepass.view.EntryEditContentsView
|
import com.kunzisoft.keepass.view.EntryEditContentsView
|
||||||
|
import com.kunzisoft.keepass.view.showActionError
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPickerListener, GeneratePasswordDialogFragment.GeneratePasswordListener {
|
class EntryEditActivity : LockingActivity(),
|
||||||
|
IconPickerDialogFragment.IconPickerListener,
|
||||||
|
GeneratePasswordDialogFragment.GeneratePasswordListener,
|
||||||
|
SetOTPDialogFragment.CreateOtpListener,
|
||||||
|
DatePickerDialog.OnDateSetListener,
|
||||||
|
TimePickerDialog.OnTimeSetListener {
|
||||||
|
|
||||||
private var mDatabase: Database? = null
|
private var mDatabase: Database? = null
|
||||||
|
|
||||||
// Refs of an entry and group in database, are not modifiable
|
// Refs of an entry and group in database, are not modifiable
|
||||||
private var mEntry: EntryVersioned? = null
|
private var mEntry: Entry? = null
|
||||||
private var mParent: GroupVersioned? = null
|
private var mParent: Group? = null
|
||||||
// New or copy of mEntry in the database to be modifiable
|
// 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
|
private var mIsNew: Boolean = false
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
private var scrollView: ScrollView? = null
|
private var coordinatorLayout: CoordinatorLayout? = null
|
||||||
|
private var scrollView: NestedScrollView? = null
|
||||||
private var entryEditContentsView: EntryEditContentsView? = null
|
private var entryEditContentsView: EntryEditContentsView? = null
|
||||||
|
private var entryEditAddToolBar: ActionMenuView? = null
|
||||||
private var saveView: View? = null
|
private var saveView: View? = null
|
||||||
|
private var lockView: View? = null
|
||||||
|
|
||||||
// Education
|
// Education
|
||||||
private var entryEditActivityEducation: EntryEditActivityEducation? = null
|
private var entryEditActivityEducation: EntryEditActivityEducation? = null
|
||||||
@@ -76,19 +96,40 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
|||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
|
|
||||||
|
coordinatorLayout = findViewById(R.id.entry_edit_coordinator_layout)
|
||||||
|
|
||||||
scrollView = findViewById(R.id.entry_edit_scroll)
|
scrollView = findViewById(R.id.entry_edit_scroll)
|
||||||
scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
|
scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
|
||||||
|
|
||||||
entryEditContentsView = findViewById(R.id.entry_edit_contents)
|
entryEditContentsView = findViewById(R.id.entry_edit_contents)
|
||||||
entryEditContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
|
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
|
// Focus view to reinitialize timeout
|
||||||
resetAppTimeoutWhenViewFocusedOrChanged(entryEditContentsView)
|
resetAppTimeoutWhenViewFocusedOrChanged(entryEditContentsView)
|
||||||
|
|
||||||
|
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
|
||||||
|
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
|
||||||
|
|
||||||
// Likely the app has been killed exit the activity
|
// Likely the app has been killed exit the activity
|
||||||
mDatabase = Database.getInstance()
|
mDatabase = Database.getInstance()
|
||||||
|
|
||||||
// Entry is retrieve, it's an entry to update
|
// Entry is retrieve, it's an entry to update
|
||||||
intent.getParcelableExtra<PwNodeId<*>>(KEY_ENTRY)?.let {
|
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let {
|
||||||
mIsNew = false
|
mIsNew = false
|
||||||
// Create an Entry copy to modify from the database entry
|
// Create an Entry copy to modify from the database entry
|
||||||
mEntry = mDatabase?.getEntryById(it)
|
mEntry = mDatabase?.getEntryById(it)
|
||||||
@@ -103,25 +144,27 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the icon after an orientation change
|
// Create the new entry from the current one
|
||||||
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
|
if (savedInstanceState == null
|
||||||
mNewEntry = savedInstanceState.getParcelable(KEY_NEW_ENTRY) as EntryVersioned
|
|| !savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
|
||||||
} else {
|
|
||||||
mEntry?.let { entry ->
|
mEntry?.let { entry ->
|
||||||
// Create a copy to modify
|
// Create a copy to modify
|
||||||
mNewEntry = EntryVersioned(entry).also { newEntry ->
|
mNewEntry = Entry(entry).also { newEntry ->
|
||||||
|
|
||||||
// WARNING Remove the parent to keep memory with parcelable
|
// WARNING Remove the parent to keep memory with parcelable
|
||||||
newEntry.parent = null
|
newEntry.removeParent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parent is retrieve, it's a new entry to create
|
// Parent is retrieve, it's a new entry to create
|
||||||
intent.getParcelableExtra<PwNodeId<*>>(KEY_PARENT)?.let {
|
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let {
|
||||||
mIsNew = true
|
mIsNew = true
|
||||||
mNewEntry = mDatabase?.createEntry()
|
// Create an empty new entry
|
||||||
|
if (savedInstanceState == null
|
||||||
|
|| !savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
|
||||||
|
mNewEntry = mDatabase?.createEntry()
|
||||||
|
}
|
||||||
mParent = mDatabase?.getGroupById(it)
|
mParent = mDatabase?.getGroupById(it)
|
||||||
// Add the default icon
|
// Add the default icon
|
||||||
mDatabase?.drawFactory?.let { iconFactory ->
|
mDatabase?.drawFactory?.let { iconFactory ->
|
||||||
@@ -129,6 +172,12 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve the new entry after an orientation change
|
||||||
|
if (savedInstanceState != null
|
||||||
|
&& savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
|
||||||
|
mNewEntry = savedInstanceState.getParcelable(KEY_NEW_ENTRY)
|
||||||
|
}
|
||||||
|
|
||||||
// Close the activity if entry or parent can't be retrieve
|
// Close the activity if entry or parent can't be retrieve
|
||||||
if (mNewEntry == null || mParent == null) {
|
if (mNewEntry == null || mParent == null) {
|
||||||
finish()
|
finish()
|
||||||
@@ -143,47 +192,73 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
|||||||
// Add listener to the icon
|
// Add listener to the icon
|
||||||
entryEditContentsView?.setOnIconViewClickListener { IconPickerDialogFragment.launch(this@EntryEditActivity) }
|
entryEditContentsView?.setOnIconViewClickListener { IconPickerDialogFragment.launch(this@EntryEditActivity) }
|
||||||
|
|
||||||
// Generate password button
|
// Bottom Bar
|
||||||
entryEditContentsView?.setOnPasswordGeneratorClickListener { openPasswordGenerator() }
|
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
|
// Save button
|
||||||
saveView = findViewById(R.id.entry_edit_save)
|
saveView = findViewById(R.id.entry_edit_validate)
|
||||||
saveView?.setOnClickListener { saveEntry() }
|
saveView?.setOnClickListener { saveEntry() }
|
||||||
|
|
||||||
entryEditContentsView?.allowCustomField(mNewEntry?.allowExtraFields() == true) { addNewCustomField() }
|
|
||||||
|
|
||||||
// Verify the education views
|
// Verify the education views
|
||||||
entryEditActivityEducation = EntryEditActivityEducation(this)
|
entryEditActivityEducation = EntryEditActivityEducation(this)
|
||||||
entryEditActivityEducation?.let {
|
|
||||||
Handler().post { performedNextEducation(it) }
|
// Create progress dialog
|
||||||
|
mProgressDialogThread?.onActionFinish = { actionTask, result ->
|
||||||
|
when (actionTask) {
|
||||||
|
ACTION_DATABASE_CREATE_ENTRY_TASK,
|
||||||
|
ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
|
||||||
|
if (result.isSuccess)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
coordinatorLayout?.showActionError(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
override fun onResume() {
|
||||||
val passwordView = entryEditContentsView?.generatePasswordView
|
super.onResume()
|
||||||
val addNewFieldView = entryEditContentsView?.addNewFieldView
|
|
||||||
|
|
||||||
if (passwordView != null
|
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
|
||||||
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
|
View.VISIBLE
|
||||||
passwordView,
|
} else {
|
||||||
{
|
View.GONE
|
||||||
openPasswordGenerator()
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
performedNextEducation(entryEditActivityEducation)
|
|
||||||
}
|
|
||||||
))
|
|
||||||
else if (mNewEntry != null && mNewEntry!!.allowExtraFields() && !mNewEntry!!.containsCustomFields()
|
|
||||||
&& addNewFieldView != null && addNewFieldView.visibility == View.VISIBLE
|
|
||||||
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
|
|
||||||
addNewFieldView,
|
|
||||||
{
|
|
||||||
addNewCustomField()
|
|
||||||
}))
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun populateViewsWithEntry(newEntry: EntryVersioned) {
|
private fun populateViewsWithEntry(newEntry: Entry) {
|
||||||
// Don't start the field reference manager, we want to see the raw ref
|
// Don't start the field reference manager, we want to see the raw ref
|
||||||
mDatabase?.stopManageEntry(newEntry)
|
mDatabase?.stopManageEntry(newEntry)
|
||||||
|
|
||||||
@@ -193,30 +268,43 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
|||||||
// Set info in view
|
// Set info in view
|
||||||
entryEditContentsView?.apply {
|
entryEditContentsView?.apply {
|
||||||
title = newEntry.title
|
title = newEntry.title
|
||||||
username = newEntry.username
|
username = if (mIsNew && newEntry.username.isEmpty())
|
||||||
|
mDatabase?.defaultUsername ?: ""
|
||||||
|
else
|
||||||
|
newEntry.username
|
||||||
url = newEntry.url
|
url = newEntry.url
|
||||||
password = newEntry.password
|
password = newEntry.password
|
||||||
|
expires = newEntry.expires
|
||||||
|
if (expires)
|
||||||
|
expiresDate = newEntry.expiryTime
|
||||||
notes = newEntry.notes
|
notes = newEntry.notes
|
||||||
newEntry.fields.doActionToAllCustomProtectedField { key, value ->
|
for (entry in newEntry.customFields.entries) {
|
||||||
addNewCustomField(key, value)
|
post {
|
||||||
|
putCustomField(entry.key, entry.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun populateEntryWithViews(newEntry: EntryVersioned) {
|
private fun populateEntryWithViews(newEntry: Entry) {
|
||||||
|
|
||||||
mDatabase?.startManageEntry(newEntry)
|
mDatabase?.startManageEntry(newEntry)
|
||||||
|
|
||||||
newEntry.apply {
|
newEntry.apply {
|
||||||
// Build info from view
|
// Build info from view
|
||||||
entryEditContentsView?.let { entryView ->
|
entryEditContentsView?.let { entryView ->
|
||||||
|
removeAllFields()
|
||||||
title = entryView.title
|
title = entryView.title
|
||||||
username = entryView.username
|
username = entryView.username
|
||||||
url = entryView.url
|
url = entryView.url
|
||||||
password = entryView.password
|
password = entryView.password
|
||||||
notes = entryView.notes
|
expires = entryView.expires
|
||||||
|
if (entryView.expires) {
|
||||||
|
expiryTime = entryView.expiresDate
|
||||||
|
}
|
||||||
|
notes = entryView. notes
|
||||||
entryView.customFields.forEach { customField ->
|
entryView.customFields.forEach { customField ->
|
||||||
addExtraField(customField.name, customField.protectedValue)
|
putExtraField(customField.name, customField.protectedValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,7 +312,7 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
|||||||
mDatabase?.stopManageEntry(newEntry)
|
mDatabase?.stopManageEntry(newEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun temporarilySaveAndShowSelectedIcon(icon: PwIcon) {
|
private fun temporarilySaveAndShowSelectedIcon(icon: IconImage) {
|
||||||
mNewEntry?.icon = icon
|
mNewEntry?.icon = icon
|
||||||
mDatabase?.drawFactory?.let { iconDrawFactory ->
|
mDatabase?.drawFactory?.let { iconDrawFactory ->
|
||||||
entryEditContentsView?.setIcon(iconDrawFactory, icon)
|
entryEditContentsView?.setIcon(iconDrawFactory, icon)
|
||||||
@@ -242,9 +330,14 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
|||||||
* Add a new customized field view and scroll to bottom
|
* Add a new customized field view and scroll to bottom
|
||||||
*/
|
*/
|
||||||
private fun addNewCustomField() {
|
private fun addNewCustomField() {
|
||||||
entryEditContentsView?.addNewCustomField()
|
entryEditContentsView?.addEmptyCustomField()
|
||||||
// Scroll bottom
|
}
|
||||||
scrollView?.post { scrollView?.fullScroll(ScrollView.FOCUS_DOWN) }
|
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -255,47 +348,32 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
|||||||
// Launch a validation and show the error if present
|
// Launch a validation and show the error if present
|
||||||
if (entryEditContentsView?.isValid() == true) {
|
if (entryEditContentsView?.isValid() == true) {
|
||||||
// Clone the entry
|
// Clone the entry
|
||||||
mDatabase?.let { database ->
|
mNewEntry?.let { newEntry ->
|
||||||
mNewEntry?.let { newEntry ->
|
|
||||||
|
|
||||||
// WARNING Add the parent previously deleted
|
// WARNING Add the parent previously deleted
|
||||||
newEntry.parent = mEntry?.parent
|
newEntry.parent = mEntry?.parent
|
||||||
// Build info
|
// Build info
|
||||||
newEntry.lastAccessTime = PwDate()
|
newEntry.lastAccessTime = DateInstant()
|
||||||
newEntry.lastModificationTime = PwDate()
|
newEntry.lastModificationTime = DateInstant()
|
||||||
|
|
||||||
populateEntryWithViews(newEntry)
|
populateEntryWithViews(newEntry)
|
||||||
|
|
||||||
// Open a progress dialog and save entry
|
// Open a progress dialog and save entry
|
||||||
var actionRunnable: ActionRunnable? = null
|
if (mIsNew) {
|
||||||
val afterActionNodeFinishRunnable = object : AfterActionNodeFinishRunnable() {
|
mParent?.let { parent ->
|
||||||
override fun onActionNodeFinish(actionNodeValues: ActionNodeValues) {
|
mProgressDialogThread?.startDatabaseCreateEntry(
|
||||||
if (actionNodeValues.result.isSuccess)
|
newEntry,
|
||||||
finish()
|
parent,
|
||||||
}
|
!mReadOnly && mAutoSaveEnable
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (mIsNew) {
|
} else {
|
||||||
mParent?.let { parent ->
|
mEntry?.let { oldEntry ->
|
||||||
actionRunnable = AddEntryRunnable(this@EntryEditActivity,
|
mProgressDialogThread?.startDatabaseUpdateEntry(
|
||||||
database,
|
oldEntry,
|
||||||
newEntry,
|
newEntry,
|
||||||
parent,
|
!mReadOnly && mAutoSaveEnable
|
||||||
afterActionNodeFinishRunnable,
|
)
|
||||||
!readOnly)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
mEntry?.let { oldEntry ->
|
|
||||||
actionRunnable = UpdateEntryRunnable(this@EntryEditActivity,
|
|
||||||
database,
|
|
||||||
oldEntry,
|
|
||||||
newEntry,
|
|
||||||
afterActionNodeFinishRunnable,
|
|
||||||
!readOnly)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
actionRunnable?.let { runnable ->
|
|
||||||
ProgressDialogSaveDatabaseThread(this@EntryEditActivity) { runnable }.start()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,35 +384,125 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
|||||||
super.onCreateOptionsMenu(menu)
|
super.onCreateOptionsMenu(menu)
|
||||||
|
|
||||||
val inflater = menuInflater
|
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)
|
MenuUtil.contributionMenuInflater(inflater, menu)
|
||||||
|
|
||||||
|
entryEditActivityEducation?.let {
|
||||||
|
Handler().post { performedNextEducation(it) }
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
||||||
|
val passwordGeneratorView: View? = entryEditAddToolBar?.findViewById(R.id.menu_generate_password)
|
||||||
|
val generatePasswordEducationPerformed = passwordGeneratorView != null
|
||||||
|
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
|
||||||
|
passwordGeneratorView,
|
||||||
|
{
|
||||||
|
openPasswordGenerator()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
performedNextEducation(entryEditActivityEducation)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (!generatePasswordEducationPerformed) {
|
||||||
|
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 {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.menu_lock -> {
|
R.id.menu_save_database -> {
|
||||||
lockAndExit()
|
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
|
||||||
|
}
|
||||||
|
R.id.menu_contribute -> {
|
||||||
|
MenuUtil.onContributionItemSelected(this)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
android.R.id.home -> {
|
||||||
R.id.menu_contribute -> return MenuUtil.onContributionItemSelected(this)
|
onBackPressed()
|
||||||
|
}
|
||||||
android.R.id.home -> finish()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item)
|
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) {
|
override fun iconPicked(bundle: Bundle) {
|
||||||
IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon ->
|
IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon ->
|
||||||
temporarilySaveAndShowSelectedIcon(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) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
outState.putParcelable(KEY_NEW_ENTRY, mNewEntry)
|
mNewEntry?.let {
|
||||||
|
populateEntryWithViews(it)
|
||||||
|
outState.putParcelable(KEY_NEW_ENTRY, it)
|
||||||
|
}
|
||||||
|
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
@@ -353,6 +521,15 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
|||||||
// Do nothing here
|
// 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() {
|
override fun finish() {
|
||||||
// Assign entry callback as a result in all case
|
// Assign entry callback as a result in all case
|
||||||
try {
|
try {
|
||||||
@@ -397,7 +574,7 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
|||||||
* @param activity from activity
|
* @param activity from activity
|
||||||
* @param pwEntry Entry to update
|
* @param pwEntry Entry to update
|
||||||
*/
|
*/
|
||||||
fun launch(activity: Activity, pwEntry: EntryVersioned) {
|
fun launch(activity: Activity, pwEntry: Entry) {
|
||||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||||
val intent = Intent(activity, EntryEditActivity::class.java)
|
val intent = Intent(activity, EntryEditActivity::class.java)
|
||||||
intent.putExtra(KEY_ENTRY, pwEntry.nodeId)
|
intent.putExtra(KEY_ENTRY, pwEntry.nodeId)
|
||||||
@@ -411,7 +588,7 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
|||||||
* @param activity from activity
|
* @param activity from activity
|
||||||
* @param pwGroup Group who will contains new entry
|
* @param pwGroup Group who will contains new entry
|
||||||
*/
|
*/
|
||||||
fun launch(activity: Activity, pwGroup: GroupVersioned) {
|
fun launch(activity: Activity, pwGroup: Group) {
|
||||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||||
val intent = Intent(activity, EntryEditActivity::class.java)
|
val intent = Intent(activity, EntryEditActivity::class.java)
|
||||||
intent.putExtra(KEY_PARENT, pwGroup.nodeId)
|
intent.putExtra(KEY_PARENT, pwGroup.nodeId)
|
||||||
|
|||||||
@@ -1,243 +1,206 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities
|
||||||
|
|
||||||
import android.Manifest
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.assist.AssistStructure
|
import android.app.assist.AssistStructure
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.preference.PreferenceManager
|
|
||||||
import android.support.annotation.RequiresApi
|
|
||||||
import android.support.v7.app.AlertDialog
|
|
||||||
import android.support.v7.widget.LinearLayoutManager
|
|
||||||
import android.support.v7.widget.RecyclerView
|
|
||||||
import android.support.v7.widget.Toolbar
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.EditText
|
import androidx.annotation.RequiresApi
|
||||||
import android.widget.TextView
|
import androidx.appcompat.widget.Toolbar
|
||||||
import android.widget.Toast
|
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.R
|
||||||
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
||||||
import com.kunzisoft.keepass.activities.dialogs.CreateFileDialogFragment
|
|
||||||
import com.kunzisoft.keepass.activities.dialogs.FileInformationDialogFragment
|
|
||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||||
import com.kunzisoft.keepass.activities.helpers.KeyFileHelper
|
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
||||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||||
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
|
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
|
||||||
import com.kunzisoft.keepass.database.action.CreateDatabaseRunnable
|
import com.kunzisoft.keepass.autofill.AutofillHelper.KEY_SEARCH_INFO
|
||||||
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
|
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
|
||||||
import com.kunzisoft.keepass.fileselect.DeleteFileHistoryAsyncTask
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
import com.kunzisoft.keepass.fileselect.FileDatabaseModel
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
|
||||||
import com.kunzisoft.keepass.fileselect.OpenFileHistoryAsyncTask
|
|
||||||
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
|
|
||||||
import com.kunzisoft.keepass.magikeyboard.KeyboardHelper
|
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
import com.kunzisoft.keepass.utils.*
|
||||||
import com.kunzisoft.keepass.utils.MenuUtil
|
import com.kunzisoft.keepass.view.asError
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import kotlinx.android.synthetic.main.activity_file_selection.*
|
||||||
import net.cachapa.expandablelayout.ExpandableLayout
|
|
||||||
import permissions.dispatcher.*
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.IOException
|
|
||||||
import java.lang.ref.WeakReference
|
|
||||||
import java.net.URLDecoder
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@RuntimePermissions
|
|
||||||
class FileDatabaseSelectActivity : StylishActivity(),
|
class FileDatabaseSelectActivity : StylishActivity(),
|
||||||
CreateFileDialogFragment.DefinePathDialogListener,
|
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
|
||||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
|
||||||
FileDatabaseHistoryAdapter.FileItemOpenListener,
|
|
||||||
FileDatabaseHistoryAdapter.FileSelectClearListener,
|
|
||||||
FileDatabaseHistoryAdapter.FileInformationShowListener {
|
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
private var fileListContainer: View? = null
|
private var coordinatorLayout: CoordinatorLayout? = null
|
||||||
|
private var fileManagerExplanationButton: View? = null
|
||||||
private var createButtonView: View? = null
|
private var createButtonView: View? = null
|
||||||
private var browseButtonView: View? = null
|
private var openDatabaseButtonView: View? = null
|
||||||
private var openButtonView: View? = null
|
|
||||||
private var fileSelectExpandableButtonView: View? = null
|
|
||||||
private var fileSelectExpandableLayout: ExpandableLayout? = null
|
|
||||||
private var openFileNameView: EditText? = null
|
|
||||||
|
|
||||||
// Adapter to manage database history list
|
// Adapter to manage database history list
|
||||||
private var mAdapterDatabaseHistory: FileDatabaseHistoryAdapter? = null
|
private var mAdapterDatabaseHistory: FileDatabaseHistoryAdapter? = null
|
||||||
|
|
||||||
private var mFileDatabaseHistory: FileDatabaseHistory? = null
|
private var mFileDatabaseHistoryAction: FileDatabaseHistoryAction? = null
|
||||||
|
|
||||||
private var mDatabaseFileUri: Uri? = null
|
private var mDatabaseFileUri: Uri? = null
|
||||||
|
|
||||||
private var mKeyFileHelper: KeyFileHelper? = null
|
private var mOpenFileHelper: OpenFileHelper? = null
|
||||||
|
|
||||||
private var mDefaultPath: String? = null
|
private var mProgressDialogThread: ProgressDialogThread? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
mFileDatabaseHistory = FileDatabaseHistory.getInstance(WeakReference(applicationContext))
|
mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(applicationContext)
|
||||||
|
|
||||||
setContentView(R.layout.activity_file_selection)
|
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)
|
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
||||||
toolbar.title = ""
|
toolbar.title = ""
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
|
|
||||||
openFileNameView = findViewById(R.id.file_filename)
|
fileManagerExplanationButton = findViewById(R.id.file_manager_explanation_button)
|
||||||
|
fileManagerExplanationButton?.setOnClickListener {
|
||||||
// Set the initial value of the filename
|
UriUtil.gotoUrl(this, R.string.file_manager_explanation_url)
|
||||||
mDefaultPath = (Environment.getExternalStorageDirectory().absolutePath
|
|
||||||
+ getString(R.string.database_file_path_default)
|
|
||||||
+ getString(R.string.database_file_name_default)
|
|
||||||
+ getString(R.string.database_file_extension_default))
|
|
||||||
openFileNameView?.setHint(R.string.open_link_database)
|
|
||||||
|
|
||||||
// Button to expand file selection
|
|
||||||
fileSelectExpandableButtonView = findViewById(R.id.file_select_expandable_button)
|
|
||||||
fileSelectExpandableLayout = findViewById(R.id.file_select_expandable)
|
|
||||||
fileSelectExpandableButtonView?.setOnClickListener { _ ->
|
|
||||||
if (fileSelectExpandableLayout?.isExpanded == true)
|
|
||||||
fileSelectExpandableLayout?.collapse()
|
|
||||||
else
|
|
||||||
fileSelectExpandableLayout?.expand()
|
|
||||||
}
|
|
||||||
|
|
||||||
// History list
|
|
||||||
val databaseFileListView = findViewById<RecyclerView>(R.id.file_list)
|
|
||||||
databaseFileListView.layoutManager = LinearLayoutManager(this)
|
|
||||||
|
|
||||||
// Open button
|
|
||||||
openButtonView = findViewById(R.id.open_database)
|
|
||||||
openButtonView?.setOnClickListener { _ ->
|
|
||||||
var fileName = openFileNameView?.text?.toString() ?: ""
|
|
||||||
mDefaultPath?.let {
|
|
||||||
if (fileName.isEmpty())
|
|
||||||
fileName = it
|
|
||||||
}
|
|
||||||
launchPasswordActivityWithPath(fileName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create button
|
// Create button
|
||||||
createButtonView = findViewById(R.id.create_database)
|
createButtonView = findViewById(R.id.create_database_button)
|
||||||
createButtonView?.setOnClickListener { openCreateFileDialogFragmentWithPermissionCheck() }
|
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
|
||||||
|
}
|
||||||
|
|
||||||
mKeyFileHelper = KeyFileHelper(this)
|
createButtonView?.setOnClickListener { createNewFile() }
|
||||||
browseButtonView = findViewById(R.id.browse_button)
|
|
||||||
browseButtonView?.setOnClickListener(mKeyFileHelper!!.getOpenFileOnClickViewListener {
|
|
||||||
Uri.parse("file://" + openFileNameView!!.text.toString())
|
|
||||||
})
|
|
||||||
|
|
||||||
|
mOpenFileHelper = OpenFileHelper(this)
|
||||||
|
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)
|
||||||
|
fileDatabaseHistoryRecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
|
||||||
|
// Removes blinks
|
||||||
|
(fileDatabaseHistoryRecyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||||
// Construct adapter with listeners
|
// Construct adapter with listeners
|
||||||
mAdapterDatabaseHistory = FileDatabaseHistoryAdapter(this@FileDatabaseSelectActivity,
|
mAdapterDatabaseHistory = FileDatabaseHistoryAdapter(this)
|
||||||
mFileDatabaseHistory?.databaseUriList ?: ArrayList())
|
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryOpenListener { fileDatabaseHistoryEntityToOpen ->
|
||||||
mAdapterDatabaseHistory?.setOnItemClickListener(this)
|
UriUtil.parse(fileDatabaseHistoryEntityToOpen.databaseUri)?.let { databaseFileUri ->
|
||||||
mAdapterDatabaseHistory?.setFileSelectClearListener(this)
|
launchPasswordActivity(
|
||||||
mAdapterDatabaseHistory?.setFileInformationShowListener(this)
|
databaseFileUri,
|
||||||
databaseFileListView.adapter = mAdapterDatabaseHistory
|
UriUtil.parse(fileDatabaseHistoryEntityToOpen.keyFileUri))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryDeleteListener { fileDatabaseHistoryToDelete ->
|
||||||
|
// Remove from app database
|
||||||
|
mFileDatabaseHistoryAction?.deleteFileDatabaseHistory(fileDatabaseHistoryToDelete) { fileHistoryDeleted ->
|
||||||
|
// Remove from adapter
|
||||||
|
fileHistoryDeleted?.let { databaseFileHistoryDeleted ->
|
||||||
|
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileHistoryDeleted)
|
||||||
|
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
mAdapterDatabaseHistory?.setOnSaveAliasListener { fileDatabaseHistoryWithNewAlias ->
|
||||||
|
mFileDatabaseHistoryAction?.addOrUpdateFileDatabaseHistory(fileDatabaseHistoryWithNewAlias)
|
||||||
|
}
|
||||||
|
fileDatabaseHistoryRecyclerView.adapter = mAdapterDatabaseHistory
|
||||||
|
|
||||||
// Load default database if not an orientation change
|
// Load default database if not an orientation change
|
||||||
if (!(savedInstanceState != null
|
if (!(savedInstanceState != null
|
||||||
&& savedInstanceState.containsKey(EXTRA_STAY)
|
&& savedInstanceState.containsKey(EXTRA_STAY)
|
||||||
&& savedInstanceState.getBoolean(EXTRA_STAY, false))) {
|
&& savedInstanceState.getBoolean(EXTRA_STAY, false))) {
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
val databasePath = PreferencesUtil.getDefaultDatabasePath(this)
|
||||||
val fileName = prefs.getString(PasswordActivity.KEY_DEFAULT_FILENAME, "")
|
|
||||||
|
|
||||||
if (fileName != null && fileName.isNotEmpty()) {
|
UriUtil.parse(databasePath)?.let { databaseFileUri ->
|
||||||
val dbUri = UriUtil.parseUriFile(fileName)
|
launchPasswordActivityWithPath(databaseFileUri)
|
||||||
var scheme: String? = null
|
} ?: run {
|
||||||
if (dbUri != null)
|
Log.i(TAG, "Unable to launch Password Activity")
|
||||||
scheme = dbUri.scheme
|
|
||||||
|
|
||||||
if (scheme != null && scheme.isNotEmpty() && scheme.equals("file", ignoreCase = true)) {
|
|
||||||
val path = dbUri!!.path
|
|
||||||
val db = File(path!!)
|
|
||||||
|
|
||||||
if (db.exists()) {
|
|
||||||
launchPasswordActivityWithPath(path)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (dbUri != null)
|
|
||||||
launchPasswordActivityWithPath(dbUri.toString())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Handler().post { performedNextEducation(FileDatabaseSelectActivityEducation(this)) }
|
// Retrieve the database URI provided by file manager after an orientation change
|
||||||
|
if (savedInstanceState != null
|
||||||
|
&& savedInstanceState.containsKey(EXTRA_DATABASE_URI)) {
|
||||||
|
mDatabaseFileUri = savedInstanceState.getParcelable(EXTRA_DATABASE_URI)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach the dialog thread to this activity
|
||||||
|
mProgressDialogThread = ProgressDialogThread(this).apply {
|
||||||
|
onActionFinish = { actionTask, _ ->
|
||||||
|
when (actionTask) {
|
||||||
|
ACTION_DATABASE_CREATE_TASK -> {
|
||||||
|
GroupActivity.launch(this@FileDatabaseSelectActivity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) {
|
/**
|
||||||
// If no recent files
|
* Create a new file by calling the content provider
|
||||||
if (createButtonView != null
|
*/
|
||||||
&& mFileDatabaseHistory != null
|
@SuppressLint("InlinedApi")
|
||||||
&& !mFileDatabaseHistory!!.hasRecentFiles() && fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
|
private fun createNewFile() {
|
||||||
createButtonView!!,
|
createDocument(this, getString(R.string.database_file_name_default) +
|
||||||
{
|
getString(R.string.database_file_extension_default), "application/x-keepass")
|
||||||
openCreateFileDialogFragmentWithPermissionCheck()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// But if the user cancel, it can also select a database
|
|
||||||
performedNextEducation(fileDatabaseSelectActivityEducation)
|
|
||||||
}))
|
|
||||||
else if (browseButtonView != null
|
|
||||||
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
|
|
||||||
browseButtonView!!,
|
|
||||||
{tapTargetView ->
|
|
||||||
tapTargetView?.let {
|
|
||||||
mKeyFileHelper?.openFileOnClickViewListener?.onClick(it)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fileSelectExpandableButtonView?.let {
|
|
||||||
fileDatabaseSelectActivityEducation
|
|
||||||
.checkAndPerformedOpenLinkDatabaseEducation(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
))
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fileNoFoundAction(e: FileNotFoundException) {
|
private fun fileNoFoundAction(e: FileNotFoundException) {
|
||||||
val error = getString(R.string.file_not_found_content)
|
val error = getString(R.string.file_not_found_content)
|
||||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
coordinatorLayout?.let {
|
||||||
error, Toast.LENGTH_LONG).show()
|
Snackbar.make(it, error, Snackbar.LENGTH_LONG).asError().show()
|
||||||
|
}
|
||||||
Log.e(TAG, error, e)
|
Log.e(TAG, error, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchPasswordActivity(fileName: String, keyFile: String) {
|
private fun launchPasswordActivity(databaseUri: Uri, keyFile: Uri?) {
|
||||||
EntrySelectionHelper.doEntrySelectionAction(intent,
|
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
PasswordActivity.launch(this@FileDatabaseSelectActivity,
|
PasswordActivity.launch(this@FileDatabaseSelectActivity,
|
||||||
fileName, keyFile)
|
databaseUri, keyFile)
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
fileNoFoundAction(e)
|
fileNoFoundAction(e)
|
||||||
}
|
}
|
||||||
@@ -245,7 +208,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
PasswordActivity.launchForKeyboardResult(this@FileDatabaseSelectActivity,
|
PasswordActivity.launchForKeyboardResult(this@FileDatabaseSelectActivity,
|
||||||
fileName, keyFile)
|
databaseUri, keyFile)
|
||||||
finish()
|
finish()
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
fileNoFoundAction(e)
|
fileNoFoundAction(e)
|
||||||
@@ -255,8 +218,9 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
try {
|
try {
|
||||||
PasswordActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
|
PasswordActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
|
||||||
fileName, keyFile,
|
databaseUri, keyFile,
|
||||||
assistStructure)
|
assistStructure,
|
||||||
|
intent.getParcelableExtra(KEY_SEARCH_INFO))
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
fileNoFoundAction(e)
|
fileNoFoundAction(e)
|
||||||
}
|
}
|
||||||
@@ -265,139 +229,82 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchPasswordActivityWithPath(path: String) {
|
private fun launchGroupActivity(readOnly: Boolean) {
|
||||||
launchPasswordActivity(path, "")
|
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||||
|
{
|
||||||
|
GroupActivity.launch(this@FileDatabaseSelectActivity,
|
||||||
|
readOnly)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
GroupActivity.launchForKeyboardSelection(this@FileDatabaseSelectActivity,
|
||||||
|
readOnly)
|
||||||
|
// Do not keep history
|
||||||
|
finish()
|
||||||
|
},
|
||||||
|
{ assistStructure ->
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
GroupActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
|
||||||
|
assistStructure,
|
||||||
|
intent.getParcelableExtra(KEY_SEARCH_INFO),
|
||||||
|
readOnly)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchPasswordActivityWithPath(databaseUri: Uri) {
|
||||||
|
launchPasswordActivity(databaseUri, null)
|
||||||
// Delete flickering for kitkat <=
|
// Delete flickering for kitkat <=
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
||||||
overridePendingTransition(0, 0)
|
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() {
|
override fun onResume() {
|
||||||
|
val database = Database.getInstance()
|
||||||
|
if (database.loaded) {
|
||||||
|
launchGroupActivity(database.isReadOnly)
|
||||||
|
}
|
||||||
|
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
updateExternalStorageWarning()
|
// Construct adapter with listeners
|
||||||
updateFileListVisibility()
|
if (PreferencesUtil.showRecentFiles(this)) {
|
||||||
mAdapterDatabaseHistory!!.notifyDataSetChanged()
|
mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList ->
|
||||||
|
databaseFileHistoryList?.let { historyList ->
|
||||||
|
val hideBrokenLocations = PreferencesUtil.hideBrokenLocations(this@FileDatabaseSelectActivity)
|
||||||
|
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(
|
||||||
|
// Show only uri accessible
|
||||||
|
historyList.filter {
|
||||||
|
if (hideBrokenLocations) {
|
||||||
|
FileDatabaseInfo(this@FileDatabaseSelectActivity,
|
||||||
|
it.databaseUri).exists
|
||||||
|
} else
|
||||||
|
true
|
||||||
|
})
|
||||||
|
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
|
||||||
|
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register progress task
|
||||||
|
mProgressDialogThread?.registerProgressTask()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
// Unregister progress task
|
||||||
|
mProgressDialogThread?.unregisterProgressTask()
|
||||||
|
|
||||||
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
// only to keep the current activity
|
// only to keep the current activity
|
||||||
outState.putBoolean(EXTRA_STAY, true)
|
outState.putBoolean(EXTRA_STAY, true)
|
||||||
}
|
// to retrieve the URI of a created database after an orientation change
|
||||||
|
outState.putParcelable(EXTRA_DATABASE_URI, mDatabaseFileUri)
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
||||||
// NOTE: delegate the permission handling to generated method
|
|
||||||
onRequestPermissionsResult(requestCode, grantResults)
|
|
||||||
}
|
|
||||||
|
|
||||||
@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
fun openCreateFileDialogFragment() {
|
|
||||||
val createFileDialogFragment = CreateFileDialogFragment()
|
|
||||||
createFileDialogFragment.show(supportFragmentManager, "createFileDialogFragment")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateFileListVisibility() {
|
|
||||||
if (mAdapterDatabaseHistory?.itemCount == 0)
|
|
||||||
fileListContainer?.visibility = View.INVISIBLE
|
|
||||||
else
|
|
||||||
fileListContainer?.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create file for database
|
|
||||||
* @return If not created, return false
|
|
||||||
*/
|
|
||||||
private fun createDatabaseFile(path: Uri): Boolean {
|
|
||||||
|
|
||||||
val pathString = URLDecoder.decode(path.path, "UTF-8")
|
|
||||||
// Make sure file name exists
|
|
||||||
if (pathString.isEmpty()) {
|
|
||||||
Log.e(TAG, getString(R.string.error_filename_required))
|
|
||||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
|
||||||
R.string.error_filename_required,
|
|
||||||
Toast.LENGTH_LONG).show()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to create the file
|
|
||||||
val file = File(pathString)
|
|
||||||
try {
|
|
||||||
if (file.exists()) {
|
|
||||||
Log.e(TAG, getString(R.string.error_database_exists) + " " + file)
|
|
||||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
|
||||||
R.string.error_database_exists,
|
|
||||||
Toast.LENGTH_LONG).show()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
val parent = file.parentFile
|
|
||||||
|
|
||||||
if (parent == null || parent.exists() && !parent.isDirectory) {
|
|
||||||
Log.e(TAG, getString(R.string.error_invalid_path) + " " + file)
|
|
||||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
|
||||||
R.string.error_invalid_path,
|
|
||||||
Toast.LENGTH_LONG).show()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!parent.exists()) {
|
|
||||||
// Create parent directory
|
|
||||||
if (!parent.mkdirs()) {
|
|
||||||
Log.e(TAG, getString(R.string.error_could_not_create_parent) + " " + parent)
|
|
||||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
|
||||||
R.string.error_could_not_create_parent,
|
|
||||||
Toast.LENGTH_LONG).show()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return file.createNewFile()
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.e(TAG, getString(R.string.error_could_not_create_parent) + " " + e.localizedMessage)
|
|
||||||
e.printStackTrace()
|
|
||||||
Toast.makeText(
|
|
||||||
this@FileDatabaseSelectActivity,
|
|
||||||
getText(R.string.error_file_not_create).toString() + " "
|
|
||||||
+ e.localizedMessage,
|
|
||||||
Toast.LENGTH_LONG).show()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDefinePathDialogPositiveClick(pathFile: Uri?): Boolean {
|
|
||||||
mDatabaseFileUri = pathFile
|
|
||||||
if (pathFile == null)
|
|
||||||
return false
|
|
||||||
return if (createDatabaseFile(pathFile)) {
|
|
||||||
AssignMasterKeyDialogFragment().show(supportFragmentManager, "passwordDialog")
|
|
||||||
true
|
|
||||||
} else
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDefinePathDialogNegativeClick(pathFile: Uri?): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAssignKeyDialogPositiveClick(
|
override fun onAssignKeyDialogPositiveClick(
|
||||||
@@ -405,52 +312,21 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
keyFileChecked: Boolean, keyFile: Uri?) {
|
keyFileChecked: Boolean, keyFile: Uri?) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
UriUtil.parseUriFile(mDatabaseFileUri)?.let { databaseUri ->
|
mDatabaseFileUri?.let { databaseUri ->
|
||||||
|
|
||||||
// Create the new database
|
// Create the new database
|
||||||
ProgressDialogThread(this@FileDatabaseSelectActivity,
|
mProgressDialogThread?.startDatabaseCreate(
|
||||||
{
|
databaseUri,
|
||||||
CreateDatabaseRunnable(this@FileDatabaseSelectActivity,
|
masterPasswordChecked,
|
||||||
databaseUri,
|
masterPassword,
|
||||||
Database.getInstance(),
|
keyFileChecked,
|
||||||
masterPasswordChecked,
|
keyFile
|
||||||
masterPassword,
|
)
|
||||||
keyFileChecked,
|
|
||||||
keyFile,
|
|
||||||
true, // TODO get readonly
|
|
||||||
LaunchGroupActivityFinish(databaseUri)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
R.string.progress_create)
|
|
||||||
.start()
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val error = "Unable to create database with this password and key file"
|
val error = getString(R.string.error_create_database_file)
|
||||||
Toast.makeText(this, error, Toast.LENGTH_LONG).show()
|
Snackbar.make(activity_file_selection_coordinator_layout, error, Snackbar.LENGTH_LONG).asError().show()
|
||||||
Log.e(TAG, error + " " + e.message)
|
Log.e(TAG, error, e)
|
||||||
// TODO remove
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class LaunchGroupActivityFinish internal constructor(private val fileURI: Uri) : ActionRunnable() {
|
|
||||||
|
|
||||||
override fun run() {
|
|
||||||
finishRun(true, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFinishRun(result: Result) {
|
|
||||||
runOnUiThread {
|
|
||||||
if (result.isSuccess) {
|
|
||||||
// Add database to recent files
|
|
||||||
mFileDatabaseHistory?.addDatabaseUri(fileURI)
|
|
||||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
|
||||||
updateFileListVisibility()
|
|
||||||
GroupActivity.launch(this@FileDatabaseSelectActivity)
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Unable to open the database")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,29 +336,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFileItemOpenListener(itemPosition: Int) {
|
|
||||||
OpenFileHistoryAsyncTask({ fileName, keyFile ->
|
|
||||||
if (fileName != null && keyFile != null)
|
|
||||||
launchPasswordActivity(fileName, keyFile)
|
|
||||||
updateFileListVisibility()
|
|
||||||
}, mFileDatabaseHistory).execute(itemPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClickFileInformation(fileDatabaseModel: FileDatabaseModel) {
|
|
||||||
FileInformationDialogFragment.newInstance(fileDatabaseModel).show(supportFragmentManager, "fileInformation")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFileSelectClearListener(fileDatabaseModel: FileDatabaseModel): Boolean {
|
|
||||||
DeleteFileHistoryAsyncTask({
|
|
||||||
fileDatabaseModel.fileUri?.let {
|
|
||||||
mFileDatabaseHistory?.deleteDatabaseUri(it)
|
|
||||||
}
|
|
||||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
|
||||||
updateFileListVisibility()
|
|
||||||
}, mFileDatabaseHistory, mAdapterDatabaseHistory).execute(fileDatabaseModel)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
@@ -490,44 +343,67 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
|
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
mKeyFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
||||||
) { uri ->
|
) { uri ->
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
if (PreferencesUtil.autoOpenSelectedFile(this@FileDatabaseSelectActivity)) {
|
launchPasswordActivityWithPath(uri)
|
||||||
launchPasswordActivityWithPath(uri.toString())
|
|
||||||
} else {
|
|
||||||
fileSelectExpandableLayout?.expand(false)
|
|
||||||
openFileNameView?.setText(uri.toString())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
// Retrieve the created URI from the file manager
|
||||||
internal fun showRationaleForExternalStorage(request: PermissionRequest) {
|
onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
|
||||||
AlertDialog.Builder(this)
|
mDatabaseFileUri = databaseFileCreatedUri
|
||||||
.setMessage(R.string.permission_external_storage_rationale_write_database)
|
if (mDatabaseFileUri != null) {
|
||||||
.setPositiveButton(R.string.allow) { _, _ -> request.proceed() }
|
AssignMasterKeyDialogFragment.getInstance(true)
|
||||||
.setNegativeButton(R.string.cancel) { _, _ -> request.cancel() }
|
.show(supportFragmentManager, "passwordDialog")
|
||||||
.show()
|
} else {
|
||||||
}
|
val error = getString(R.string.error_create_database)
|
||||||
|
coordinatorLayout?.let {
|
||||||
@OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
Snackbar.make(it, error, Snackbar.LENGTH_LONG).asError().show()
|
||||||
internal fun showDeniedForExternalStorage() {
|
}
|
||||||
Toast.makeText(this, R.string.permission_external_storage_denied, Toast.LENGTH_SHORT).show()
|
Log.e(TAG, error)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
internal fun showNeverAskForExternalStorage() {
|
|
||||||
Toast.makeText(this, R.string.permission_external_storage_never_ask, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
super.onCreateOptionsMenu(menu)
|
super.onCreateOptionsMenu(menu)
|
||||||
MenuUtil.defaultMenuInflater(menuInflater, menu)
|
MenuUtil.defaultMenuInflater(menuInflater, menu)
|
||||||
|
|
||||||
|
Handler().post { performedNextEducation(FileDatabaseSelectActivityEducation(this)) }
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) {
|
||||||
|
// If no recent files
|
||||||
|
val createDatabaseEducationPerformed = createButtonView != null && createButtonView!!.visibility == View.VISIBLE
|
||||||
|
&& mAdapterDatabaseHistory != null
|
||||||
|
&& mAdapterDatabaseHistory!!.itemCount > 0
|
||||||
|
&& fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
|
||||||
|
createButtonView!!,
|
||||||
|
{
|
||||||
|
createNewFile()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// But if the user cancel, it can also select a database
|
||||||
|
performedNextEducation(fileDatabaseSelectActivityEducation)
|
||||||
|
})
|
||||||
|
if (!createDatabaseEducationPerformed) {
|
||||||
|
// selectDatabaseEducationPerformed
|
||||||
|
openDatabaseButtonView != null
|
||||||
|
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
|
||||||
|
openDatabaseButtonView!!,
|
||||||
|
{tapTargetView ->
|
||||||
|
tapTargetView?.let {
|
||||||
|
mOpenFileHelper?.openFileOnClickViewListener?.onClick(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item)
|
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
@@ -536,6 +412,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
|
|
||||||
private const val TAG = "FileDbSelectActivity"
|
private const val TAG = "FileDbSelectActivity"
|
||||||
private const val EXTRA_STAY = "EXTRA_STAY"
|
private const val EXTRA_STAY = "EXTRA_STAY"
|
||||||
|
private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* -------------------------
|
* -------------------------
|
||||||
@@ -550,7 +427,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
fun launchForKeyboardSelection(activity: Activity) {
|
fun launchForKeyboardSelection(activity: Activity) {
|
||||||
KeyboardHelper.startActivityForKeyboardSelection(activity, Intent(activity, FileDatabaseSelectActivity::class.java))
|
EntrySelectionHelper.startActivityForEntrySelection(activity, Intent(activity, FileDatabaseSelectActivity::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -560,10 +437,13 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
fun launchForAutofillResult(activity: Activity, assistStructure: AssistStructure) {
|
fun launchForAutofillResult(activity: Activity,
|
||||||
|
assistStructure: AssistStructure,
|
||||||
|
searchInfo: SearchInfo?) {
|
||||||
AutofillHelper.startActivityForAutofillResult(activity,
|
AutofillHelper.startActivityForAutofillResult(activity,
|
||||||
Intent(activity, FileDatabaseSelectActivity::class.java),
|
Intent(activity, FileDatabaseSelectActivity::class.java),
|
||||||
assistStructure)
|
assistStructure,
|
||||||
|
searchInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package com.kunzisoft.keepass.activities
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.preference.PreferenceManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import android.support.v7.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.support.v7.widget.RecyclerView
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
@@ -14,35 +31,42 @@ import android.view.MenuInflater
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.view.ActionMode
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.adapters.NodeAdapter
|
import com.kunzisoft.keepass.adapters.NodeAdapter
|
||||||
import com.kunzisoft.keepass.database.SortNodeEnum
|
import com.kunzisoft.keepass.database.element.SortNodeEnum
|
||||||
import com.kunzisoft.keepass.database.element.GroupVersioned
|
import com.kunzisoft.keepass.database.element.Group
|
||||||
import com.kunzisoft.keepass.database.element.NodeVersioned
|
import com.kunzisoft.keepass.database.element.node.Node
|
||||||
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
|
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
|
||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.activities.stylish.StylishFragment
|
import com.kunzisoft.keepass.activities.stylish.StylishFragment
|
||||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
|
import com.kunzisoft.keepass.database.element.node.Type
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener {
|
class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener {
|
||||||
|
|
||||||
private var nodeClickCallback: NodeAdapter.NodeClickCallback? = null
|
private var nodeClickListener: NodeClickListener? = null
|
||||||
private var nodeMenuListener: NodeAdapter.NodeMenuListener? = null
|
|
||||||
private var onScrollListener: OnScrollListener? = null
|
private var onScrollListener: OnScrollListener? = null
|
||||||
|
|
||||||
private var listView: RecyclerView? = null
|
private var mNodesRecyclerView: RecyclerView? = null
|
||||||
var mainGroup: GroupVersioned? = null
|
var mainGroup: Group? = null
|
||||||
private set
|
private set
|
||||||
private var mAdapter: NodeAdapter? = null
|
private var mAdapter: NodeAdapter? = null
|
||||||
|
|
||||||
|
var nodeActionSelectionMode = false
|
||||||
|
private set
|
||||||
|
var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED
|
||||||
|
private set
|
||||||
|
private val listActionNodes = LinkedList<Node>()
|
||||||
|
private val listPasteNodes = LinkedList<Node>()
|
||||||
|
|
||||||
private var notFoundView: View? = null
|
private var notFoundView: View? = null
|
||||||
private var isASearchResult: Boolean = false
|
private var isASearchResult: Boolean = false
|
||||||
|
|
||||||
// Preferences for sorting
|
|
||||||
private var prefs: SharedPreferences? = null
|
|
||||||
|
|
||||||
private var readOnly: Boolean = false
|
private var readOnly: Boolean = false
|
||||||
get() {
|
get() {
|
||||||
@@ -53,31 +77,22 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
val isEmpty: Boolean
|
val isEmpty: Boolean
|
||||||
get() = mAdapter == null || mAdapter?.itemCount?:0 <= 0
|
get() = mAdapter == null || mAdapter?.itemCount?:0 <= 0
|
||||||
|
|
||||||
override fun onAttach(context: Context?) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
try {
|
try {
|
||||||
nodeClickCallback = context as NodeAdapter.NodeClickCallback?
|
nodeClickListener = context as NodeClickListener
|
||||||
} catch (e: ClassCastException) {
|
} catch (e: ClassCastException) {
|
||||||
// The activity doesn't implement the interface, throw exception
|
// The activity doesn't implement the interface, throw exception
|
||||||
throw ClassCastException(context?.toString()
|
throw ClassCastException(context.toString()
|
||||||
+ " must implement " + NodeAdapter.NodeClickCallback::class.java.name)
|
+ " must implement " + NodeAdapter.NodeClickCallback::class.java.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
nodeMenuListener = context as NodeAdapter.NodeMenuListener?
|
onScrollListener = context as OnScrollListener
|
||||||
} catch (e: ClassCastException) {
|
|
||||||
nodeMenuListener = null
|
|
||||||
// Context menu can be omit
|
|
||||||
Log.w(TAG, context?.toString()
|
|
||||||
+ " must implement " + NodeAdapter.NodeMenuListener::class.java.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
onScrollListener = context as OnScrollListener?
|
|
||||||
} catch (e: ClassCastException) {
|
} catch (e: ClassCastException) {
|
||||||
onScrollListener = null
|
onScrollListener = null
|
||||||
// Context menu can be omit
|
// Context menu can be omit
|
||||||
Log.w(TAG, context?.toString()
|
Log.w(TAG, context.toString()
|
||||||
+ " must implement " + RecyclerView.OnScrollListener::class.java.name)
|
+ " must implement " + RecyclerView.OnScrollListener::class.java.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,32 +100,56 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
activity?.let { currentActivity ->
|
setHasOptionsMenu(true)
|
||||||
setHasOptionsMenu(true)
|
|
||||||
|
|
||||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments)
|
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments)
|
||||||
|
|
||||||
arguments?.let { args ->
|
arguments?.let { args ->
|
||||||
// Contains all the group in element
|
// Contains all the group in element
|
||||||
if (args.containsKey(GROUP_KEY)) {
|
if (args.containsKey(GROUP_KEY)) {
|
||||||
mainGroup = args.getParcelable(GROUP_KEY)
|
mainGroup = args.getParcelable(GROUP_KEY)
|
||||||
}
|
|
||||||
if (args.containsKey(IS_SEARCH)) {
|
|
||||||
isASearchResult = args.getBoolean(IS_SEARCH)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (args.containsKey(IS_SEARCH)) {
|
||||||
contextThemed?.let { context ->
|
isASearchResult = args.getBoolean(IS_SEARCH)
|
||||||
mAdapter = NodeAdapter(context, currentActivity.menuInflater)
|
}
|
||||||
mAdapter?.apply {
|
}
|
||||||
setReadOnly(readOnly)
|
|
||||||
setIsASearchResult(isASearchResult)
|
contextThemed?.let { context ->
|
||||||
setOnNodeClickListener(nodeClickCallback)
|
mAdapter = NodeAdapter(context)
|
||||||
setActivateContextMenu(true)
|
mAdapter?.apply {
|
||||||
setNodeMenuListener(nodeMenuListener)
|
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
|
||||||
}
|
override fun onNodeClick(node: Node) {
|
||||||
|
if (nodeActionSelectionMode) {
|
||||||
|
if (listActionNodes.contains(node)) {
|
||||||
|
// Remove selected item if already selected
|
||||||
|
listActionNodes.remove(node)
|
||||||
|
} else {
|
||||||
|
// Add selected item if not already selected
|
||||||
|
listActionNodes.add(node)
|
||||||
|
}
|
||||||
|
nodeClickListener?.onNodeSelected(listActionNodes)
|
||||||
|
setActionNodes(listActionNodes)
|
||||||
|
notifyNodeChanged(node)
|
||||||
|
} else {
|
||||||
|
nodeClickListener?.onNodeClick(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNodeLongClick(node: Node): Boolean {
|
||||||
|
if (nodeActionPasteMode == PasteMode.UNDEFINED) {
|
||||||
|
// Select the first item after a long click
|
||||||
|
if (!listActionNodes.contains(node))
|
||||||
|
listActionNodes.add(node)
|
||||||
|
|
||||||
|
nodeClickListener?.onNodeSelected(listActionNodes)
|
||||||
|
|
||||||
|
setActionNodes(listActionNodes)
|
||||||
|
notifyNodeChanged(node)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,20 +164,24 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
// To apply theme
|
// To apply theme
|
||||||
val rootView = inflater.cloneInContext(contextThemed)
|
val rootView = inflater.cloneInContext(contextThemed)
|
||||||
.inflate(R.layout.fragment_list_nodes, container, false)
|
.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)
|
notFoundView = rootView.findViewById(R.id.not_found_container)
|
||||||
|
|
||||||
|
mNodesRecyclerView?.apply {
|
||||||
|
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
|
||||||
|
layoutManager = LinearLayoutManager(context)
|
||||||
|
adapter = mAdapter
|
||||||
|
}
|
||||||
|
|
||||||
onScrollListener?.let { onScrollListener ->
|
onScrollListener?.let { onScrollListener ->
|
||||||
listView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
mNodesRecyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
onScrollListener.onScrolled(dy)
|
onScrollListener.onScrolled(dy)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuildList()
|
|
||||||
|
|
||||||
return rootView
|
return rootView
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,20 +191,16 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
activity?.intent?.let {
|
activity?.intent?.let {
|
||||||
selectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(it)
|
selectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(it)
|
||||||
}
|
}
|
||||||
// Force read only mode if selection mode
|
|
||||||
mAdapter?.apply {
|
|
||||||
setReadOnly(readOnly)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh data
|
// Refresh data
|
||||||
mAdapter?.notifyDataSetChanged()
|
rebuildList()
|
||||||
|
|
||||||
if (isASearchResult && mAdapter!= null && mAdapter!!.isEmpty) {
|
if (isASearchResult && mAdapter!= null && mAdapter!!.isEmpty) {
|
||||||
// To show the " no search entry found "
|
// To show the " no search entry found "
|
||||||
listView?.visibility = View.GONE
|
mNodesRecyclerView?.visibility = View.GONE
|
||||||
notFoundView?.visibility = View.VISIBLE
|
notFoundView?.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
listView?.visibility = View.VISIBLE
|
mNodesRecyclerView?.visibility = View.VISIBLE
|
||||||
notFoundView?.visibility = View.GONE
|
notFoundView?.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,46 +208,42 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
fun rebuildList() {
|
fun rebuildList() {
|
||||||
// Add elements to the list
|
// Add elements to the list
|
||||||
mainGroup?.let { mainGroup ->
|
mainGroup?.let { mainGroup ->
|
||||||
mAdapter?.rebuildList(mainGroup)
|
mAdapter?.apply {
|
||||||
}
|
rebuildList(mainGroup)
|
||||||
listView?.apply {
|
// To visually change the elements
|
||||||
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
|
if (PreferencesUtil.APPEARANCE_CHANGED) {
|
||||||
layoutManager = LinearLayoutManager(context)
|
notifyDataSetChanged()
|
||||||
adapter = mAdapter
|
PreferencesUtil.APPEARANCE_CHANGED = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSortSelected(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) {
|
override fun onSortSelected(sortNodeEnum: SortNodeEnum,
|
||||||
// Toggle setting
|
sortNodeParameters: SortNodeEnum.SortNodeParameters) {
|
||||||
prefs?.edit()?.apply {
|
// Save setting
|
||||||
putString(getString(R.string.sort_node_key), sortNodeEnum.name)
|
context?.let {
|
||||||
putBoolean(getString(R.string.sort_ascending_key), ascending)
|
PreferencesUtil.saveNodeSort(it, sortNodeEnum, sortNodeParameters)
|
||||||
putBoolean(getString(R.string.sort_group_before_key), groupsBefore)
|
|
||||||
putBoolean(getString(R.string.sort_recycle_bin_bottom_key), recycleBinBottom)
|
|
||||||
apply()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell the adapter to refresh it's list
|
// Tell the adapter to refresh it's list
|
||||||
mAdapter?.notifyChangeSort(sortNodeEnum, ascending, groupsBefore)
|
mAdapter?.notifyChangeSort(sortNodeEnum, sortNodeParameters)
|
||||||
mainGroup?.let { mainGroup ->
|
rebuildList()
|
||||||
mAdapter?.rebuildList(mainGroup)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
inflater?.inflate(R.menu.tree, menu)
|
inflater.inflate(R.menu.tree, menu)
|
||||||
|
|
||||||
super.onCreateOptionsMenu(menu, inflater)
|
super.onCreateOptionsMenu(menu, inflater)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item?.itemId) {
|
when (item.itemId) {
|
||||||
|
|
||||||
R.id.menu_sort -> {
|
R.id.menu_sort -> {
|
||||||
context?.let { context ->
|
context?.let { context ->
|
||||||
val sortDialogFragment: SortDialogFragment =
|
val sortDialogFragment: SortDialogFragment =
|
||||||
if (Database.getInstance().isRecycleBinAvailable
|
if (Database.getInstance().isRecycleBinEnabled) {
|
||||||
&& Database.getInstance().isRecycleBinEnabled) {
|
|
||||||
SortDialogFragment.getInstance(
|
SortDialogFragment.getInstance(
|
||||||
PreferencesUtil.getListSort(context),
|
PreferencesUtil.getListSort(context),
|
||||||
PreferencesUtil.getAscendingSort(context),
|
PreferencesUtil.getAscendingSort(context),
|
||||||
@@ -230,6 +265,103 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun actionNodesCallback(nodes: List<Node>,
|
||||||
|
menuListener: NodesActionMenuListener?) : ActionMode.Callback {
|
||||||
|
|
||||||
|
return object : ActionMode.Callback {
|
||||||
|
|
||||||
|
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
|
nodeActionSelectionMode = false
|
||||||
|
nodeActionPasteMode = PasteMode.UNDEFINED
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
|
menu?.clear()
|
||||||
|
|
||||||
|
if (nodeActionPasteMode != PasteMode.UNDEFINED) {
|
||||||
|
mode?.menuInflater?.inflate(R.menu.node_paste_menu, menu)
|
||||||
|
} else {
|
||||||
|
nodeActionSelectionMode = true
|
||||||
|
mode?.menuInflater?.inflate(R.menu.node_menu, menu)
|
||||||
|
|
||||||
|
val database = Database.getInstance()
|
||||||
|
|
||||||
|
// Open and Edit for a single item
|
||||||
|
if (nodes.size == 1) {
|
||||||
|
// Edition
|
||||||
|
if (readOnly
|
||||||
|
|| (database.isRecycleBinEnabled && nodes[0] == database.recycleBin)) {
|
||||||
|
menu?.removeItem(R.id.menu_edit)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
menu?.removeItem(R.id.menu_open)
|
||||||
|
menu?.removeItem(R.id.menu_edit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy and Move (not for groups)
|
||||||
|
if (readOnly
|
||||||
|
|| isASearchResult
|
||||||
|
|| nodes.any { it.type == Type.GROUP }) {
|
||||||
|
// TODO Copy For Group
|
||||||
|
menu?.removeItem(R.id.menu_copy)
|
||||||
|
menu?.removeItem(R.id.menu_move)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deletion
|
||||||
|
if (readOnly
|
||||||
|
|| (database.isRecycleBinEnabled && nodes.any { it == database.recycleBin })) {
|
||||||
|
menu?.removeItem(R.id.menu_delete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the number of items selected in title
|
||||||
|
mode?.title = nodes.size.toString()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||||
|
if (menuListener == null)
|
||||||
|
return false
|
||||||
|
return when (item?.itemId) {
|
||||||
|
R.id.menu_open -> menuListener.onOpenMenuClick(nodes[0])
|
||||||
|
R.id.menu_edit -> menuListener.onEditMenuClick(nodes[0])
|
||||||
|
R.id.menu_copy -> {
|
||||||
|
nodeActionPasteMode = PasteMode.PASTE_FROM_COPY
|
||||||
|
mAdapter?.unselectActionNodes()
|
||||||
|
val returnValue = menuListener.onCopyMenuClick(nodes)
|
||||||
|
nodeActionSelectionMode = false
|
||||||
|
returnValue
|
||||||
|
}
|
||||||
|
R.id.menu_move -> {
|
||||||
|
nodeActionPasteMode = PasteMode.PASTE_FROM_MOVE
|
||||||
|
mAdapter?.unselectActionNodes()
|
||||||
|
val returnValue = menuListener.onMoveMenuClick(nodes)
|
||||||
|
nodeActionSelectionMode = false
|
||||||
|
returnValue
|
||||||
|
}
|
||||||
|
R.id.menu_delete -> menuListener.onDeleteMenuClick(nodes)
|
||||||
|
R.id.menu_paste -> {
|
||||||
|
val returnValue = menuListener.onPasteMenuClick(nodeActionPasteMode, nodes)
|
||||||
|
nodeActionPasteMode = PasteMode.UNDEFINED
|
||||||
|
nodeActionSelectionMode = false
|
||||||
|
returnValue
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
|
listActionNodes.clear()
|
||||||
|
listPasteNodes.clear()
|
||||||
|
mAdapter?.unselectActionNodes()
|
||||||
|
nodeActionPasteMode = PasteMode.UNDEFINED
|
||||||
|
nodeActionSelectionMode = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
@@ -237,33 +369,77 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
|
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
|
||||||
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE
|
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE
|
||||||
|| resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
|| resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
||||||
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)
|
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
|
||||||
mAdapter?.addNode(newNode)
|
addNode(changedNode)
|
||||||
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE)
|
||||||
//mAdapter.updateLastNodeRegister(newNode);
|
mAdapter?.notifyDataSetChanged()
|
||||||
mainGroup?.let { mainGroup ->
|
|
||||||
mAdapter?.rebuildList(mainGroup)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ?: Log.e(this.javaClass.name, "New node can be retrieve in Activity Result")
|
} ?: Log.e(this.javaClass.name, "New node can be retrieve in Activity Result")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addNode(newNode: NodeVersioned) {
|
fun contains(node: Node): Boolean {
|
||||||
|
return mAdapter?.contains(node) ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addNode(newNode: Node) {
|
||||||
mAdapter?.addNode(newNode)
|
mAdapter?.addNode(newNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned) {
|
fun addNodes(newNodes: List<Node>) {
|
||||||
mAdapter?.updateNode(oldNode, newNode)
|
mAdapter?.addNodes(newNodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeNode(pwNode: NodeVersioned) {
|
fun updateNode(oldNode: Node, newNode: Node? = null) {
|
||||||
|
mAdapter?.updateNode(oldNode, newNode ?: oldNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
|
||||||
|
mAdapter?.updateNodes(oldNodes, newNodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeNode(pwNode: Node) {
|
||||||
mAdapter?.removeNode(pwNode)
|
mAdapter?.removeNode(pwNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeNodes(nodes: List<Node>) {
|
||||||
|
mAdapter?.removeNodes(nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeNodeAt(position: Int) {
|
||||||
|
mAdapter?.removeNodeAt(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeNodesAt(positions: IntArray) {
|
||||||
|
mAdapter?.removeNodesAt(positions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback listener to redefine to do an action when a node is click
|
||||||
|
*/
|
||||||
|
interface NodeClickListener {
|
||||||
|
fun onNodeClick(node: Node)
|
||||||
|
fun onNodeSelected(nodes: List<Node>): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Menu listener to redefine to do an action in menu
|
||||||
|
*/
|
||||||
|
interface NodesActionMenuListener {
|
||||||
|
fun onOpenMenuClick(node: Node): Boolean
|
||||||
|
fun onEditMenuClick(node: Node): Boolean
|
||||||
|
fun onCopyMenuClick(nodes: List<Node>): Boolean
|
||||||
|
fun onMoveMenuClick(nodes: List<Node>): Boolean
|
||||||
|
fun onDeleteMenuClick(nodes: List<Node>): Boolean
|
||||||
|
fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<Node>): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class PasteMode {
|
||||||
|
UNDEFINED, PASTE_FROM_COPY, PASTE_FROM_MOVE
|
||||||
|
}
|
||||||
|
|
||||||
interface OnScrollListener {
|
interface OnScrollListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -282,7 +458,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
private const val GROUP_KEY = "GROUP_KEY"
|
private const val GROUP_KEY = "GROUP_KEY"
|
||||||
private const val IS_SEARCH = "IS_SEARCH"
|
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()
|
val bundle = Bundle()
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
bundle.putParcelable(GROUP_KEY, group)
|
bundle.putParcelable(GROUP_KEY, group)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
@@ -25,17 +25,17 @@ import android.content.DialogInterface
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.DialogFragment
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import android.support.v7.app.AlertDialog
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.CompoundButton
|
import android.widget.CompoundButton
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.helpers.KeyFileHelper
|
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
||||||
|
|
||||||
class AssignMasterKeyDialogFragment : DialogFragment() {
|
class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||||
|
|
||||||
@@ -43,15 +43,30 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
private var mKeyFile: Uri? = null
|
private var mKeyFile: Uri? = null
|
||||||
|
|
||||||
private var rootView: View? = null
|
private var rootView: View? = null
|
||||||
|
|
||||||
private var passwordCheckBox: CompoundButton? = null
|
private var passwordCheckBox: CompoundButton? = null
|
||||||
private var passView: TextView? = null
|
|
||||||
private var passConfView: TextView? = null
|
private var passwordTextInputLayout: TextInputLayout? = null
|
||||||
|
private var passwordView: TextView? = null
|
||||||
|
private var passwordRepeatTextInputLayout: TextInputLayout? = null
|
||||||
|
private var passwordRepeatView: TextView? = null
|
||||||
|
|
||||||
private var keyFileCheckBox: CompoundButton? = null
|
private var keyFileCheckBox: CompoundButton? = null
|
||||||
private var keyFileView: TextView? = null
|
private var keyFileSelectionView: KeyFileSelectionView? = null
|
||||||
|
|
||||||
private var mListener: AssignPasswordDialogListener? = null
|
private var mListener: AssignPasswordDialogListener? = null
|
||||||
|
|
||||||
private var mKeyFileHelper: KeyFileHelper? = null
|
private var mOpenFileHelper: OpenFileHelper? = null
|
||||||
|
|
||||||
|
private val passwordTextWatcher = object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||||
|
|
||||||
|
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||||
|
|
||||||
|
override fun afterTextChanged(editable: Editable) {
|
||||||
|
passwordCheckBox?.isChecked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface AssignPasswordDialogListener {
|
interface AssignPasswordDialogListener {
|
||||||
fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean, masterPassword: String?,
|
fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean, masterPassword: String?,
|
||||||
@@ -60,18 +75,25 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
keyFileChecked: Boolean, keyFile: Uri?)
|
keyFileChecked: Boolean, keyFile: Uri?)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttach(activity: Context?) {
|
override fun onAttach(activity: Context) {
|
||||||
super.onAttach(activity)
|
super.onAttach(activity)
|
||||||
try {
|
try {
|
||||||
mListener = activity as AssignPasswordDialogListener?
|
mListener = activity as AssignPasswordDialogListener
|
||||||
} catch (e: ClassCastException) {
|
} catch (e: ClassCastException) {
|
||||||
throw ClassCastException(activity?.toString()
|
throw ClassCastException(activity.toString()
|
||||||
+ " must implement " + AssignPasswordDialogListener::class.java.name)
|
+ " must implement " + AssignPasswordDialogListener::class.java.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
|
|
||||||
|
var allowNoMasterKey = false
|
||||||
|
arguments?.apply {
|
||||||
|
if (containsKey(ALLOW_NO_MASTER_KEY_ARG))
|
||||||
|
allowNoMasterKey = getBoolean(ALLOW_NO_MASTER_KEY_ARG, false)
|
||||||
|
}
|
||||||
|
|
||||||
val builder = AlertDialog.Builder(activity)
|
val builder = AlertDialog.Builder(activity)
|
||||||
val inflater = activity.layoutInflater
|
val inflater = activity.layoutInflater
|
||||||
|
|
||||||
@@ -80,36 +102,22 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
.setTitle(R.string.assign_master_key)
|
.setTitle(R.string.assign_master_key)
|
||||||
// Add action buttons
|
// Add action buttons
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
|
|
||||||
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
|
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
|
||||||
passView = rootView?.findViewById(R.id.pass_password)
|
passwordTextInputLayout = rootView?.findViewById(R.id.password_input_layout)
|
||||||
passView?.addTextChangedListener(object : TextWatcher {
|
passwordView = rootView?.findViewById(R.id.pass_password)
|
||||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
|
||||||
|
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
|
||||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
|
||||||
|
|
||||||
override fun afterTextChanged(editable: Editable) {
|
|
||||||
passwordCheckBox?.isChecked = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
passConfView = rootView?.findViewById(R.id.pass_conf_password)
|
|
||||||
|
|
||||||
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
|
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
|
||||||
keyFileView = rootView?.findViewById(R.id.pass_keyfile)
|
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
|
||||||
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) {}
|
mOpenFileHelper = OpenFileHelper(this)
|
||||||
|
keyFileSelectionView?.apply {
|
||||||
override fun afterTextChanged(editable: Editable) {
|
setOnClickListener(mOpenFileHelper?.openFileOnClickViewListener)
|
||||||
keyFileCheckBox?.isChecked = true
|
setOnLongClickListener(mOpenFileHelper?.openFileOnClickViewListener)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
mKeyFileHelper = KeyFileHelper(this)
|
|
||||||
rootView?.findViewById<View>(R.id.browse_button)?.setOnClickListener { view ->
|
|
||||||
mKeyFileHelper?.openFileOnClickViewListener?.onClick(view) }
|
|
||||||
|
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
|
|
||||||
@@ -124,7 +132,11 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
var error = verifyPassword() || verifyFile()
|
var error = verifyPassword() || verifyFile()
|
||||||
if (!passwordCheckBox!!.isChecked && !keyFileCheckBox!!.isChecked) {
|
if (!passwordCheckBox!!.isChecked && !keyFileCheckBox!!.isChecked) {
|
||||||
error = true
|
error = true
|
||||||
showNoKeyConfirmationDialog()
|
if (allowNoMasterKey)
|
||||||
|
showNoKeyConfirmationDialog()
|
||||||
|
else {
|
||||||
|
passwordTextInputLayout?.error = getString(R.string.error_disallow_no_credentials)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!error) {
|
if (!error) {
|
||||||
mListener?.onAssignKeyDialogPositiveClick(
|
mListener?.onAssignKeyDialogPositiveClick(
|
||||||
@@ -149,20 +161,33 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
return super.onCreateDialog(savedInstanceState)
|
return super.onCreateDialog(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
// To check checkboxes if a text is present
|
||||||
|
passwordView?.addTextChangedListener(passwordTextWatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
|
||||||
|
passwordView?.removeTextChangedListener(passwordTextWatcher)
|
||||||
|
}
|
||||||
|
|
||||||
private fun verifyPassword(): Boolean {
|
private fun verifyPassword(): Boolean {
|
||||||
var error = false
|
var error = false
|
||||||
if (passwordCheckBox != null
|
if (passwordCheckBox != null
|
||||||
&& passwordCheckBox!!.isChecked
|
&& passwordCheckBox!!.isChecked
|
||||||
&& passView != null
|
&& passwordView != null
|
||||||
&& passConfView != null) {
|
&& passwordRepeatView != null) {
|
||||||
mMasterPassword = passView!!.text.toString()
|
mMasterPassword = passwordView!!.text.toString()
|
||||||
val confPassword = passConfView!!.text.toString()
|
val confPassword = passwordRepeatView!!.text.toString()
|
||||||
|
|
||||||
// Verify that passwords match
|
// Verify that passwords match
|
||||||
if (mMasterPassword != confPassword) {
|
if (mMasterPassword != confPassword) {
|
||||||
error = true
|
error = true
|
||||||
// Passwords do not match
|
// Passwords do not match
|
||||||
Toast.makeText(context, R.string.error_pass_match, Toast.LENGTH_LONG).show()
|
passwordRepeatTextInputLayout?.error = getString(R.string.error_pass_match)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mMasterPassword == null || mMasterPassword!!.isEmpty()) {
|
if (mMasterPassword == null || mMasterPassword!!.isEmpty()) {
|
||||||
@@ -170,6 +195,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
showEmptyPasswordConfirmationDialog()
|
showEmptyPasswordConfirmationDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,13 +203,12 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
var error = false
|
var error = false
|
||||||
if (keyFileCheckBox != null
|
if (keyFileCheckBox != null
|
||||||
&& keyFileCheckBox!!.isChecked) {
|
&& keyFileCheckBox!!.isChecked) {
|
||||||
val keyFile = UriUtil.parseUriFile(keyFileView?.text?.toString())
|
|
||||||
mKeyFile = keyFile
|
|
||||||
|
|
||||||
// Verify that a keyfile is set
|
keyFileSelectionView?.uri?.let { uri ->
|
||||||
if (keyFile == null || keyFile.toString().isEmpty()) {
|
mKeyFile = uri
|
||||||
|
} ?: run {
|
||||||
error = true
|
error = true
|
||||||
Toast.makeText(context, R.string.error_nokeyfile, Toast.LENGTH_LONG).show()
|
keyFileSelectionView?.error = getString(R.string.error_nokeyfile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return error
|
return error
|
||||||
@@ -201,7 +226,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
this@AssignMasterKeyDialogFragment.dismiss()
|
this@AssignMasterKeyDialogFragment.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
builder.create().show()
|
builder.create().show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,7 +241,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
keyFileCheckBox!!.isChecked, mKeyFile)
|
keyFileCheckBox!!.isChecked, mKeyFile)
|
||||||
this@AssignMasterKeyDialogFragment.dismiss()
|
this@AssignMasterKeyDialogFragment.dismiss()
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
builder.create().show()
|
builder.create().show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,13 +249,25 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
mKeyFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
||||||
) { uri ->
|
) { uri ->
|
||||||
UriUtil.parseUriFile(uri)?.let { pathUri ->
|
uri?.let { pathUri ->
|
||||||
keyFileCheckBox?.isChecked = true
|
keyFileCheckBox?.isChecked = true
|
||||||
keyFileView?.text = pathUri.toString()
|
keyFileSelectionView?.uri = pathUri
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val ALLOW_NO_MASTER_KEY_ARG = "ALLOW_NO_MASTER_KEY_ARG"
|
||||||
|
|
||||||
|
fun getInstance(allowNoMasterKey: Boolean): AssignMasterKeyDialogFragment {
|
||||||
|
val fragment = AssignMasterKeyDialogFragment()
|
||||||
|
val args = Bundle()
|
||||||
|
args.putBoolean(ALLOW_NO_MASTER_KEY_ARG, allowNoMasterKey)
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,213 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.Environment
|
|
||||||
import android.support.v4.app.DialogFragment
|
|
||||||
import android.support.v7.app.AlertDialog
|
|
||||||
import android.view.ActionMode
|
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.AdapterView
|
|
||||||
import android.widget.ArrayAdapter
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.Spinner
|
|
||||||
import android.widget.TextView
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R
|
|
||||||
import com.kunzisoft.keepass.activities.stylish.FilePickerStylishActivity
|
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
|
||||||
import com.nononsenseapps.filepicker.FilePickerActivity
|
|
||||||
import com.nononsenseapps.filepicker.Utils
|
|
||||||
|
|
||||||
class CreateFileDialogFragment : DialogFragment(), AdapterView.OnItemSelectedListener {
|
|
||||||
|
|
||||||
private val FILE_CODE = 3853
|
|
||||||
|
|
||||||
private var folderPathView: EditText? = null
|
|
||||||
private var fileNameView: EditText? = null
|
|
||||||
private var positiveButton: Button? = null
|
|
||||||
private var negativeButton: Button? = null
|
|
||||||
|
|
||||||
private var mDefinePathDialogListener: DefinePathDialogListener? = null
|
|
||||||
|
|
||||||
private var mDatabaseFileExtension: String? = null
|
|
||||||
private var mUriPath: Uri? = null
|
|
||||||
|
|
||||||
interface DefinePathDialogListener {
|
|
||||||
fun onDefinePathDialogPositiveClick(pathFile: Uri?): Boolean
|
|
||||||
fun onDefinePathDialogNegativeClick(pathFile: Uri?): Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttach(activity: Context?) {
|
|
||||||
super.onAttach(activity)
|
|
||||||
try {
|
|
||||||
mDefinePathDialogListener = activity as DefinePathDialogListener?
|
|
||||||
} catch (e: ClassCastException) {
|
|
||||||
throw ClassCastException(activity?.toString()
|
|
||||||
+ " must implement " + DefinePathDialogListener::class.java.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
activity?.let { activity ->
|
|
||||||
val builder = AlertDialog.Builder(activity)
|
|
||||||
val inflater = activity.layoutInflater
|
|
||||||
|
|
||||||
val rootView = inflater.inflate(R.layout.fragment_file_creation, null)
|
|
||||||
builder.setView(rootView)
|
|
||||||
.setTitle(R.string.create_keepass_file)
|
|
||||||
// Add action buttons
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
|
||||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
|
||||||
|
|
||||||
// To prevent crash issue #69 https://github.com/Kunzisoft/KeePassDX/issues/69
|
|
||||||
val actionCopyBarCallback = object : ActionMode.Callback {
|
|
||||||
|
|
||||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
|
||||||
positiveButton?.isEnabled = false
|
|
||||||
negativeButton?.isEnabled = false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyActionMode(mode: ActionMode) {
|
|
||||||
positiveButton?.isEnabled = true
|
|
||||||
negativeButton?.isEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Folder selection
|
|
||||||
val browseView = rootView.findViewById<View>(R.id.browse_button)
|
|
||||||
folderPathView = rootView.findViewById(R.id.folder_path)
|
|
||||||
folderPathView?.customSelectionActionModeCallback = actionCopyBarCallback
|
|
||||||
fileNameView = rootView.findViewById(R.id.filename)
|
|
||||||
fileNameView?.customSelectionActionModeCallback = actionCopyBarCallback
|
|
||||||
|
|
||||||
val defaultPath = Environment.getExternalStorageDirectory().path + getString(R.string.database_file_path_default)
|
|
||||||
folderPathView?.setText(defaultPath)
|
|
||||||
browseView.setOnClickListener { _ ->
|
|
||||||
Intent(context, FilePickerStylishActivity::class.java).apply {
|
|
||||||
putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
|
|
||||||
putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
|
|
||||||
putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
|
|
||||||
putExtra(FilePickerActivity.EXTRA_START_PATH,
|
|
||||||
Environment.getExternalStorageDirectory().path)
|
|
||||||
startActivityForResult(this, FILE_CODE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init path
|
|
||||||
mUriPath = null
|
|
||||||
|
|
||||||
// Extension
|
|
||||||
mDatabaseFileExtension = getString(R.string.database_file_extension_default)
|
|
||||||
val spinner = rootView.findViewById<Spinner>(R.id.file_types)
|
|
||||||
spinner.onItemSelectedListener = this
|
|
||||||
|
|
||||||
// Spinner Drop down elements
|
|
||||||
val fileTypes = resources.getStringArray(R.array.file_types)
|
|
||||||
val dataAdapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, fileTypes)
|
|
||||||
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
|
||||||
spinner.adapter = dataAdapter
|
|
||||||
// Or text if only one item https://github.com/Kunzisoft/KeePassDX/issues/105
|
|
||||||
if (fileTypes.size == 1) {
|
|
||||||
val params = spinner.layoutParams
|
|
||||||
spinner.visibility = View.GONE
|
|
||||||
val extensionTextView = TextView(context)
|
|
||||||
extensionTextView.text = mDatabaseFileExtension
|
|
||||||
extensionTextView.layoutParams = params
|
|
||||||
val parentView = spinner.parent as ViewGroup
|
|
||||||
parentView.addView(extensionTextView)
|
|
||||||
}
|
|
||||||
|
|
||||||
val dialog = builder.create()
|
|
||||||
|
|
||||||
dialog.setOnShowListener { _ ->
|
|
||||||
positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE)
|
|
||||||
negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE)
|
|
||||||
positiveButton?.setOnClickListener { _ ->
|
|
||||||
mDefinePathDialogListener?.let {
|
|
||||||
if (it.onDefinePathDialogPositiveClick(buildPath()))
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
negativeButton?.setOnClickListener { _->
|
|
||||||
mDefinePathDialogListener?.let {
|
|
||||||
if (it.onDefinePathDialogNegativeClick(buildPath())) {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dialog
|
|
||||||
}
|
|
||||||
return super.onCreateDialog(savedInstanceState)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildPath(): Uri? {
|
|
||||||
if (folderPathView != null && fileNameView != null && mDatabaseFileExtension != null) {
|
|
||||||
var path = Uri.Builder().path(folderPathView!!.text.toString())
|
|
||||||
.appendPath(fileNameView!!.text.toString() + mDatabaseFileExtension!!)
|
|
||||||
.build()
|
|
||||||
context?.let { context ->
|
|
||||||
path = UriUtil.translateUri(context, path)
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
if (requestCode == FILE_CODE && resultCode == Activity.RESULT_OK) {
|
|
||||||
mUriPath = data?.data
|
|
||||||
mUriPath?.let {
|
|
||||||
val file = Utils.getFileForUri(it)
|
|
||||||
folderPathView?.setText(file.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemSelected(adapterView: AdapterView<*>, view: View, position: Int, id: Long) {
|
|
||||||
mDatabaseFileExtension = adapterView.getItemAtPosition(position).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNothingSelected(adapterView: AdapterView<*>) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
|
|
||||||
|
class DuplicateUuidDialog : DialogFragment() {
|
||||||
|
|
||||||
|
var positiveAction: (() -> Unit)? = null
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
activity?.let { activity ->
|
||||||
|
// Use the Builder class for convenient dialog construction
|
||||||
|
val builder = androidx.appcompat.app.AlertDialog.Builder(activity).apply {
|
||||||
|
val message = getString(R.string.contains_duplicate_uuid) +
|
||||||
|
"\n\n" + getString(R.string.contains_duplicate_uuid_procedure)
|
||||||
|
setMessage(message)
|
||||||
|
setPositiveButton(getString(android.R.string.ok)) { _, _ ->
|
||||||
|
positiveAction?.invoke()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
setNegativeButton(getString(android.R.string.cancel)) { _, _ -> dismiss() }
|
||||||
|
}
|
||||||
|
// Create the AlertDialog object and return it
|
||||||
|
return builder.create()
|
||||||
|
}
|
||||||
|
return super.onCreateDialog(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
this.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.support.v4.app.DialogFragment
|
|
||||||
import android.support.v7.app.AlertDialog
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.TextView
|
|
||||||
import com.kunzisoft.keepass.R
|
|
||||||
import com.kunzisoft.keepass.fileselect.FileDatabaseModel
|
|
||||||
import java.text.DateFormat
|
|
||||||
|
|
||||||
class FileInformationDialogFragment : DialogFragment() {
|
|
||||||
|
|
||||||
private var fileSizeContainerView: View? = null
|
|
||||||
private var fileModificationContainerView: View? = null
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
activity?.let { activity ->
|
|
||||||
val builder = AlertDialog.Builder(activity)
|
|
||||||
val inflater = activity.layoutInflater
|
|
||||||
val root = inflater.inflate(R.layout.fragment_file_selection_information, null)
|
|
||||||
val fileNameView = root.findViewById<TextView>(R.id.file_filename)
|
|
||||||
val filePathView = root.findViewById<TextView>(R.id.file_path)
|
|
||||||
fileSizeContainerView = root.findViewById(R.id.file_size_container)
|
|
||||||
val fileSizeView = root.findViewById<TextView>(R.id.file_size)
|
|
||||||
fileModificationContainerView = root.findViewById(R.id.file_modification_container)
|
|
||||||
val fileModificationView = root.findViewById<TextView>(R.id.file_modification)
|
|
||||||
|
|
||||||
arguments?.apply {
|
|
||||||
if (containsKey(FILE_SELECT_BEEN_ARG)) {
|
|
||||||
(getSerializable(FILE_SELECT_BEEN_ARG) as FileDatabaseModel?)?.let { fileDatabaseModel ->
|
|
||||||
fileDatabaseModel.fileUri?.let { fileUri ->
|
|
||||||
filePathView.text = Uri.decode(fileUri.toString())
|
|
||||||
}
|
|
||||||
fileNameView.text = fileDatabaseModel.fileName
|
|
||||||
|
|
||||||
if (fileDatabaseModel.notFound()) {
|
|
||||||
hideFileInfo()
|
|
||||||
} else {
|
|
||||||
showFileInfo()
|
|
||||||
fileSizeView.text = fileDatabaseModel.size.toString()
|
|
||||||
fileModificationView.text = DateFormat.getDateTimeInstance()
|
|
||||||
.format(fileDatabaseModel.lastModification)
|
|
||||||
}
|
|
||||||
} ?: hideFileInfo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.setView(root)
|
|
||||||
builder.setPositiveButton(android.R.string.ok) { _, _ -> }
|
|
||||||
return builder.create()
|
|
||||||
}
|
|
||||||
return super.onCreateDialog(savedInstanceState)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showFileInfo() {
|
|
||||||
fileSizeContainerView?.visibility = View.VISIBLE
|
|
||||||
fileModificationContainerView?.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hideFileInfo() {
|
|
||||||
fileSizeContainerView?.visibility = View.GONE
|
|
||||||
fileModificationContainerView?.visibility = View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
private const val FILE_SELECT_BEEN_ARG = "FILE_SELECT_BEEN_ARG"
|
|
||||||
|
|
||||||
fun newInstance(fileDatabaseModel: FileDatabaseModel): FileInformationDialogFragment {
|
|
||||||
val fileInformationDialogFragment = FileInformationDialogFragment()
|
|
||||||
val args = Bundle()
|
|
||||||
args.putSerializable(FILE_SELECT_BEEN_ARG, fileDatabaseModel)
|
|
||||||
fileInformationDialogFragment.arguments = args
|
|
||||||
return fileInformationDialogFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +1,34 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import android.support.v7.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
import android.widget.TextView
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.utils.Util
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
|
|
||||||
class BrowserDialogFragment : DialogFragment() {
|
class FileManagerDialogFragment : DialogFragment() {
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
@@ -35,17 +36,13 @@ class BrowserDialogFragment : DialogFragment() {
|
|||||||
// Get the layout inflater
|
// Get the layout inflater
|
||||||
val root = activity.layoutInflater.inflate(R.layout.fragment_browser_install, null)
|
val root = activity.layoutInflater.inflate(R.layout.fragment_browser_install, null)
|
||||||
builder.setView(root)
|
builder.setView(root)
|
||||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
|
|
||||||
val market = root.findViewById<Button>(R.id.install_market)
|
val textDescription = root.findViewById<TextView>(R.id.file_manager_install_description)
|
||||||
market.setOnClickListener {
|
textDescription.text = getString(R.string.file_manager_install_description)
|
||||||
Util.gotoUrl(context!!, R.string.filemanager_play_store)
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
val web = root.findViewById<Button>(R.id.install_web)
|
root.findViewById<Button>(R.id.file_manager_button).setOnClickListener {
|
||||||
web.setOnClickListener {
|
UriUtil.gotoUrl(requireContext(), R.string.file_manager_explanation_url)
|
||||||
Util.gotoUrl(context!!, R.string.filemanager_f_droid)
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
@@ -22,14 +22,18 @@ package com.kunzisoft.keepass.activities.dialogs
|
|||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.DialogFragment
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import android.support.v7.app.AlertDialog
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.Button
|
||||||
|
import android.widget.CompoundButton
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.SeekBar
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.password.PasswordGenerator
|
import com.kunzisoft.keepass.password.PasswordGenerator
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.utils.applyFontVisibility
|
import com.kunzisoft.keepass.view.applyFontVisibility
|
||||||
|
|
||||||
class GeneratePasswordDialogFragment : DialogFragment() {
|
class GeneratePasswordDialogFragment : DialogFragment() {
|
||||||
|
|
||||||
@@ -37,6 +41,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
private var root: View? = null
|
private var root: View? = null
|
||||||
private var lengthTextView: EditText? = null
|
private var lengthTextView: EditText? = null
|
||||||
|
private var passwordInputLayoutView: TextInputLayout? = null
|
||||||
private var passwordView: EditText? = null
|
private var passwordView: EditText? = null
|
||||||
|
|
||||||
private var uppercaseBox: CompoundButton? = null
|
private var uppercaseBox: CompoundButton? = null
|
||||||
@@ -49,12 +54,12 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
|||||||
private var bracketsBox: CompoundButton? = null
|
private var bracketsBox: CompoundButton? = null
|
||||||
private var extendedBox: CompoundButton? = null
|
private var extendedBox: CompoundButton? = null
|
||||||
|
|
||||||
override fun onAttach(context: Context?) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
try {
|
try {
|
||||||
mListener = context as GeneratePasswordListener?
|
mListener = context as GeneratePasswordListener
|
||||||
} catch (e: ClassCastException) {
|
} catch (e: ClassCastException) {
|
||||||
throw ClassCastException(context?.toString()
|
throw ClassCastException(context.toString()
|
||||||
+ " must implement " + GeneratePasswordListener::class.java.name)
|
+ " must implement " + GeneratePasswordListener::class.java.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,6 +70,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
|||||||
val inflater = activity.layoutInflater
|
val inflater = activity.layoutInflater
|
||||||
root = inflater.inflate(R.layout.fragment_generate_password, null)
|
root = inflater.inflate(R.layout.fragment_generate_password, null)
|
||||||
|
|
||||||
|
passwordInputLayoutView = root?.findViewById(R.id.password_input_layout)
|
||||||
passwordView = root?.findViewById(R.id.password)
|
passwordView = root?.findViewById(R.id.password)
|
||||||
passwordView?.applyFontVisibility()
|
passwordView?.applyFontVisibility()
|
||||||
|
|
||||||
@@ -108,7 +114,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
mListener?.cancelPassword(bundle)
|
mListener?.cancelPassword(bundle)
|
||||||
|
|
||||||
@@ -161,9 +167,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
|||||||
var password = ""
|
var password = ""
|
||||||
try {
|
try {
|
||||||
val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString())
|
val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString())
|
||||||
|
password = PasswordGenerator(resources).generatePassword(length,
|
||||||
val generator = PasswordGenerator(resources)
|
|
||||||
password = generator.generatePassword(length,
|
|
||||||
uppercaseBox?.isChecked == true,
|
uppercaseBox?.isChecked == true,
|
||||||
lowercaseBox?.isChecked == true,
|
lowercaseBox?.isChecked == true,
|
||||||
digitsBox?.isChecked == true,
|
digitsBox?.isChecked == true,
|
||||||
@@ -173,10 +177,11 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
|||||||
specialsBox?.isChecked == true,
|
specialsBox?.isChecked == true,
|
||||||
bracketsBox?.isChecked == true,
|
bracketsBox?.isChecked == true,
|
||||||
extendedBox?.isChecked == true)
|
extendedBox?.isChecked == true)
|
||||||
|
passwordInputLayoutView?.error = null
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: NumberFormatException) {
|
||||||
Toast.makeText(context, R.string.error_wrong_length, Toast.LENGTH_LONG).show()
|
passwordInputLayoutView?.error = getString(R.string.error_wrong_length)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
passwordInputLayoutView?.error = e.message
|
||||||
}
|
}
|
||||||
|
|
||||||
return password
|
return password
|
||||||
@@ -188,7 +193,6 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val KEY_PASSWORD_ID = "KEY_PASSWORD_ID"
|
const val KEY_PASSWORD_ID = "KEY_PASSWORD_ID"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
@@ -23,9 +23,9 @@ import android.app.Dialog
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.design.widget.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import android.support.v4.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import android.support.v7.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
@@ -33,8 +33,8 @@ import com.kunzisoft.keepass.R
|
|||||||
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION
|
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION
|
||||||
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE
|
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.element.GroupVersioned
|
import com.kunzisoft.keepass.database.element.Group
|
||||||
import com.kunzisoft.keepass.database.element.PwIcon
|
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||||
|
|
||||||
class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener {
|
class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener {
|
||||||
@@ -45,7 +45,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
|||||||
|
|
||||||
private var editGroupDialogAction: EditGroupDialogAction? = null
|
private var editGroupDialogAction: EditGroupDialogAction? = null
|
||||||
private var nameGroup: String? = null
|
private var nameGroup: String? = null
|
||||||
private var iconGroup: PwIcon? = null
|
private var iconGroup: IconImage? = null
|
||||||
|
|
||||||
private var nameTextLayoutView: TextInputLayout? = null
|
private var nameTextLayoutView: TextInputLayout? = null
|
||||||
private var nameTextView: TextView? = null
|
private var nameTextView: TextView? = null
|
||||||
@@ -62,15 +62,15 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttach(context: Context?) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
// Verify that the host activity implements the callback interface
|
// Verify that the host activity implements the callback interface
|
||||||
try {
|
try {
|
||||||
// Instantiate the NoticeDialogListener so we can send events to the host
|
// Instantiate the NoticeDialogListener so we can send events to the host
|
||||||
editGroupListener = context as EditGroupListener?
|
editGroupListener = context as EditGroupListener
|
||||||
} catch (e: ClassCastException) {
|
} catch (e: ClassCastException) {
|
||||||
// The activity doesn't implement the interface, throw exception
|
// The activity doesn't implement the interface, throw exception
|
||||||
throw ClassCastException(context?.toString()
|
throw ClassCastException(context.toString()
|
||||||
+ " must implement " + GroupEditDialogFragment::class.java.name)
|
+ " must implement " + GroupEditDialogFragment::class.java.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
|||||||
val builder = AlertDialog.Builder(activity)
|
val builder = AlertDialog.Builder(activity)
|
||||||
builder.setView(root)
|
builder.setView(root)
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
editGroupListener?.cancelEditGroup(
|
editGroupListener?.cancelEditGroup(
|
||||||
editGroupDialogAction,
|
editGroupDialogAction,
|
||||||
nameTextView?.text?.toString(),
|
nameTextView?.text?.toString(),
|
||||||
@@ -130,9 +130,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
|||||||
}
|
}
|
||||||
|
|
||||||
iconButtonView?.setOnClickListener { _ ->
|
iconButtonView?.setOnClickListener { _ ->
|
||||||
fragmentManager?.let {
|
IconPickerDialogFragment().show(parentFragmentManager, "IconPickerDialogFragment")
|
||||||
IconPickerDialogFragment().show(it, "IconPickerDialogFragment")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.create()
|
return builder.create()
|
||||||
@@ -186,8 +184,8 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface EditGroupListener {
|
interface EditGroupListener {
|
||||||
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?)
|
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
|
||||||
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?)
|
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -206,7 +204,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
|||||||
return fragment
|
return fragment
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build(group: GroupVersioned): GroupEditDialogFragment {
|
fun build(group: Group): GroupEditDialogFragment {
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putString(KEY_NAME, group.title)
|
bundle.putString(KEY_NAME, group.title)
|
||||||
bundle.putParcelable(KEY_ICON, group.icon)
|
bundle.putParcelable(KEY_ICON, group.icon)
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
@@ -24,18 +24,18 @@ import android.content.Context
|
|||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.DialogFragment
|
|
||||||
import android.support.v4.widget.ImageViewCompat
|
|
||||||
import android.support.v7.app.AlertDialog
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.BaseAdapter
|
import android.widget.BaseAdapter
|
||||||
import android.widget.GridView
|
import android.widget.GridView
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.widget.ImageViewCompat
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
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.IconPack
|
||||||
import com.kunzisoft.keepass.icons.IconPackChooser
|
import com.kunzisoft.keepass.icons.IconPackChooser
|
||||||
|
|
||||||
@@ -45,13 +45,13 @@ class IconPickerDialogFragment : DialogFragment() {
|
|||||||
private var iconPickerListener: IconPickerListener? = null
|
private var iconPickerListener: IconPickerListener? = null
|
||||||
private var iconPack: IconPack? = null
|
private var iconPack: IconPack? = null
|
||||||
|
|
||||||
override fun onAttach(context: Context?) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
try {
|
try {
|
||||||
iconPickerListener = context as IconPickerListener?
|
iconPickerListener = context as IconPickerListener
|
||||||
} catch (e: ClassCastException) {
|
} catch (e: ClassCastException) {
|
||||||
// The activity doesn't implement the interface, throw exception
|
// The activity doesn't implement the interface, throw exception
|
||||||
throw ClassCastException(context!!.toString()
|
throw ClassCastException(context.toString()
|
||||||
+ " must implement " + IconPickerListener::class.java.name)
|
+ " must implement " + IconPickerListener::class.java.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ class IconPickerDialogFragment : DialogFragment() {
|
|||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
val builder = AlertDialog.Builder(activity)
|
val builder = AlertDialog.Builder(activity)
|
||||||
|
|
||||||
iconPack = IconPackChooser.getSelectedIconPack(context!!)
|
iconPack = IconPackChooser.getSelectedIconPack(requireContext())
|
||||||
|
|
||||||
// Inflate and set the layout for the dialog
|
// Inflate and set the layout for the dialog
|
||||||
// Pass null as the parent view because its going in the dialog layout
|
// Pass null as the parent view because its going in the dialog layout
|
||||||
@@ -72,12 +72,12 @@ class IconPickerDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
currIconGridView.setOnItemClickListener { _, _, position, _ ->
|
currIconGridView.setOnItemClickListener { _, _, position, _ ->
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putParcelable(KEY_ICON_STANDARD, PwIconStandard(position))
|
bundle.putParcelable(KEY_ICON_STANDARD, IconImageStandard(position))
|
||||||
iconPickerListener?.iconPicked(bundle)
|
iconPickerListener?.iconPicked(bundle)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setNegativeButton(R.string.cancel) { _, _ -> this@IconPickerDialogFragment.dialog.cancel() }
|
builder.setNegativeButton(android.R.string.cancel) { _, _ -> this@IconPickerDialogFragment.dialog?.cancel() }
|
||||||
|
|
||||||
return builder.create()
|
return builder.create()
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ class IconPickerDialogFragment : DialogFragment() {
|
|||||||
// Retrieve the textColor to tint the icon
|
// Retrieve the textColor to tint the icon
|
||||||
val ta = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
|
val ta = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
|
||||||
ImageViewCompat.setImageTintList(iconImageView, ColorStateList.valueOf(ta.getColor(0, Color.BLACK)))
|
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"
|
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)
|
return bundle.getParcelable(KEY_ICON_STANDARD)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.provider.Settings
|
|
||||||
import android.support.v4.app.DialogFragment
|
|
||||||
import android.support.v7.app.AlertDialog
|
|
||||||
import android.view.View
|
|
||||||
import com.kunzisoft.keepass.BuildConfig
|
|
||||||
import com.kunzisoft.keepass.R
|
|
||||||
import com.kunzisoft.keepass.utils.Util
|
|
||||||
|
|
||||||
class KeyboardExplanationDialogFragment : DialogFragment() {
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
activity?.let {
|
|
||||||
val builder = AlertDialog.Builder(activity!!)
|
|
||||||
val inflater = activity!!.layoutInflater
|
|
||||||
|
|
||||||
val rootView = inflater.inflate(R.layout.fragment_keyboard_explanation, null)
|
|
||||||
|
|
||||||
rootView.findViewById<View>(R.id.keyboards_activate_setting_path1_text)
|
|
||||||
.setOnClickListener { launchActivateKeyboardSetting() }
|
|
||||||
rootView.findViewById<View>(R.id.keyboards_activate_setting_path2_text)
|
|
||||||
.setOnClickListener { launchActivateKeyboardSetting() }
|
|
||||||
|
|
||||||
val containerKeyboardSwitcher = rootView.findViewById<View>(R.id.container_keyboard_switcher)
|
|
||||||
if (BuildConfig.CLOSED_STORE) {
|
|
||||||
containerKeyboardSwitcher.setOnClickListener { Util.gotoUrl(context!!, R.string.keyboard_switcher_play_store) }
|
|
||||||
} else {
|
|
||||||
containerKeyboardSwitcher.setOnClickListener { Util.gotoUrl(context!!, R.string.keyboard_switcher_f_droid) }
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.setView(rootView)
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
|
||||||
return builder.create()
|
|
||||||
}
|
|
||||||
return super.onCreateDialog(savedInstanceState)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun launchActivateKeyboardSetting() {
|
|
||||||
val intent = Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)
|
|
||||||
intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
@@ -23,7 +23,7 @@ import android.app.AlertDialog
|
|||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
|
|
||||||
class PasswordEncodingDialogFragment : DialogFragment() {
|
class PasswordEncodingDialogFragment : DialogFragment() {
|
||||||
@@ -35,7 +35,7 @@ class PasswordEncodingDialogFragment : DialogFragment() {
|
|||||||
val builder = AlertDialog.Builder(activity)
|
val builder = AlertDialog.Builder(activity)
|
||||||
builder.setMessage(activity.getString(R.string.warning_password_encoding)).setTitle(R.string.warning)
|
builder.setMessage(activity.getString(R.string.warning_password_encoding)).setTitle(R.string.warning)
|
||||||
builder.setPositiveButton(android.R.string.ok, positiveButtonClickListener)
|
builder.setPositiveButton(android.R.string.ok, positiveButtonClickListener)
|
||||||
builder.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.cancel() }
|
builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.cancel() }
|
||||||
|
|
||||||
return builder.create()
|
return builder.create()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,34 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.DialogFragment
|
|
||||||
import android.support.v7.app.AlertDialog
|
|
||||||
import android.text.Html
|
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.widget.Toast
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
|
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.kunzisoft.keepass.BuildConfig
|
import com.kunzisoft.keepass.BuildConfig
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.utils.Util
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom Dialog that asks the user to download the pro version or make a donation.
|
* Custom Dialog that asks the user to download the pro version or make a donation.
|
||||||
@@ -44,25 +42,16 @@ class ProFeatureDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
val stringBuilder = SpannableStringBuilder()
|
val stringBuilder = SpannableStringBuilder()
|
||||||
if (BuildConfig.CLOSED_STORE) {
|
if (BuildConfig.CLOSED_STORE) {
|
||||||
// TODO HtmlCompat with androidX
|
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_ad_free), FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_ad_free))).append("\n\n")
|
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_buy_pro), FROM_HTML_MODE_LEGACY))
|
||||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_buy_pro)))
|
|
||||||
builder.setPositiveButton(R.string.download) { _, _ ->
|
builder.setPositiveButton(R.string.download) { _, _ ->
|
||||||
try {
|
UriUtil.gotoUrl(requireContext(), R.string.app_pro_url)
|
||||||
Util.gotoUrl(context!!, R.string.app_pro_url)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_feature_generosity))).append("\n\n")
|
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_feature_generosity), FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_donation)))
|
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_donation), FROM_HTML_MODE_LEGACY))
|
||||||
builder.setPositiveButton(R.string.contribute) { _, _ ->
|
builder.setPositiveButton(R.string.contribute) { _, _ ->
|
||||||
try {
|
UriUtil.gotoUrl(requireContext(), R.string.contribution_url)
|
||||||
Util.gotoUrl(context!!, R.string.contribution_url)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.setMessage(stringBuilder)
|
builder.setMessage(stringBuilder)
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
|
||||||
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.preference.PreferenceManager
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R
|
|
||||||
|
|
||||||
class ReadOnlyDialog(context: Context) : AlertDialog(context) {
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle) {
|
|
||||||
val ctx = context
|
|
||||||
var warning = ctx.getString(R.string.read_only_warning)
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
||||||
warning = warning + "\n\n" + context.getString(R.string.read_only_kitkat_warning)
|
|
||||||
}
|
|
||||||
setMessage(warning)
|
|
||||||
|
|
||||||
setButton(BUTTON_POSITIVE, ctx.getText(android.R.string.ok)) { _, _ -> dismiss() }
|
|
||||||
setButton(BUTTON_NEGATIVE, ctx.getText(R.string.beta_dontask)) { _, _ ->
|
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(ctx)
|
|
||||||
val edit = prefs.edit()
|
|
||||||
edit.putBoolean(ctx.getString(R.string.show_read_only_warning), false)
|
|
||||||
edit.apply()
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.Spinner
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import com.kunzisoft.keepass.BuildConfig
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.model.OtpModel
|
||||||
|
import com.kunzisoft.keepass.otp.OtpElement
|
||||||
|
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_HOTP_COUNTER
|
||||||
|
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_OTP_DIGITS
|
||||||
|
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_TOTP_PERIOD
|
||||||
|
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_HOTP_COUNTER
|
||||||
|
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_OTP_DIGITS
|
||||||
|
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_TOTP_PERIOD
|
||||||
|
import com.kunzisoft.keepass.otp.OtpTokenType
|
||||||
|
import com.kunzisoft.keepass.otp.OtpType
|
||||||
|
import com.kunzisoft.keepass.otp.TokenCalculator
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class SetOTPDialogFragment : DialogFragment() {
|
||||||
|
|
||||||
|
private var mCreateOTPElementListener: CreateOtpListener? = null
|
||||||
|
|
||||||
|
private var mOtpElement: OtpElement = OtpElement()
|
||||||
|
|
||||||
|
private var otpTypeSpinner: Spinner? = null
|
||||||
|
private var otpTokenTypeSpinner: Spinner? = null
|
||||||
|
private var otpSecretContainer: TextInputLayout? = null
|
||||||
|
private var otpSecretTextView: EditText? = null
|
||||||
|
private var otpPeriodContainer: TextInputLayout? = null
|
||||||
|
private var otpPeriodTextView: EditText? = null
|
||||||
|
private var otpCounterContainer: TextInputLayout? = null
|
||||||
|
private var otpCounterTextView: EditText? = null
|
||||||
|
private var otpDigitsContainer: TextInputLayout? = null
|
||||||
|
private var otpDigitsTextView: EditText? = null
|
||||||
|
private var otpAlgorithmSpinner: Spinner? = null
|
||||||
|
|
||||||
|
private var otpTypeAdapter: ArrayAdapter<OtpType>? = null
|
||||||
|
private var otpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
|
||||||
|
private var totpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
|
||||||
|
private var hotpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
|
||||||
|
private var otpAlgorithmAdapter: ArrayAdapter<TokenCalculator.HashAlgorithm>? = null
|
||||||
|
|
||||||
|
private var mManualEvent = false
|
||||||
|
private var mOnFocusChangeListener = View.OnFocusChangeListener { _, isFocus ->
|
||||||
|
if (!isFocus)
|
||||||
|
mManualEvent = true
|
||||||
|
}
|
||||||
|
private var mOnTouchListener = View.OnTouchListener { _, event ->
|
||||||
|
when (event.action) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
mManualEvent = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
private var mSecretWellFormed = false
|
||||||
|
private var mCounterWellFormed = true
|
||||||
|
private var mPeriodWellFormed = true
|
||||||
|
private var mDigitsWellFormed = true
|
||||||
|
|
||||||
|
override fun onAttach(context: Context) {
|
||||||
|
super.onAttach(context)
|
||||||
|
// Verify that the host activity implements the callback interface
|
||||||
|
try {
|
||||||
|
// Instantiate the NoticeDialogListener so we can send events to the host
|
||||||
|
mCreateOTPElementListener = context as CreateOtpListener
|
||||||
|
} catch (e: ClassCastException) {
|
||||||
|
// The activity doesn't implement the interface, throw exception
|
||||||
|
throw ClassCastException(context.toString()
|
||||||
|
+ " must implement " + CreateOtpListener::class.java.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
|
||||||
|
// Retrieve OTP model from instance state
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
if (savedInstanceState.containsKey(KEY_OTP)) {
|
||||||
|
savedInstanceState.getParcelable<OtpModel>(KEY_OTP)?.let { otpModel ->
|
||||||
|
mOtpElement = OtpElement(otpModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
arguments?.apply {
|
||||||
|
if (containsKey(KEY_OTP)) {
|
||||||
|
getParcelable<OtpModel?>(KEY_OTP)?.let { otpModel ->
|
||||||
|
mOtpElement = OtpElement(otpModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activity?.let { activity ->
|
||||||
|
val root = activity.layoutInflater.inflate(R.layout.fragment_set_otp, null) as ViewGroup?
|
||||||
|
otpTypeSpinner = root?.findViewById(R.id.setup_otp_type)
|
||||||
|
otpTokenTypeSpinner = root?.findViewById(R.id.setup_otp_token_type)
|
||||||
|
otpSecretContainer = root?.findViewById(R.id.setup_otp_secret_label)
|
||||||
|
otpSecretTextView = root?.findViewById(R.id.setup_otp_secret)
|
||||||
|
otpAlgorithmSpinner = root?.findViewById(R.id.setup_otp_algorithm)
|
||||||
|
otpPeriodContainer= root?.findViewById(R.id.setup_otp_period_label)
|
||||||
|
otpPeriodTextView = root?.findViewById(R.id.setup_otp_period)
|
||||||
|
otpCounterContainer= root?.findViewById(R.id.setup_otp_counter_label)
|
||||||
|
otpCounterTextView = root?.findViewById(R.id.setup_otp_counter)
|
||||||
|
otpDigitsContainer = root?.findViewById(R.id.setup_otp_digits_label)
|
||||||
|
otpDigitsTextView = root?.findViewById(R.id.setup_otp_digits)
|
||||||
|
|
||||||
|
// To fix init element
|
||||||
|
// With tab keyboard selection
|
||||||
|
otpSecretTextView?.onFocusChangeListener = mOnFocusChangeListener
|
||||||
|
// With finger selection
|
||||||
|
otpTypeSpinner?.setOnTouchListener(mOnTouchListener)
|
||||||
|
otpTokenTypeSpinner?.setOnTouchListener(mOnTouchListener)
|
||||||
|
otpSecretTextView?.setOnTouchListener(mOnTouchListener)
|
||||||
|
otpAlgorithmSpinner?.setOnTouchListener(mOnTouchListener)
|
||||||
|
otpPeriodTextView?.setOnTouchListener(mOnTouchListener)
|
||||||
|
otpCounterTextView?.setOnTouchListener(mOnTouchListener)
|
||||||
|
otpDigitsTextView?.setOnTouchListener(mOnTouchListener)
|
||||||
|
|
||||||
|
|
||||||
|
// HOTP / TOTP Type selection
|
||||||
|
val otpTypeArray = OtpType.values()
|
||||||
|
otpTypeAdapter = ArrayAdapter<OtpType>(activity,
|
||||||
|
android.R.layout.simple_spinner_item, otpTypeArray).apply {
|
||||||
|
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
}
|
||||||
|
otpTypeSpinner?.adapter = otpTypeAdapter
|
||||||
|
|
||||||
|
// Otp Token type selection
|
||||||
|
val hotpTokenTypeArray = OtpTokenType.getHotpTokenTypeValues()
|
||||||
|
hotpTokenTypeAdapter = ArrayAdapter(activity,
|
||||||
|
android.R.layout.simple_spinner_item, hotpTokenTypeArray).apply {
|
||||||
|
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
}
|
||||||
|
// Proprietary only on closed and full version
|
||||||
|
val totpTokenTypeArray = OtpTokenType.getTotpTokenTypeValues(
|
||||||
|
BuildConfig.CLOSED_STORE && BuildConfig.FULL_VERSION)
|
||||||
|
totpTokenTypeAdapter = ArrayAdapter(activity,
|
||||||
|
android.R.layout.simple_spinner_item, totpTokenTypeArray).apply {
|
||||||
|
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
}
|
||||||
|
otpTokenTypeAdapter = hotpTokenTypeAdapter
|
||||||
|
otpTokenTypeSpinner?.adapter = otpTokenTypeAdapter
|
||||||
|
|
||||||
|
// OTP Algorithm
|
||||||
|
val otpAlgorithmArray = TokenCalculator.HashAlgorithm.values()
|
||||||
|
otpAlgorithmAdapter = ArrayAdapter<TokenCalculator.HashAlgorithm>(activity,
|
||||||
|
android.R.layout.simple_spinner_item, otpAlgorithmArray).apply {
|
||||||
|
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
}
|
||||||
|
otpAlgorithmSpinner?.adapter = otpAlgorithmAdapter
|
||||||
|
|
||||||
|
// Set the default value of OTP element
|
||||||
|
upgradeType()
|
||||||
|
upgradeTokenType()
|
||||||
|
upgradeParameters()
|
||||||
|
|
||||||
|
attachListeners()
|
||||||
|
|
||||||
|
val builder = AlertDialog.Builder(activity)
|
||||||
|
builder.apply {
|
||||||
|
setTitle(R.string.entry_setup_otp)
|
||||||
|
setView(root)
|
||||||
|
.setPositiveButton(android.R.string.ok) {_, _ -> }
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.create()
|
||||||
|
}
|
||||||
|
return super.onCreateDialog(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
(dialog as AlertDialog).getButton(Dialog.BUTTON_POSITIVE).setOnClickListener {
|
||||||
|
if (mSecretWellFormed
|
||||||
|
&& mCounterWellFormed
|
||||||
|
&& mPeriodWellFormed
|
||||||
|
&& mDigitsWellFormed) {
|
||||||
|
mCreateOTPElementListener?.onOtpCreated(mOtpElement)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun attachListeners() {
|
||||||
|
// Set Type listener
|
||||||
|
otpTypeSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||||
|
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
|
if (mManualEvent) {
|
||||||
|
(parent?.selectedItem as OtpType?)?.let {
|
||||||
|
mOtpElement.type = it
|
||||||
|
upgradeTokenType()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set type token listener
|
||||||
|
otpTokenTypeSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||||
|
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
|
if (mManualEvent) {
|
||||||
|
(parent?.selectedItem as OtpTokenType?)?.let {
|
||||||
|
mOtpElement.tokenType = it
|
||||||
|
upgradeParameters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set algorithm spinner
|
||||||
|
otpAlgorithmSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||||
|
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
|
if (mManualEvent) {
|
||||||
|
(parent?.selectedItem as TokenCalculator.HashAlgorithm?)?.let {
|
||||||
|
mOtpElement.algorithm = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set secret in OtpElement
|
||||||
|
otpSecretTextView?.addTextChangedListener(object: TextWatcher {
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
s?.toString()?.let { userString ->
|
||||||
|
try {
|
||||||
|
mOtpElement.setBase32Secret(userString.toUpperCase(Locale.ENGLISH))
|
||||||
|
otpSecretContainer?.error = null
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
otpSecretContainer?.error = getString(R.string.error_otp_secret_key)
|
||||||
|
}
|
||||||
|
mSecretWellFormed = otpSecretContainer?.error == null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set counter in OtpElement
|
||||||
|
otpCounterTextView?.addTextChangedListener(object: TextWatcher {
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
if (mManualEvent) {
|
||||||
|
s?.toString()?.toLongOrNull()?.let {
|
||||||
|
try {
|
||||||
|
mOtpElement.counter = it
|
||||||
|
otpCounterContainer?.error = null
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
otpCounterContainer?.error = getString(R.string.error_otp_counter,
|
||||||
|
MIN_HOTP_COUNTER, MAX_HOTP_COUNTER)
|
||||||
|
}
|
||||||
|
mCounterWellFormed = otpCounterContainer?.error == null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set period in OtpElement
|
||||||
|
otpPeriodTextView?.addTextChangedListener(object: TextWatcher {
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
if (mManualEvent) {
|
||||||
|
s?.toString()?.toIntOrNull()?.let {
|
||||||
|
try {
|
||||||
|
mOtpElement.period = it
|
||||||
|
otpPeriodContainer?.error = null
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
otpPeriodContainer?.error = getString(R.string.error_otp_period,
|
||||||
|
MIN_TOTP_PERIOD, MAX_TOTP_PERIOD)
|
||||||
|
}
|
||||||
|
mPeriodWellFormed = otpPeriodContainer?.error == null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set digits in OtpElement
|
||||||
|
otpDigitsTextView?.addTextChangedListener(object: TextWatcher {
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
if (mManualEvent) {
|
||||||
|
s?.toString()?.toIntOrNull()?.let {
|
||||||
|
try {
|
||||||
|
mOtpElement.digits = it
|
||||||
|
otpDigitsContainer?.error = null
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
otpDigitsContainer?.error = getString(R.string.error_otp_digits,
|
||||||
|
MIN_OTP_DIGITS, MAX_OTP_DIGITS)
|
||||||
|
}
|
||||||
|
mDigitsWellFormed = otpDigitsContainer?.error == null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun upgradeType() {
|
||||||
|
otpTypeSpinner?.setSelection(OtpType.values().indexOf(mOtpElement.type))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun upgradeTokenType() {
|
||||||
|
when (mOtpElement.type) {
|
||||||
|
OtpType.HOTP -> {
|
||||||
|
otpPeriodContainer?.visibility = View.GONE
|
||||||
|
otpCounterContainer?.visibility = View.VISIBLE
|
||||||
|
otpTokenTypeSpinner?.adapter = hotpTokenTypeAdapter
|
||||||
|
otpTokenTypeSpinner?.setSelection(OtpTokenType
|
||||||
|
.getHotpTokenTypeValues().indexOf(mOtpElement.tokenType))
|
||||||
|
}
|
||||||
|
OtpType.TOTP -> {
|
||||||
|
otpPeriodContainer?.visibility = View.VISIBLE
|
||||||
|
otpCounterContainer?.visibility = View.GONE
|
||||||
|
otpTokenTypeSpinner?.adapter = totpTokenTypeAdapter
|
||||||
|
otpTokenTypeSpinner?.setSelection(OtpTokenType
|
||||||
|
.getTotpTokenTypeValues().indexOf(mOtpElement.tokenType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun upgradeParameters() {
|
||||||
|
otpAlgorithmSpinner?.setSelection(TokenCalculator.HashAlgorithm.values()
|
||||||
|
.indexOf(mOtpElement.algorithm))
|
||||||
|
otpSecretTextView?.apply {
|
||||||
|
setText(mOtpElement.getBase32Secret())
|
||||||
|
// Cursor at end
|
||||||
|
setSelection(this.text.length)
|
||||||
|
}
|
||||||
|
otpCounterTextView?.setText(mOtpElement.counter.toString())
|
||||||
|
otpPeriodTextView?.setText(mOtpElement.period.toString())
|
||||||
|
otpDigitsTextView?.setText(mOtpElement.digits.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putParcelable(KEY_OTP, mOtpElement.otpModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreateOtpListener {
|
||||||
|
fun onOtpCreated(otpElement: OtpElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val KEY_OTP = "KEY_OTP"
|
||||||
|
|
||||||
|
fun build(otpModel: OtpModel? = null): SetOTPDialogFragment {
|
||||||
|
return SetOTPDialogFragment().apply {
|
||||||
|
if (otpModel != null) {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putParcelable(KEY_OTP, otpModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
@@ -22,14 +22,14 @@ package com.kunzisoft.keepass.activities.dialogs
|
|||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
import android.support.v4.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import android.support.v7.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.CompoundButton
|
import android.widget.CompoundButton
|
||||||
import android.widget.RadioGroup
|
import android.widget.RadioGroup
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.database.SortNodeEnum
|
import com.kunzisoft.keepass.database.element.SortNodeEnum
|
||||||
|
|
||||||
class SortDialogFragment : DialogFragment() {
|
class SortDialogFragment : DialogFragment() {
|
||||||
|
|
||||||
@@ -42,12 +42,14 @@ class SortDialogFragment : DialogFragment() {
|
|||||||
private var mAscending: Boolean = true
|
private var mAscending: Boolean = true
|
||||||
private var mRecycleBinBottom: Boolean = true
|
private var mRecycleBinBottom: Boolean = true
|
||||||
|
|
||||||
override fun onAttach(context: Context?) {
|
private var recycleBinBottomView: CompoundButton? = null
|
||||||
|
|
||||||
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
try {
|
try {
|
||||||
mListener = context as SortSelectionListener?
|
mListener = context as SortSelectionListener
|
||||||
} catch (e: ClassCastException) {
|
} catch (e: ClassCastException) {
|
||||||
throw ClassCastException(context!!.toString()
|
throw ClassCastException(context.toString()
|
||||||
+ " must implement " + SortSelectionListener::class.java.name)
|
+ " must implement " + SortSelectionListener::class.java.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,15 +75,17 @@ class SortDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mCheckedId = retrieveViewFromEnum(mSortNodeEnum!!)
|
mCheckedId = retrieveViewFromEnum(mSortNodeEnum)
|
||||||
|
|
||||||
val rootView = activity.layoutInflater.inflate(R.layout.fragment_sort_selection, null)
|
val rootView = activity.layoutInflater.inflate(R.layout.fragment_sort_selection, null)
|
||||||
builder.setTitle(R.string.sort_menu)
|
builder.setTitle(R.string.sort_menu)
|
||||||
builder.setView(rootView)
|
builder.setView(rootView)
|
||||||
// Add action buttons
|
// Add action buttons
|
||||||
.setPositiveButton(android.R.string.ok
|
.setPositiveButton(android.R.string.ok
|
||||||
) { _, _ -> mListener?.onSortSelected(mSortNodeEnum!!, mAscending, mGroupsBefore, mRecycleBinBottom) }
|
) { _, _ -> mListener?.onSortSelected(mSortNodeEnum,
|
||||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
SortNodeEnum.SortNodeParameters(mAscending, mGroupsBefore, mRecycleBinBottom))
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
|
|
||||||
val ascendingView = rootView.findViewById<CompoundButton>(R.id.sort_selection_ascending)
|
val ascendingView = rootView.findViewById<CompoundButton>(R.id.sort_selection_ascending)
|
||||||
// Check if is ascending or descending
|
// Check if is ascending or descending
|
||||||
@@ -93,25 +97,35 @@ class SortDialogFragment : DialogFragment() {
|
|||||||
groupsBeforeView.isChecked = mGroupsBefore
|
groupsBeforeView.isChecked = mGroupsBefore
|
||||||
groupsBeforeView.setOnCheckedChangeListener { _, isChecked -> mGroupsBefore = isChecked }
|
groupsBeforeView.setOnCheckedChangeListener { _, isChecked -> mGroupsBefore = isChecked }
|
||||||
|
|
||||||
val recycleBinBottomView = rootView.findViewById<CompoundButton>(R.id.sort_selection_recycle_bin_bottom)
|
recycleBinBottomView = rootView.findViewById(R.id.sort_selection_recycle_bin_bottom)
|
||||||
if (!recycleBinAllowed) {
|
if (!recycleBinAllowed) {
|
||||||
recycleBinBottomView.visibility = View.GONE
|
recycleBinBottomView?.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
// Check if recycle bin at the bottom
|
// Check if recycle bin at the bottom
|
||||||
recycleBinBottomView.isChecked = mRecycleBinBottom
|
recycleBinBottomView?.isChecked = mRecycleBinBottom
|
||||||
recycleBinBottomView.setOnCheckedChangeListener { _, isChecked -> mRecycleBinBottom = isChecked }
|
recycleBinBottomView?.setOnCheckedChangeListener { _, isChecked -> mRecycleBinBottom = isChecked }
|
||||||
|
|
||||||
|
disableRecycleBinBottomOptionIfNaturalOrder()
|
||||||
}
|
}
|
||||||
|
|
||||||
val sortSelectionRadioGroupView = rootView.findViewById<RadioGroup>(R.id.sort_selection_radio_group)
|
val sortSelectionRadioGroupView = rootView.findViewById<RadioGroup>(R.id.sort_selection_radio_group)
|
||||||
// Check value by default
|
// Check value by default
|
||||||
sortSelectionRadioGroupView.check(mCheckedId)
|
sortSelectionRadioGroupView.check(mCheckedId)
|
||||||
sortSelectionRadioGroupView.setOnCheckedChangeListener { _, checkedId -> mSortNodeEnum = retrieveSortEnumFromViewId(checkedId) }
|
sortSelectionRadioGroupView.setOnCheckedChangeListener { _, checkedId ->
|
||||||
|
mSortNodeEnum = retrieveSortEnumFromViewId(checkedId)
|
||||||
|
disableRecycleBinBottomOptionIfNaturalOrder()
|
||||||
|
}
|
||||||
|
|
||||||
return builder.create()
|
return builder.create()
|
||||||
}
|
}
|
||||||
return super.onCreateDialog(savedInstanceState)
|
return super.onCreateDialog(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun disableRecycleBinBottomOptionIfNaturalOrder() {
|
||||||
|
// Disable recycle bin if natural order
|
||||||
|
recycleBinBottomView?.isEnabled = mSortNodeEnum != SortNodeEnum.DB
|
||||||
|
}
|
||||||
|
|
||||||
@IdRes
|
@IdRes
|
||||||
private fun retrieveViewFromEnum(sortNodeEnum: SortNodeEnum): Int {
|
private fun retrieveViewFromEnum(sortNodeEnum: SortNodeEnum): Int {
|
||||||
return when (sortNodeEnum) {
|
return when (sortNodeEnum) {
|
||||||
@@ -138,10 +152,7 @@ class SortDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface SortSelectionListener {
|
interface SortSelectionListener {
|
||||||
fun onSortSelected(sortNodeEnum: SortNodeEnum,
|
fun onSortSelected(sortNodeEnum: SortNodeEnum, sortNodeParameters: SortNodeEnum.SortNodeParameters)
|
||||||
ascending: Boolean,
|
|
||||||
groupsBefore: Boolean,
|
|
||||||
recycleBinBottom: Boolean)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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.
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
@@ -22,12 +22,12 @@ package com.kunzisoft.keepass.activities.dialogs
|
|||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import android.support.v7.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import android.text.Html
|
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
|
|
||||||
class UnavailableFeatureDialogFragment : DialogFragment() {
|
class UnavailableFeatureDialogFragment : DialogFragment() {
|
||||||
@@ -53,7 +53,7 @@ class UnavailableFeatureDialogFragment : DialogFragment() {
|
|||||||
androidNameFromApiNumber(Build.VERSION.SDK_INT, Build.VERSION.RELEASE),
|
androidNameFromApiNumber(Build.VERSION.SDK_INT, Build.VERSION.RELEASE),
|
||||||
androidNameFromApiNumber(minVersionRequired)))
|
androidNameFromApiNumber(minVersionRequired)))
|
||||||
message.append("\n\n")
|
message.append("\n\n")
|
||||||
.append(Html.fromHtml("<a href=\"https://source.android.com/setup/build-numbers\">CodeNames</a>"))
|
.append(HtmlCompat.fromHtml("<a href=\"https://source.android.com/setup/build-numbers\">CodeNames</a>", HtmlCompat.FROM_HTML_MODE_LEGACY))
|
||||||
} else
|
} else
|
||||||
message.append(getString(R.string.unavailable_feature_hardware))
|
message.append(getString(R.string.unavailable_feature_hardware))
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,33 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.DialogFragment
|
|
||||||
import android.support.v7.app.AlertDialog
|
|
||||||
import android.text.Html
|
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.widget.Toast
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.kunzisoft.keepass.BuildConfig
|
import com.kunzisoft.keepass.BuildConfig
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.utils.Util
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom Dialog that asks the user to download the pro version or make a donation.
|
* Custom Dialog that asks the user to download the pro version or make a donation.
|
||||||
@@ -45,34 +42,26 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
|
|||||||
val stringBuilder = SpannableStringBuilder()
|
val stringBuilder = SpannableStringBuilder()
|
||||||
if (BuildConfig.CLOSED_STORE) {
|
if (BuildConfig.CLOSED_STORE) {
|
||||||
if (BuildConfig.FULL_VERSION) {
|
if (BuildConfig.FULL_VERSION) {
|
||||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature_thanks))).append("\n\n")
|
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_thanks), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||||
.append(Html.fromHtml(getString(R.string.html_rose))).append("\n\n")
|
.append(HtmlCompat.fromHtml(getString(R.string.html_rose), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_work_hard))).append("\n")
|
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_work_hard), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
|
||||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_upgrade))).append(" ")
|
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_upgrade), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
|
||||||
builder.setPositiveButton(android.R.string.ok) { _, _ -> dismiss() }
|
builder.setPositiveButton(android.R.string.ok) { _, _ -> dismiss() }
|
||||||
} else {
|
} else {
|
||||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature))).append("\n\n")
|
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_buy_pro))).append("\n")
|
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_buy_pro), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
|
||||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_encourage)))
|
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
|
||||||
builder.setPositiveButton(R.string.download) { _, _ ->
|
builder.setPositiveButton(R.string.download) { _, _ ->
|
||||||
try {
|
UriUtil.gotoUrl(requireContext(), R.string.app_pro_url)
|
||||||
Util.gotoUrl(context!!, R.string.app_pro_url)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
|
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature))).append("\n\n")
|
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_contibute))).append(" ")
|
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
|
||||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_encourage)))
|
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
|
||||||
builder.setPositiveButton(R.string.contribute) { _, _ ->
|
builder.setPositiveButton(R.string.contribute) { _, _ ->
|
||||||
try {
|
UriUtil.gotoUrl(requireContext(), R.string.contribution_url)
|
||||||
Util.gotoUrl(context!!, R.string.contribution_url)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
|
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.activities.helpers;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
public class ClipDataCompat {
|
|
||||||
private static Method getClipDataFromIntent;
|
|
||||||
private static Method getDescription;
|
|
||||||
private static Method getItemCount;
|
|
||||||
private static Method getLabel;
|
|
||||||
private static Method getItemAt;
|
|
||||||
private static Method getUri;
|
|
||||||
|
|
||||||
private static boolean initSucceded;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
Class clipData = Class.forName("android.content.ClipData");
|
|
||||||
getDescription = clipData.getMethod("getDescription", (Class[])null);
|
|
||||||
getItemCount = clipData.getMethod("getItemCount", (Class[])null);
|
|
||||||
getItemAt = clipData.getMethod("getItemAt", new Class[]{int.class});
|
|
||||||
Class clipDescription = Class.forName("android.content.ClipDescription");
|
|
||||||
getLabel = clipDescription.getMethod("getLabel", (Class[])null);
|
|
||||||
|
|
||||||
Class clipDataItem = Class.forName("android.content.ClipData$Item");
|
|
||||||
getUri = clipDataItem.getMethod("getUri", (Class[])null);
|
|
||||||
|
|
||||||
getClipDataFromIntent = Intent.class.getMethod("getClipData", (Class[])null);
|
|
||||||
|
|
||||||
initSucceded = true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
initSucceded = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Uri getUriFromIntent(Intent i, String key) {
|
|
||||||
if (initSucceded) {
|
|
||||||
try {
|
|
||||||
Object clip = getClipDataFromIntent.invoke(i);
|
|
||||||
|
|
||||||
if (clip != null) {
|
|
||||||
Object clipDescription = getDescription.invoke(clip);
|
|
||||||
CharSequence label = (CharSequence)getLabel.invoke(clipDescription);
|
|
||||||
if (label.equals(key)) {
|
|
||||||
int itemCount = (int) getItemCount.invoke(clip);
|
|
||||||
if (itemCount == 1) {
|
|
||||||
Object clipItem = getItemAt.invoke(clip,0);
|
|
||||||
if (clipItem != null) {
|
|
||||||
return (Uri)getUri.invoke(clipItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Fall through below to backup method if reflection fails
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return i.getParcelableExtra(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
package com.kunzisoft.keepass.activities.helpers
|
package com.kunzisoft.keepass.activities.helpers
|
||||||
|
|
||||||
import android.app.assist.AssistStructure
|
import android.app.assist.AssistStructure
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
@@ -10,6 +30,12 @@ object EntrySelectionHelper {
|
|||||||
private const val EXTRA_ENTRY_SELECTION_MODE = "com.kunzisoft.keepass.extra.ENTRY_SELECTION_MODE"
|
private const val EXTRA_ENTRY_SELECTION_MODE = "com.kunzisoft.keepass.extra.ENTRY_SELECTION_MODE"
|
||||||
private const val DEFAULT_ENTRY_SELECTION_MODE = false
|
private const val DEFAULT_ENTRY_SELECTION_MODE = false
|
||||||
|
|
||||||
|
fun startActivityForEntrySelection(context: Context, intent: Intent) {
|
||||||
|
addEntrySelectionModeExtraInIntent(intent)
|
||||||
|
// only to avoid visible flickering when redirecting
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
fun addEntrySelectionModeExtraInIntent(intent: Intent) {
|
fun addEntrySelectionModeExtraInIntent(intent: Intent) {
|
||||||
intent.putExtra(EXTRA_ENTRY_SELECTION_MODE, true)
|
intent.putExtra(EXTRA_ENTRY_SELECTION_MODE, true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities.helpers
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Activity.RESULT_OK
|
import android.app.Activity.RESULT_OK
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -26,21 +27,20 @@ import android.content.Intent
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.support.v4.app.Fragment
|
|
||||||
import android.support.v4.app.FragmentActivity
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.kunzisoft.keepass.fileselect.StorageAF
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
|
|
||||||
class KeyFileHelper {
|
class OpenFileHelper {
|
||||||
|
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
private var fragment: Fragment? = null
|
private var fragment: Fragment? = null
|
||||||
|
|
||||||
val openFileOnClickViewListener: OpenFileOnClickViewListener
|
val openFileOnClickViewListener: OpenFileOnClickViewListener
|
||||||
get() = OpenFileOnClickViewListener(null)
|
get() = OpenFileOnClickViewListener()
|
||||||
|
|
||||||
constructor(context: Activity) {
|
constructor(context: Activity) {
|
||||||
this.activity = context
|
this.activity = context
|
||||||
@@ -52,69 +52,78 @@ class KeyFileHelper {
|
|||||||
this.fragment = context
|
this.fragment = context
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class OpenFileOnClickViewListener(private val dataUri: (() -> Uri)?) : View.OnClickListener {
|
inner class OpenFileOnClickViewListener : View.OnClickListener, View.OnLongClickListener {
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
private fun onAbstractClick(longClick: Boolean = false) {
|
||||||
try {
|
try {
|
||||||
if (activity != null && StorageAF.useStorageFramework(activity!!)) {
|
if (longClick) {
|
||||||
openActivityWithActionOpenDocument()
|
try {
|
||||||
|
openActivityWithActionGetContent()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
openActivityWithActionOpenDocument()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
openActivityWithActionGetContent()
|
try {
|
||||||
|
openActivityWithActionOpenDocument()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
openActivityWithActionGetContent()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Enable to start the file picker activity", e)
|
Log.e(TAG, "Enable to start the file picker activity", e)
|
||||||
|
// Open browser dialog
|
||||||
// Open File picker if can't open activity
|
if (lookForOpenIntentsFilePicker())
|
||||||
if (lookForOpenIntentsFilePicker(dataUri?.invoke()))
|
|
||||||
showBrowserDialog()
|
showBrowserDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onClick(v: View) {
|
||||||
|
onAbstractClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLongClick(v: View?): Boolean {
|
||||||
|
onAbstractClick(true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
private fun openActivityWithActionOpenDocument() {
|
private fun openActivityWithActionOpenDocument() {
|
||||||
val i = Intent(StorageAF.ACTION_OPEN_DOCUMENT)
|
val intentOpenDocument = Intent(APP_ACTION_OPEN_DOCUMENT).apply {
|
||||||
i.addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
i.type = "*/*"
|
type = "*/*"
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
|
||||||
i.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
|
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
} else {
|
|
||||||
i.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
||||||
}
|
}
|
||||||
if (fragment != null)
|
if (fragment != null)
|
||||||
fragment?.startActivityForResult(i, OPEN_DOC)
|
fragment?.startActivityForResult(intentOpenDocument, OPEN_DOC)
|
||||||
else
|
else
|
||||||
activity?.startActivityForResult(i, OPEN_DOC)
|
activity?.startActivityForResult(intentOpenDocument, OPEN_DOC)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
private fun openActivityWithActionGetContent() {
|
private fun openActivityWithActionGetContent() {
|
||||||
val i = Intent(Intent.ACTION_GET_CONTENT)
|
val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||||
i.addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
i.type = "*/*"
|
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)
|
if (fragment != null)
|
||||||
fragment?.startActivityForResult(i, GET_CONTENT)
|
fragment?.startActivityForResult(intentGetContent, GET_CONTENT)
|
||||||
else
|
else
|
||||||
activity?.startActivityForResult(i, GET_CONTENT)
|
activity?.startActivityForResult(intentGetContent, GET_CONTENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOpenFileOnClickViewListener(dataUri: () -> Uri): OpenFileOnClickViewListener {
|
private fun lookForOpenIntentsFilePicker(): Boolean {
|
||||||
return OpenFileOnClickViewListener(dataUri)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun lookForOpenIntentsFilePicker(dataUri: Uri?): Boolean {
|
|
||||||
var showBrowser = false
|
var showBrowser = false
|
||||||
try {
|
try {
|
||||||
if (isIntentAvailable(activity!!, OPEN_INTENTS_FILE_BROWSE)) {
|
if (isIntentAvailable(activity!!, OPEN_INTENTS_FILE_BROWSE)) {
|
||||||
val intent = Intent(OPEN_INTENTS_FILE_BROWSE)
|
val intent = Intent(OPEN_INTENTS_FILE_BROWSE)
|
||||||
// Get file path parent if possible
|
|
||||||
if (dataUri != null
|
|
||||||
&& dataUri.toString().isNotEmpty()
|
|
||||||
&& dataUri.scheme == "file") {
|
|
||||||
intent.data = dataUri
|
|
||||||
} else {
|
|
||||||
Log.w(javaClass.name, "Unable to read the URI")
|
|
||||||
}
|
|
||||||
if (fragment != null)
|
if (fragment != null)
|
||||||
fragment?.startActivityForResult(intent, FILE_BROWSE)
|
fragment?.startActivityForResult(intent, FILE_BROWSE)
|
||||||
else
|
else
|
||||||
@@ -155,11 +164,10 @@ class KeyFileHelper {
|
|||||||
*/
|
*/
|
||||||
private fun showBrowserDialog() {
|
private fun showBrowserDialog() {
|
||||||
try {
|
try {
|
||||||
val browserDialogFragment = BrowserDialogFragment()
|
val fileManagerDialogFragment = FileManagerDialogFragment()
|
||||||
if (fragment != null && fragment!!.fragmentManager != null)
|
fragment?.let {
|
||||||
browserDialogFragment.show(fragment!!.fragmentManager!!, "browserDialog")
|
fileManagerDialogFragment.show(it.parentFragmentManager, "browserDialog")
|
||||||
else if (activity!!.fragmentManager != null)
|
} ?: fileManagerDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
|
||||||
browserDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Can't open BrowserDialog", e)
|
Log.e(TAG, "Can't open BrowserDialog", e)
|
||||||
}
|
}
|
||||||
@@ -182,7 +190,7 @@ class KeyFileHelper {
|
|||||||
val filename = data?.dataString
|
val filename = data?.dataString
|
||||||
var keyUri: Uri? = null
|
var keyUri: Uri? = null
|
||||||
if (filename != null) {
|
if (filename != null) {
|
||||||
keyUri = UriUtil.parseUriFile(filename)
|
keyUri = UriUtil.parse(filename)
|
||||||
}
|
}
|
||||||
keyFileCallback?.invoke(keyUri)
|
keyFileCallback?.invoke(keyUri)
|
||||||
}
|
}
|
||||||
@@ -191,23 +199,18 @@ class KeyFileHelper {
|
|||||||
GET_CONTENT, OPEN_DOC -> {
|
GET_CONTENT, OPEN_DOC -> {
|
||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
var uri = data.data
|
val uri = data.data
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
if (StorageAF.useStorageFramework(activity!!)) {
|
try {
|
||||||
try {
|
// try to persist read and write permissions
|
||||||
// try to persist read and write permissions
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
activity?.contentResolver?.apply {
|
||||||
activity?.contentResolver?.apply {
|
takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
takePersistableUriPermission(uri!!, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
takePersistableUriPermission(uri!!, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
// nop
|
|
||||||
}
|
}
|
||||||
}
|
} catch (e: Exception) {
|
||||||
if (requestCode == GET_CONTENT) {
|
// nop
|
||||||
uri = UriUtil.translateUri(activity!!, uri)
|
|
||||||
}
|
}
|
||||||
keyFileCallback?.invoke(uri)
|
keyFileCallback?.invoke(uri)
|
||||||
}
|
}
|
||||||
@@ -221,7 +224,13 @@ class KeyFileHelper {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val TAG = "KeyFileHelper"
|
private const val TAG = "OpenFileHelper"
|
||||||
|
|
||||||
|
private var APP_ACTION_OPEN_DOCUMENT: String = try {
|
||||||
|
Intent::class.java.getField("ACTION_OPEN_DOCUMENT").get(null) as String
|
||||||
|
} catch (e: Exception) {
|
||||||
|
"android.intent.action.OPEN_DOCUMENT"
|
||||||
|
}
|
||||||
|
|
||||||
const val OPEN_INTENTS_FILE_BROWSE = "org.openintents.action.PICK_FILE"
|
const val OPEN_INTENTS_FILE_BROWSE = "org.openintents.action.PICK_FILE"
|
||||||
|
|
||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
package com.kunzisoft.keepass.activities.helpers
|
package com.kunzisoft.keepass.activities.helpers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
package com.kunzisoft.keepass.activities.helpers
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.AsyncTask
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R
|
|
||||||
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
|
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
import java.lang.ref.WeakReference
|
|
||||||
|
|
||||||
class UriIntentInitTask(private val weakContext: WeakReference<Context>,
|
|
||||||
private val uriIntentInitTaskCallback: UriIntentInitTaskCallback,
|
|
||||||
private val isKeyFileNeeded: Boolean)
|
|
||||||
: AsyncTask<Intent, Void, Int>() {
|
|
||||||
|
|
||||||
private var databaseUri: Uri? = null
|
|
||||||
private var keyFileUri: Uri? = null
|
|
||||||
|
|
||||||
override fun doInBackground(vararg args: Intent): Int? {
|
|
||||||
val intent = args[0]
|
|
||||||
val action = intent.action
|
|
||||||
|
|
||||||
// If is a view intent
|
|
||||||
if (action != null && action == VIEW_INTENT) {
|
|
||||||
val incoming = intent.data
|
|
||||||
databaseUri = incoming
|
|
||||||
keyFileUri = ClipDataCompat.getUriFromIntent(intent, KEY_KEYFILE)
|
|
||||||
|
|
||||||
if (incoming == null) {
|
|
||||||
return R.string.error_can_not_handle_uri
|
|
||||||
} else if (incoming.scheme == "file") {
|
|
||||||
val fileName = incoming.path
|
|
||||||
|
|
||||||
if (fileName?.isNotEmpty() == true) {
|
|
||||||
// No file name
|
|
||||||
return R.string.file_not_found
|
|
||||||
}
|
|
||||||
|
|
||||||
val dbFile = File(fileName)
|
|
||||||
if (!dbFile.exists()) {
|
|
||||||
// File does not exist
|
|
||||||
return R.string.file_not_found
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyFileUri == null) {
|
|
||||||
keyFileUri = getKeyFileUri(databaseUri)
|
|
||||||
}
|
|
||||||
} else if (incoming.scheme == "content") {
|
|
||||||
if (keyFileUri == null) {
|
|
||||||
keyFileUri = getKeyFileUri(databaseUri)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return R.string.error_can_not_handle_uri
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
databaseUri = UriUtil.parseUriFile(intent.getStringExtra(KEY_FILENAME))
|
|
||||||
keyFileUri = UriUtil.parseUriFile(intent.getStringExtra(KEY_KEYFILE))
|
|
||||||
|
|
||||||
if (keyFileUri == null || keyFileUri!!.toString().isEmpty()) {
|
|
||||||
keyFileUri = getKeyFileUri(databaseUri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
public override fun onPostExecute(result: Int?) {
|
|
||||||
uriIntentInitTaskCallback.onPostInitTask(databaseUri, keyFileUri, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getKeyFileUri(databaseUri: Uri?): Uri? {
|
|
||||||
return if (isKeyFileNeeded) {
|
|
||||||
FileDatabaseHistory.getInstance(weakContext).getKeyFileUriByDatabaseUri(databaseUri!!)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val KEY_FILENAME = "fileName"
|
|
||||||
const val KEY_KEYFILE = "keyFile"
|
|
||||||
private const val VIEW_INTENT = "android.intent.action.VIEW"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.activities.helpers
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
|
|
||||||
interface UriIntentInitTaskCallback {
|
|
||||||
fun onPostInitTask(databaseFileUri: Uri?, keyFileUri: Uri?, errorStringId: Int?)
|
|
||||||
}
|
|
||||||
@@ -1,30 +1,25 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
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.Intent
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -32,12 +27,11 @@ import android.view.ViewGroup
|
|||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
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.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.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
import com.kunzisoft.keepass.utils.*
|
||||||
|
|
||||||
abstract class LockingActivity : StylishActivity() {
|
abstract class LockingActivity : StylishActivity() {
|
||||||
|
|
||||||
@@ -51,46 +45,53 @@ abstract class LockingActivity : StylishActivity() {
|
|||||||
const val TIMEOUT_ENABLE_KEY_DEFAULT = true
|
const val TIMEOUT_ENABLE_KEY_DEFAULT = true
|
||||||
}
|
}
|
||||||
|
|
||||||
protected var timeoutEnable: Boolean = true
|
protected var mTimeoutEnable: Boolean = true
|
||||||
|
|
||||||
private var lockReceiver: LockReceiver? = null
|
private var mLockReceiver: LockReceiver? = null
|
||||||
private var exitLock: Boolean = false
|
private var mExitLock: Boolean = false
|
||||||
|
|
||||||
// Force readOnly if Entry Selection mode
|
// Force readOnly if Entry Selection mode
|
||||||
protected var readOnly: Boolean = false
|
protected var mReadOnly: Boolean = false
|
||||||
get() {
|
get() {
|
||||||
return field || selectionMode
|
return field || mSelectionMode
|
||||||
}
|
}
|
||||||
protected var selectionMode: Boolean = false
|
protected var mSelectionMode: Boolean = false
|
||||||
|
protected var mAutoSaveEnable: Boolean = true
|
||||||
|
|
||||||
|
var mProgressDialogThread: ProgressDialogThread? = null
|
||||||
|
private set
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
if (savedInstanceState != null
|
if (savedInstanceState != null
|
||||||
&& savedInstanceState.containsKey(TIMEOUT_ENABLE_KEY)) {
|
&& savedInstanceState.containsKey(TIMEOUT_ENABLE_KEY)) {
|
||||||
timeoutEnable = savedInstanceState.getBoolean(TIMEOUT_ENABLE_KEY)
|
mTimeoutEnable = savedInstanceState.getBoolean(TIMEOUT_ENABLE_KEY)
|
||||||
} else {
|
} else {
|
||||||
if (intent != null)
|
if (intent != null)
|
||||||
timeoutEnable = intent.getBooleanExtra(TIMEOUT_ENABLE_KEY, TIMEOUT_ENABLE_KEY_DEFAULT)
|
mTimeoutEnable = intent.getBooleanExtra(TIMEOUT_ENABLE_KEY, TIMEOUT_ENABLE_KEY_DEFAULT)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeoutEnable) {
|
if (mTimeoutEnable) {
|
||||||
lockReceiver = LockReceiver()
|
mLockReceiver = LockReceiver {
|
||||||
val intentFilter = IntentFilter().apply {
|
closeDatabase()
|
||||||
addAction(Intent.ACTION_SCREEN_OFF)
|
// Add onActivityForResult response
|
||||||
addAction(LOCK_ACTION)
|
setResult(RESULT_EXIT_LOCK)
|
||||||
|
finish()
|
||||||
}
|
}
|
||||||
registerReceiver(lockReceiver, intentFilter)
|
registerLockReceiver(mLockReceiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
exitLock = false
|
mExitLock = false
|
||||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent)
|
mReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent)
|
||||||
|
|
||||||
|
mProgressDialogThread = ProgressDialogThread(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
if (resultCode == RESULT_EXIT_LOCK) {
|
if (resultCode == RESULT_EXIT_LOCK) {
|
||||||
exitLock = true
|
mExitLock = true
|
||||||
if (Database.getInstance().loaded) {
|
if (Database.getInstance().loaded) {
|
||||||
lockAndExit()
|
lockAndExit()
|
||||||
}
|
}
|
||||||
@@ -100,10 +101,15 @@ abstract class LockingActivity : StylishActivity() {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
// To refresh when back to normal workflow from selection workflow
|
mProgressDialogThread?.registerProgressTask()
|
||||||
selectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent)
|
|
||||||
|
|
||||||
if (timeoutEnable) {
|
// To refresh when back to normal workflow from selection workflow
|
||||||
|
mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent)
|
||||||
|
mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this)
|
||||||
|
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
|
||||||
|
if (mTimeoutEnable) {
|
||||||
// End activity if database not loaded
|
// End activity if database not loaded
|
||||||
if (!Database.getInstance().loaded) {
|
if (!Database.getInstance().loaded) {
|
||||||
finish()
|
finish()
|
||||||
@@ -115,53 +121,35 @@ abstract class LockingActivity : StylishActivity() {
|
|||||||
// If the time is out -> close the Activity
|
// If the time is out -> close the Activity
|
||||||
TimeoutHelper.checkTimeAndLockIfTimeout(this)
|
TimeoutHelper.checkTimeAndLockIfTimeout(this)
|
||||||
// If onCreate already record time
|
// If onCreate already record time
|
||||||
if (!exitLock)
|
if (!mExitLock)
|
||||||
TimeoutHelper.recordTime(this)
|
TimeoutHelper.recordTime(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidateOptionsMenu()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
|
ReadOnlyHelper.onSaveInstanceState(outState, mReadOnly)
|
||||||
outState.putBoolean(TIMEOUT_ENABLE_KEY, timeoutEnable)
|
outState.putBoolean(TIMEOUT_ENABLE_KEY, mTimeoutEnable)
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
mProgressDialogThread?.unregisterProgressTask()
|
||||||
|
|
||||||
super.onPause()
|
super.onPause()
|
||||||
|
|
||||||
if (timeoutEnable) {
|
if (mTimeoutEnable) {
|
||||||
// If the time is out during our navigation in activity -> close the Activity
|
// If the time is out during our navigation in activity -> close the Activity
|
||||||
TimeoutHelper.checkTimeAndLockIfTimeout(this)
|
TimeoutHelper.checkTimeAndLockIfTimeout(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
unregisterLockReceiver(mLockReceiver)
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
if (lockReceiver != null)
|
|
||||||
unregisterReceiver(lockReceiver)
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
protected fun lockAndExit() {
|
||||||
lock()
|
sendBroadcast(Intent(LOCK_ACTION))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -184,7 +172,7 @@ abstract class LockingActivity : StylishActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (timeoutEnable) {
|
if (mTimeoutEnable) {
|
||||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
|
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
@@ -193,20 +181,3 @@ abstract class LockingActivity : StylishActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity.lock() {
|
|
||||||
// Stop the Magikeyboard service
|
|
||||||
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
|
|
||||||
MagikIME.removeEntry(this)
|
|
||||||
|
|
||||||
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,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.activities.stylish
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.support.annotation.StyleRes
|
|
||||||
import android.util.Log
|
|
||||||
|
|
||||||
import com.nononsenseapps.filepicker.FilePickerActivity
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FilePickerActivity class with a style compatibility
|
|
||||||
*/
|
|
||||||
class FilePickerStylishActivity : FilePickerActivity() {
|
|
||||||
|
|
||||||
@StyleRes
|
|
||||||
private var themeId: Int = 0
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
this.themeId = Stylish.getFilePickerThemeId(this)
|
|
||||||
setTheme(themeId)
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
if (Stylish.getFilePickerThemeId(this) != this.themeId) {
|
|
||||||
Log.d(this.javaClass.name, "Theme change detected, restarting activity")
|
|
||||||
this.recreate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +1,27 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities.stylish
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.support.annotation.StyleRes
|
import androidx.annotation.StyleRes
|
||||||
import android.support.v7.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
@@ -61,6 +61,7 @@ object Stylish {
|
|||||||
|
|
||||||
return when (themeString) {
|
return when (themeString) {
|
||||||
context.getString(R.string.list_style_name_night) -> R.style.KeepassDXStyle_Night
|
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_dark) -> R.style.KeepassDXStyle_Dark
|
||||||
context.getString(R.string.list_style_name_blue) -> R.style.KeepassDXStyle_Blue
|
context.getString(R.string.list_style_name_blue) -> R.style.KeepassDXStyle_Blue
|
||||||
context.getString(R.string.list_style_name_red) -> R.style.KeepassDXStyle_Red
|
context.getString(R.string.list_style_name_red) -> R.style.KeepassDXStyle_Red
|
||||||
@@ -68,15 +69,4 @@ object Stylish {
|
|||||||
else -> R.style.KeepassDXStyle_Light
|
else -> R.style.KeepassDXStyle_Light
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@StyleRes
|
|
||||||
fun getFilePickerThemeId(context: Context): Int {
|
|
||||||
return when {
|
|
||||||
themeString.equals(context.getString(R.string.list_style_name_dark)) -> R.style.KeepassDXStyle_FilePickerStyle_Dark
|
|
||||||
themeString.equals(context.getString(R.string.list_style_name_blue)) -> R.style.KeepassDXStyle_FilePickerStyle_Blue
|
|
||||||
themeString.equals(context.getString(R.string.list_style_name_red)) -> R.style.KeepassDXStyle_FilePickerStyle_Red
|
|
||||||
themeString.equals(context.getString(R.string.list_style_name_purple)) -> R.style.KeepassDXStyle_FilePickerStyle_Purple
|
|
||||||
else -> R.style.KeepassDXStyle_FilePickerStyle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,64 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities.stylish
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.annotation.StyleRes
|
import androidx.annotation.StyleRes
|
||||||
import android.support.v7.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.util.Log
|
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() {
|
abstract class StylishActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@StyleRes
|
@StyleRes
|
||||||
private var themeId: Int = 0
|
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 {
|
||||||
|
intent.component?.let {
|
||||||
|
if (it.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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
this.themeId = Stylish.getThemeId(this)
|
this.themeId = Stylish.getThemeId(this)
|
||||||
setTheme(themeId)
|
setTheme(themeId)
|
||||||
|
|
||||||
|
// Several gingerbread devices have problems with FLAG_SECURE
|
||||||
|
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.activities.stylish
|
||||||
@@ -23,9 +23,9 @@ import android.content.Context
|
|||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.annotation.StyleRes
|
import androidx.annotation.StyleRes
|
||||||
import android.support.v4.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import android.support.v7.view.ContextThemeWrapper
|
import androidx.appcompat.view.ContextThemeWrapper
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -36,18 +36,16 @@ abstract class StylishFragment : Fragment() {
|
|||||||
protected var themeId: Int = 0
|
protected var themeId: Int = 0
|
||||||
protected var contextThemed: Context? = null
|
protected var contextThemed: Context? = null
|
||||||
|
|
||||||
override fun onAttach(context: Context?) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
if (context != null) {
|
this.themeId = Stylish.getThemeId(context)
|
||||||
this.themeId = Stylish.getThemeId(context)
|
|
||||||
}
|
|
||||||
contextThemed = ContextThemeWrapper(context, themeId)
|
contextThemed = ContextThemeWrapper(context, themeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
// To fix status bar color
|
// To fix status bar color
|
||||||
if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
val window = activity!!.window
|
val window = requireActivity().window
|
||||||
|
|
||||||
val attrColorPrimaryDark = intArrayOf(android.R.attr.colorPrimaryDark)
|
val attrColorPrimaryDark = intArrayOf(android.R.attr.colorPrimaryDark)
|
||||||
val taColorPrimaryDark = contextThemed?.theme?.obtainStyledAttributes(attrColorPrimaryDark)
|
val taColorPrimaryDark = contextThemed?.theme?.obtainStyledAttributes(attrColorPrimaryDark)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.adapters
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
|
|
||||||
|
class EntryHistoryAdapter(val context: Context) : RecyclerView.Adapter<EntryHistoryAdapter.EntryHistoryViewHolder>() {
|
||||||
|
|
||||||
|
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
|
var entryHistoryList: MutableList<Entry> = ArrayList()
|
||||||
|
var onItemClickListener: ((item: Entry, position: Int)->Unit)? = null
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryHistoryViewHolder {
|
||||||
|
return EntryHistoryViewHolder(inflater.inflate(R.layout.item_list_entry_history, parent, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: EntryHistoryViewHolder, position: Int) {
|
||||||
|
val entryHistory = entryHistoryList[position]
|
||||||
|
|
||||||
|
holder.lastModifiedView.text = entryHistory.lastModificationTime.getDateTimeString(context.resources)
|
||||||
|
holder.titleView.text = entryHistory.title
|
||||||
|
holder.usernameView.text = entryHistory.username
|
||||||
|
holder.urlView.text = entryHistory.url
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
onItemClickListener?.invoke(entryHistory, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return entryHistoryList.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
entryHistoryList.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class EntryHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
||||||
|
var lastModifiedView: TextView = itemView.findViewById(R.id.entry_history_last_modified)
|
||||||
|
var titleView: TextView = itemView.findViewById(R.id.entry_history_title)
|
||||||
|
var usernameView: TextView = itemView.findViewById(R.id.entry_history_username)
|
||||||
|
var urlView: TextView = itemView.findViewById(R.id.entry_history_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,26 @@
|
|||||||
package com.kunzisoft.keepass.magikeyboard.adapter
|
/*
|
||||||
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.adapters
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.support.v7.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -15,10 +34,9 @@ import java.util.ArrayList
|
|||||||
class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.FieldViewHolder>() {
|
class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.FieldViewHolder>() {
|
||||||
|
|
||||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
var fields: MutableList<Field> = ArrayList()
|
private var fields: MutableList<Field> = ArrayList()
|
||||||
var onItemClickListener: OnItemClickListener? = null
|
var onItemClickListener: OnItemClickListener? = null
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder {
|
||||||
val view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false)
|
val view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false)
|
||||||
return FieldViewHolder(view)
|
return FieldViewHolder(view)
|
||||||
@@ -34,6 +52,11 @@ class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.Field
|
|||||||
return fields.size
|
return fields.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setFields(fieldsToAdd: List<Field>) {
|
||||||
|
fields.clear()
|
||||||
|
fields.addAll(fieldsToAdd)
|
||||||
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
fields.clear()
|
fields.clear()
|
||||||
}
|
}
|
||||||
@@ -1,43 +1,52 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* 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
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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
|
package com.kunzisoft.keepass.adapters
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.graphics.Color
|
||||||
import android.support.annotation.ColorInt
|
import android.graphics.PorterDuff
|
||||||
import android.support.v7.widget.RecyclerView
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.*
|
import android.view.*
|
||||||
|
import android.widget.EditText
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import android.widget.ViewSwitcher
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.fileselect.FileDatabaseModel
|
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryEntity
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.utils.FileDatabaseInfo
|
||||||
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
|
|
||||||
class FileDatabaseHistoryAdapter(private val context: Context, private val listFiles: List<String>)
|
class FileDatabaseHistoryAdapter(private val context: Context)
|
||||||
: RecyclerView.Adapter<FileDatabaseHistoryAdapter.FileDatabaseHistoryViewHolder>() {
|
: RecyclerView.Adapter<FileDatabaseHistoryAdapter.FileDatabaseHistoryViewHolder>() {
|
||||||
|
|
||||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
private var fileItemOpenListener: FileItemOpenListener? = null
|
private var fileItemOpenListener: ((FileDatabaseHistoryEntity)->Unit)? = null
|
||||||
private var fileSelectClearListener: FileSelectClearListener? = null
|
private var fileSelectClearListener: ((FileDatabaseHistoryEntity)->Boolean)? = null
|
||||||
private var fileInformationShowListener: FileInformationShowListener? = null
|
private var saveAliasListener: ((FileDatabaseHistoryEntity)->Unit)? = null
|
||||||
|
|
||||||
|
private val listDatabaseFiles = ArrayList<FileDatabaseHistoryEntity>()
|
||||||
|
|
||||||
|
private var mExpandedPosition = -1
|
||||||
|
private var mPreviousExpandedPosition = -1
|
||||||
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
private val defaultColor: Int
|
private val defaultColor: Int
|
||||||
@@ -45,7 +54,6 @@ class FileDatabaseHistoryAdapter(private val context: Context, private val listF
|
|||||||
private val warningColor: Int
|
private val warningColor: Int
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
||||||
val typedValue = TypedValue()
|
val typedValue = TypedValue()
|
||||||
val theme = context.theme
|
val theme = context.theme
|
||||||
theme.resolveAttribute(R.attr.colorAccent, typedValue, true)
|
theme.resolveAttribute(R.attr.colorAccent, typedValue, true)
|
||||||
@@ -60,91 +68,135 @@ class FileDatabaseHistoryAdapter(private val context: Context, private val listF
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: FileDatabaseHistoryViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: FileDatabaseHistoryViewHolder, position: Int) {
|
||||||
val fileDatabaseModel = FileDatabaseModel(context, listFiles[position])
|
// Get info from position
|
||||||
// Context menu creation
|
val fileHistoryEntity = listDatabaseFiles[position]
|
||||||
holder.fileContainer.setOnCreateContextMenuListener(ContextMenuBuilder(fileDatabaseModel))
|
val fileDatabaseInfo = FileDatabaseInfo(context, fileHistoryEntity.databaseUri)
|
||||||
|
|
||||||
// Click item to open file
|
// Click item to open file
|
||||||
if (fileItemOpenListener != null)
|
if (fileItemOpenListener != null)
|
||||||
holder.fileContainer.setOnClickListener(FileItemClickListener(position))
|
holder.fileContainer.setOnClickListener {
|
||||||
// Assign file name
|
fileItemOpenListener?.invoke(fileHistoryEntity)
|
||||||
if (PreferencesUtil.isFullFilePathEnable(context))
|
}
|
||||||
holder.fileName.text = Uri.decode(fileDatabaseModel.fileUri.toString())
|
|
||||||
else
|
// File alias
|
||||||
holder.fileName.text = fileDatabaseModel.fileName
|
holder.fileAlias.text = fileDatabaseInfo.retrieveDatabaseAlias(fileHistoryEntity.databaseAlias)
|
||||||
holder.fileName.textSize = PreferencesUtil.getListTextSize(context)
|
|
||||||
|
// File path
|
||||||
|
holder.filePath.text = UriUtil.decode(fileDatabaseInfo.fileUri?.toString())
|
||||||
|
|
||||||
|
if (fileDatabaseInfo.exists) {
|
||||||
|
holder.fileInformation.clearColorFilter()
|
||||||
|
} else {
|
||||||
|
holder.fileInformation.setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Click on information
|
||||||
if (fileInformationShowListener != null)
|
val isExpanded = position == mExpandedPosition
|
||||||
holder.fileInformation.setOnClickListener(FileInformationClickListener(fileDatabaseModel))
|
//This line hides or shows the layout in question
|
||||||
|
holder.fileExpandContainer.visibility = if (isExpanded) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
|
// Save alias modification
|
||||||
|
holder.fileAliasCloseButton.setOnClickListener {
|
||||||
|
// Change the alias
|
||||||
|
fileHistoryEntity.databaseAlias = holder.fileAliasEdit.text.toString()
|
||||||
|
saveAliasListener?.invoke(fileHistoryEntity)
|
||||||
|
|
||||||
|
// Finish save mode
|
||||||
|
holder.fileMainSwitcher.showPrevious()
|
||||||
|
// Refresh current position to show alias
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open alias modification
|
||||||
|
holder.fileModifyButton.setOnClickListener {
|
||||||
|
holder.fileAliasEdit.setText(holder.fileAlias.text)
|
||||||
|
holder.fileMainSwitcher.showNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.fileDeleteButton.setOnClickListener {
|
||||||
|
fileSelectClearListener?.invoke(fileHistoryEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExpanded) {
|
||||||
|
mPreviousExpandedPosition = position
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.fileInformation.setOnClickListener {
|
||||||
|
mExpandedPosition = if (isExpanded) -1 else position
|
||||||
|
|
||||||
|
// Notify change
|
||||||
|
if (mPreviousExpandedPosition < itemCount)
|
||||||
|
notifyItemChanged(mPreviousExpandedPosition)
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh View / Close alias modification if not contains fileAlias
|
||||||
|
if (holder.fileMainSwitcher.currentView.findViewById<View>(R.id.file_alias)
|
||||||
|
!= holder.fileAlias)
|
||||||
|
holder.fileMainSwitcher.showPrevious()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return listFiles.size
|
return listDatabaseFiles.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setOnItemClickListener(fileItemOpenListener: FileItemOpenListener) {
|
fun clearDatabaseFileHistoryList() {
|
||||||
this.fileItemOpenListener = fileItemOpenListener
|
listDatabaseFiles.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setFileSelectClearListener(fileSelectClearListener: FileSelectClearListener) {
|
fun addDatabaseFileHistoryList(listFileDatabaseHistoryToAdd: List<FileDatabaseHistoryEntity>) {
|
||||||
this.fileSelectClearListener = fileSelectClearListener
|
listDatabaseFiles.clear()
|
||||||
|
listDatabaseFiles.addAll(listFileDatabaseHistoryToAdd)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setFileInformationShowListener(fileInformationShowListener: FileInformationShowListener) {
|
fun deleteDatabaseFileHistory(fileDatabaseHistoryToDelete: FileDatabaseHistoryEntity) {
|
||||||
this.fileInformationShowListener = fileInformationShowListener
|
listDatabaseFiles.remove(fileDatabaseHistoryToDelete)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FileItemOpenListener {
|
fun setOnFileDatabaseHistoryOpenListener(listener : ((FileDatabaseHistoryEntity)->Unit)?) {
|
||||||
fun onFileItemOpenListener(itemPosition: Int)
|
this.fileItemOpenListener = listener
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FileSelectClearListener {
|
fun setOnFileDatabaseHistoryDeleteListener(listener : ((FileDatabaseHistoryEntity)->Boolean)?) {
|
||||||
fun onFileSelectClearListener(fileDatabaseModel: FileDatabaseModel): Boolean
|
this.fileSelectClearListener = listener
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FileInformationShowListener {
|
fun setOnSaveAliasListener(listener : ((FileDatabaseHistoryEntity)->Unit)?) {
|
||||||
fun onClickFileInformation(fileDatabaseModel: FileDatabaseModel)
|
this.saveAliasListener = listener
|
||||||
}
|
|
||||||
|
|
||||||
private inner class FileItemClickListener(private val position: Int) : View.OnClickListener {
|
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
|
||||||
fileItemOpenListener?.onFileItemOpenListener(position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class FileInformationClickListener(private val fileDatabaseModel: FileDatabaseModel) : View.OnClickListener {
|
|
||||||
|
|
||||||
override fun onClick(view: View) {
|
|
||||||
fileInformationShowListener?.onClickFileInformation(fileDatabaseModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class ContextMenuBuilder(private val fileDatabaseModel: FileDatabaseModel) : View.OnCreateContextMenuListener {
|
|
||||||
|
|
||||||
private val mOnMyActionClickListener = MenuItem.OnMenuItemClickListener { item ->
|
|
||||||
if (fileSelectClearListener == null)
|
|
||||||
return@OnMenuItemClickListener false
|
|
||||||
when (item.itemId) {
|
|
||||||
MENU_CLEAR -> fileSelectClearListener!!.onFileSelectClearListener(fileDatabaseModel)
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateContextMenu(contextMenu: ContextMenu?, view: View?, contextMenuInfo: ContextMenu.ContextMenuInfo?) {
|
|
||||||
contextMenu?.add(Menu.NONE, MENU_CLEAR, Menu.NONE, R.string.remove_from_filelist)
|
|
||||||
?.setOnMenuItemClickListener(mOnMyActionClickListener)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class FileDatabaseHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
inner class FileDatabaseHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
||||||
var fileContainer: View = itemView.findViewById(R.id.file_container)
|
var fileContainer: ViewGroup = itemView.findViewById(R.id.file_container_basic_info)
|
||||||
var fileName: TextView = itemView.findViewById(R.id.file_filename)
|
|
||||||
|
var fileAlias: TextView = itemView.findViewById(R.id.file_alias)
|
||||||
var fileInformation: ImageView = itemView.findViewById(R.id.file_information)
|
var fileInformation: ImageView = itemView.findViewById(R.id.file_information)
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
var fileMainSwitcher: ViewSwitcher = itemView.findViewById(R.id.file_main_switcher)
|
||||||
|
var fileAliasEdit: EditText = itemView.findViewById(R.id.file_alias_edit)
|
||||||
|
var fileAliasCloseButton: ImageView = itemView.findViewById(R.id.file_alias_save)
|
||||||
|
|
||||||
private const val MENU_CLEAR = 1
|
var fileExpandContainer: ViewGroup = itemView.findViewById(R.id.file_expand_container)
|
||||||
|
var fileModifyButton: ImageView = itemView.findViewById(R.id.file_modify_button)
|
||||||
|
var fileDeleteButton: ImageView = itemView.findViewById(R.id.file_delete_button)
|
||||||
|
var filePath: TextView = itemView.findViewById(R.id.file_path)
|
||||||
|
var fileModification: TextView = itemView.findViewById(R.id.file_modification)
|
||||||
|
var fileSize: TextView = itemView.findViewById(R.id.file_size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user