Compare commits
584 Commits
manager-v3
...
manager-v5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d139e156e | ||
|
|
7c2849356a | ||
|
|
0025ffd1c0 | ||
|
|
2ef7146642 | ||
|
|
1b27e69e40 | ||
|
|
8e7b757efd | ||
|
|
1ab543cea1 | ||
|
|
a3f86903e4 | ||
|
|
c239c305ab | ||
|
|
2e02af994e | ||
|
|
836d9afe17 | ||
|
|
007a352742 | ||
|
|
e526e5659e | ||
|
|
4a5227c7bf | ||
|
|
c2c151ec4c | ||
|
|
452096e7e4 | ||
|
|
50c2a9859e | ||
|
|
677b667307 | ||
|
|
1adf331268 | ||
|
|
349b3e961b | ||
|
|
96650c06f0 | ||
|
|
26038a0a07 | ||
|
|
6a148b5dd9 | ||
|
|
0e109ef979 | ||
|
|
de2285d5e9 | ||
|
|
b2483ba437 | ||
|
|
a82a5e5a49 | ||
|
|
d161a02e71 | ||
|
|
d2b6a700b1 | ||
|
|
af203cef24 | ||
|
|
673e917e76 | ||
|
|
a3bd41db54 | ||
|
|
0d9527921a | ||
|
|
f0e4aec0af | ||
|
|
b0d65b5edd | ||
|
|
75532ef591 | ||
|
|
9a6d1bd700 | ||
|
|
a7ed6c15d3 | ||
|
|
5ee49ba065 | ||
|
|
d34bd47bea | ||
|
|
f17792380b | ||
|
|
c11920110e | ||
|
|
ec5a993fea | ||
|
|
d250c2cc89 | ||
|
|
767e73f40c | ||
|
|
3f699c9d2f | ||
|
|
50dbd9befd | ||
|
|
760e01bf92 | ||
|
|
543f435b1e | ||
|
|
91337218b3 | ||
|
|
afff3c0a49 | ||
|
|
a1871e4bc3 | ||
|
|
3aa0294cd4 | ||
|
|
310b266251 | ||
|
|
21b1b5098e | ||
|
|
a3a4a5d8a5 | ||
|
|
270536f33c | ||
|
|
66bb433cc6 | ||
|
|
bd4ef1a03a | ||
|
|
aa2d9a3bf1 | ||
|
|
fd6cbb138c | ||
|
|
aa75c8e5e4 | ||
|
|
c461fc6daa | ||
|
|
96eaa833f5 | ||
|
|
863b13a694 | ||
|
|
e6fea4e6dd | ||
|
|
83bfc13056 | ||
|
|
bc4f09209b | ||
|
|
967ca17238 | ||
|
|
595c72147c | ||
|
|
f3c3b5a649 | ||
|
|
1cd2c5e653 | ||
|
|
b2873dd44b | ||
|
|
bb80ab4026 | ||
|
|
80cabb338b | ||
|
|
2c69e2c151 | ||
|
|
c1dd23f5e0 | ||
|
|
f93624a41c | ||
|
|
9f4559a059 | ||
|
|
fd05cad303 | ||
|
|
d58b06e493 | ||
|
|
2f0b549027 | ||
|
|
87dbd7e541 | ||
|
|
96e5da36be | ||
|
|
43745edac0 | ||
|
|
f5ceee547c | ||
|
|
b612bce779 | ||
|
|
2e88e5e9c7 | ||
|
|
9a7aa25c90 | ||
|
|
c4420fe932 | ||
|
|
a5260f3a95 | ||
|
|
47ccf4b1f5 | ||
|
|
a356b21895 | ||
|
|
614a36c888 | ||
|
|
f520fe36bd | ||
|
|
7273a1c34d | ||
|
|
dc45cbce37 | ||
|
|
708d8f75c0 | ||
|
|
bd37d90228 | ||
|
|
b1ad691464 | ||
|
|
f4e7baf31e | ||
|
|
c0e60c41f2 | ||
|
|
c8dad43e00 | ||
|
|
a8f124704d | ||
|
|
eed2816491 | ||
|
|
a6334b3e35 | ||
|
|
334beebfeb | ||
|
|
13dad848bd | ||
|
|
e518f4cef8 | ||
|
|
c8fd5da2da | ||
|
|
3a74729ecc | ||
|
|
49c672ac4d | ||
|
|
b570cb5b77 | ||
|
|
97bf388471 | ||
|
|
1a32aaea6f | ||
|
|
4635883dec | ||
|
|
3ba6db4a50 | ||
|
|
2f1de25747 | ||
|
|
f60fd42ac0 | ||
|
|
ecc8f9c792 | ||
|
|
e295dfdcf7 | ||
|
|
fc42c25390 | ||
|
|
27d5858e06 | ||
|
|
e1ef732b60 | ||
|
|
9840b95c21 | ||
|
|
a6f8446d81 | ||
|
|
c1c844c830 | ||
|
|
389299afd1 | ||
|
|
826543a291 | ||
|
|
4ac83cfded | ||
|
|
64c363ce53 | ||
|
|
cca4347bf9 | ||
|
|
3ae3d4926a | ||
|
|
36025d6d9f | ||
|
|
e171362e3e | ||
|
|
3e0bf2ae15 | ||
|
|
07aa9f4b8b | ||
|
|
b2d9f3fc64 | ||
|
|
5fb3e9167e | ||
|
|
99c74b31be | ||
|
|
ce5b13824e | ||
|
|
c39170c42e | ||
|
|
fd19fbf300 | ||
|
|
166469827f | ||
|
|
a34ed538b6 | ||
|
|
5f22d3e055 | ||
|
|
fdd700f3e5 | ||
|
|
adf930f126 | ||
|
|
05f41928cd | ||
|
|
2ee0829871 | ||
|
|
743560825d | ||
|
|
e3d84ac349 | ||
|
|
266c832b30 | ||
|
|
f5374a024e | ||
|
|
4956d826fb | ||
|
|
f5cc2af5d0 | ||
|
|
5880d4a6ec | ||
|
|
ae05dce958 | ||
|
|
9ebe372a9a | ||
|
|
e6e04cc5b3 | ||
|
|
12352510fd | ||
|
|
2b3d927937 | ||
|
|
a8890740f5 | ||
|
|
f60d7ee54b | ||
|
|
896ca2ef6b | ||
|
|
c036f6d529 | ||
|
|
6f457c0c59 | ||
|
|
13bf1b27b4 | ||
|
|
f742bb1c47 | ||
|
|
aa0b9e2db2 | ||
|
|
c10076f7ed | ||
|
|
bcd92499f2 | ||
|
|
b2bb0d4f72 | ||
|
|
e140481f14 | ||
|
|
186bd11463 | ||
|
|
a0490d6687 | ||
|
|
beef740ade | ||
|
|
2ac7786a90 | ||
|
|
a3fb5e910f | ||
|
|
319afe86b5 | ||
|
|
762ab66b86 | ||
|
|
0c239a42de | ||
|
|
e9322fba26 | ||
|
|
39b6df27b3 | ||
|
|
b1ee284e7f | ||
|
|
e986332bf2 | ||
|
|
48f9b27381 | ||
|
|
42a6e0dd10 | ||
|
|
d4798b02ac | ||
|
|
963edfe8ab | ||
|
|
53237f3ae0 | ||
|
|
64da9281a4 | ||
|
|
ab7fd9799d | ||
|
|
f6bcc84251 | ||
|
|
35dc3d9df9 | ||
|
|
566714a75d | ||
|
|
c92f30b122 | ||
|
|
294ad094c4 | ||
|
|
c1a0f520f9 | ||
|
|
773c24b7fc | ||
|
|
8f926c7ca9 | ||
|
|
c562cbc2bb | ||
|
|
3fbbb0865a | ||
|
|
7d5f612a48 | ||
|
|
4a5a36440b | ||
|
|
43dd5cfea1 | ||
|
|
7b5fec1842 | ||
|
|
5762ded601 | ||
|
|
a3abb86daa | ||
|
|
4f5c656b05 | ||
|
|
a31cddbe7b | ||
|
|
b4ecd93f1c | ||
|
|
0acc23e058 | ||
|
|
cdd5f9b628 | ||
|
|
4c9f5f4655 | ||
|
|
b80ba13cb4 | ||
|
|
8260bdc09c | ||
|
|
24f856e02b | ||
|
|
3aa619b928 | ||
|
|
4cb5e98d94 | ||
|
|
272910575e | ||
|
|
a15a62f4bc | ||
|
|
53cf11db8c | ||
|
|
01052fbe47 | ||
|
|
a5e1e075c7 | ||
|
|
6be32ac688 | ||
|
|
b362c0ef38 | ||
|
|
bba9969e31 | ||
|
|
007ba24809 | ||
|
|
df21539311 | ||
|
|
2592cb6019 | ||
|
|
f7df17a7ed | ||
|
|
62f42b72f8 | ||
|
|
a1ba4fda6f | ||
|
|
1c06b04c45 | ||
|
|
2ee22fd374 | ||
|
|
4c230d9e61 | ||
|
|
727294fbbe | ||
|
|
478c43969b | ||
|
|
79b5303350 | ||
|
|
ce4b742b25 | ||
|
|
a9dc15bda5 | ||
|
|
ba6387ff5c | ||
|
|
8fa98508b7 | ||
|
|
decdbaecf9 | ||
|
|
6d87cf9be0 | ||
|
|
94f434c4a6 | ||
|
|
7ba867c30b | ||
|
|
3424395e10 | ||
|
|
926c7359a2 | ||
|
|
ec0af99a2e | ||
|
|
b4d948886c | ||
|
|
4d8d79372a | ||
|
|
04a589722c | ||
|
|
d4a10e2873 | ||
|
|
4998ad6c7e | ||
|
|
a07ca5ff50 | ||
|
|
f07e7571ab | ||
|
|
834c16485c | ||
|
|
04a4265ef3 | ||
|
|
0ec473195d | ||
|
|
0bf09256b0 | ||
|
|
db8fd2c913 | ||
|
|
dbe6e5b3d7 | ||
|
|
cc81cd446b | ||
|
|
439c7118f1 | ||
|
|
d8154a5815 | ||
|
|
4e3787bc0d | ||
|
|
02e0955924 | ||
|
|
a78950e822 | ||
|
|
1ce1a94a35 | ||
|
|
977b6d9f67 | ||
|
|
b5e6dbd797 | ||
|
|
833e6688f1 | ||
|
|
bc22c9f84f | ||
|
|
2149a7d116 | ||
|
|
29175d2c17 | ||
|
|
803454d5c8 | ||
|
|
36cf32dc42 | ||
|
|
657f4ab303 | ||
|
|
ea6552615d | ||
|
|
4bf3287fce | ||
|
|
832c2034c2 | ||
|
|
b0aa26e1f1 | ||
|
|
e52baeb967 | ||
|
|
8268eb9a83 | ||
|
|
3cc458abd9 | ||
|
|
337b4c4268 | ||
|
|
001f8657f6 | ||
|
|
ea884e7fa1 | ||
|
|
1b1394cf5d | ||
|
|
1eef930dbb | ||
|
|
1e175e74ed | ||
|
|
75a46c365e | ||
|
|
8e7b8825f5 | ||
|
|
2ecbca303b | ||
|
|
8195a4d616 | ||
|
|
7ba40f925f | ||
|
|
345cd1795f | ||
|
|
959aaee045 | ||
|
|
53477f0f59 | ||
|
|
5716218f41 | ||
|
|
9df6b9d5c0 | ||
|
|
ec46031d36 | ||
|
|
55b84d166a | ||
|
|
34ae8bacec | ||
|
|
cb4e5ca0f7 | ||
|
|
0ba45468c4 | ||
|
|
710502784e | ||
|
|
0275a8558d | ||
|
|
58acc75cf6 | ||
|
|
874ababb9f | ||
|
|
3771e6b0cd | ||
|
|
33eaefa966 | ||
|
|
cd7e236d57 | ||
|
|
54c0b7c7d5 | ||
|
|
a2177daec2 | ||
|
|
628386b453 | ||
|
|
b222bfb3e0 | ||
|
|
ab199d883d | ||
|
|
356065d1ee | ||
|
|
76e7c5623d | ||
|
|
085fba050a | ||
|
|
295334d3ac | ||
|
|
36124ddca4 | ||
|
|
bd6585765e | ||
|
|
c325deb4ed | ||
|
|
73bb0b10ee | ||
|
|
72820b162c | ||
|
|
89e5b8d057 | ||
|
|
da4f53ebbb | ||
|
|
8458553b74 | ||
|
|
55ecc41d06 | ||
|
|
28fcdf2cbb | ||
|
|
24087679a8 | ||
|
|
5ac6a8cb4a | ||
|
|
668d85d14e | ||
|
|
c11a3dc95c | ||
|
|
56f57c20a2 | ||
|
|
240d14779a | ||
|
|
3550d1e61c | ||
|
|
6513ad249c | ||
|
|
50297b1880 | ||
|
|
f189b78b9e | ||
|
|
5c0250f495 | ||
|
|
2093f726e9 | ||
|
|
10efe3859d | ||
|
|
6933bcf7bb | ||
|
|
2ea046cd80 | ||
|
|
f4097a372b | ||
|
|
87ea2a2bef | ||
|
|
cc14a1c361 | ||
|
|
bcdface60d | ||
|
|
4dc9419d2e | ||
|
|
d2bcac813e | ||
|
|
080c37a7f6 | ||
|
|
f9a3838db6 | ||
|
|
1e61db104b | ||
|
|
30a9c7718d | ||
|
|
34b052b5d3 | ||
|
|
aaa12853ad | ||
|
|
b0ab55b0bf | ||
|
|
d2f8496f4e | ||
|
|
1a69b16d36 | ||
|
|
b5e8673e62 | ||
|
|
264c6a50b6 | ||
|
|
493642eb38 | ||
|
|
28d42b9164 | ||
|
|
42f29062ca | ||
|
|
c4377ed6c2 | ||
|
|
7d283ed65f | ||
|
|
bf1f941e50 | ||
|
|
789fef34ba | ||
|
|
1daf5a611c | ||
|
|
6aed1db67e | ||
|
|
cf68854770 | ||
|
|
711392c73b | ||
|
|
9573c32481 | ||
|
|
a15f80f79d | ||
|
|
23e7475f06 | ||
|
|
1eb571b787 | ||
|
|
dd3b716d85 | ||
|
|
28649c07e3 | ||
|
|
961e02be0d | ||
|
|
a161491bfd | ||
|
|
e0b4d1c1e4 | ||
|
|
fd4aaab137 | ||
|
|
42d14d5ca2 | ||
|
|
d3ff482c9b | ||
|
|
f682368eeb | ||
|
|
4a5d033efb | ||
|
|
343161b195 | ||
|
|
bc576a9659 | ||
|
|
19e407fcc4 | ||
|
|
bc7327d004 | ||
|
|
666fa1c797 | ||
|
|
0eda4a7821 | ||
|
|
862058fd2b | ||
|
|
69e5bcd57d | ||
|
|
efeddda328 | ||
|
|
ff6938280e | ||
|
|
1e4425b30f | ||
|
|
b5d1d8cdad | ||
|
|
029be5ccca | ||
|
|
29c2d785b5 | ||
|
|
abda8cfa32 | ||
|
|
44e7d79d4c | ||
|
|
9a1dc8ee0e | ||
|
|
27879c3f01 | ||
|
|
29096eb5d7 | ||
|
|
a573baea03 | ||
|
|
5af07c4531 | ||
|
|
44e36feb09 | ||
|
|
2a7d996881 | ||
|
|
738f943a68 | ||
|
|
47e62a5681 | ||
|
|
1ecbfd7590 | ||
|
|
67c139a04b | ||
|
|
31cc008249 | ||
|
|
9cb026439d | ||
|
|
e6f10176c6 | ||
|
|
0917c79470 | ||
|
|
597baa986d | ||
|
|
75cc4b4843 | ||
|
|
aac088d496 | ||
|
|
a822e5bbc5 | ||
|
|
c527249c21 | ||
|
|
9ef798f534 | ||
|
|
e69b99f089 | ||
|
|
55b8079e86 | ||
|
|
e272dbe9af | ||
|
|
962f8354ac | ||
|
|
20e4a960f7 | ||
|
|
82249cb50a | ||
|
|
fad417e553 | ||
|
|
5ba692f50c | ||
|
|
907e01e524 | ||
|
|
b8ed23efa7 | ||
|
|
2b3bbf7e67 | ||
|
|
464fe627a3 | ||
|
|
6a9e39c470 | ||
|
|
7fec9a3cc6 | ||
|
|
008f6ef462 | ||
|
|
2440c108ca | ||
|
|
430baad8a4 | ||
|
|
51132e74b4 | ||
|
|
a4f33e106a | ||
|
|
baba3190e0 | ||
|
|
47b13aa5ea | ||
|
|
ae88d3054d | ||
|
|
411b600e14 | ||
|
|
0a0ad9a184 | ||
|
|
234bead59e | ||
|
|
76de310986 | ||
|
|
817f050bcd | ||
|
|
60ae685d1e | ||
|
|
4c7bdbb284 | ||
|
|
435251ca41 | ||
|
|
324a0dd38f | ||
|
|
cc77d93918 | ||
|
|
0ea7d8bd8c | ||
|
|
849b217143 | ||
|
|
9af6efba59 | ||
|
|
079d6f06ef | ||
|
|
9cf0757689 | ||
|
|
b54c438948 | ||
|
|
c3ff4bfdad | ||
|
|
5d62e066e2 | ||
|
|
e94219c5a3 | ||
|
|
8ed9634adf | ||
|
|
0aefa9599f | ||
|
|
e279cf0575 | ||
|
|
a3f0ef8e77 | ||
|
|
8eba05ed4a | ||
|
|
2f78155723 | ||
|
|
6785221479 | ||
|
|
9bc410dd3d | ||
|
|
2491ab6bf9 | ||
|
|
f615ed40cd | ||
|
|
430f2cafc1 | ||
|
|
0ad049da88 | ||
|
|
2c7691567b | ||
|
|
1d70d0fe94 | ||
|
|
ac44f05811 | ||
|
|
d99252f394 | ||
|
|
b58c7ba7c5 | ||
|
|
8c5acd1a0a | ||
|
|
b9b1ebf18c | ||
|
|
8ca132cef0 | ||
|
|
a03bb90754 | ||
|
|
d1c939f48a | ||
|
|
21b11f1b48 | ||
|
|
23c84a7803 | ||
|
|
f9ab060403 | ||
|
|
df7a5bf149 | ||
|
|
c4afa069df | ||
|
|
1bfafdb44f | ||
|
|
1ef5bd7076 | ||
|
|
29176fa4f4 | ||
|
|
958c95732b | ||
|
|
44b0d4127c | ||
|
|
1418ec2416 | ||
|
|
b51978f51c | ||
|
|
b07361580a | ||
|
|
d1b5ebad7d | ||
|
|
f4ce813de9 | ||
|
|
b44ac994d8 | ||
|
|
333948814c | ||
|
|
1a51ad6e01 | ||
|
|
22a5c11f0d | ||
|
|
51b22d1ad4 | ||
|
|
bef5969580 | ||
|
|
c6bf7bb9cd | ||
|
|
2a84d92cbf | ||
|
|
62de36b0da | ||
|
|
03a9aaeff7 | ||
|
|
45765e292d | ||
|
|
6e28a26015 | ||
|
|
9150bf720d | ||
|
|
845864679c | ||
|
|
b3b2149ebb | ||
|
|
0886dca385 | ||
|
|
53198ba4a7 | ||
|
|
a9652ee1fd | ||
|
|
75caf2f01c | ||
|
|
65bab2666e | ||
|
|
6d93ae399a | ||
|
|
7239c2e31a | ||
|
|
43b7ef8110 | ||
|
|
99ef0b8cb4 | ||
|
|
0efb4da0ee | ||
|
|
ed7920d61e | ||
|
|
c0379c8e25 | ||
|
|
00a0e64fdd | ||
|
|
0dc60debea | ||
|
|
c44ae5888c | ||
|
|
b9495cd1bb | ||
|
|
bfec381933 | ||
|
|
2dddb8df69 | ||
|
|
d30397e9c0 | ||
|
|
d9597549fd | ||
|
|
13512b4146 | ||
|
|
49e546919a | ||
|
|
586015c2ed | ||
|
|
4a7e067d1a | ||
|
|
9bc0b7f183 | ||
|
|
cd4dfc9861 | ||
|
|
09bdbc1224 | ||
|
|
978b3a64c5 | ||
|
|
651547ef20 | ||
|
|
b4d95977d0 | ||
|
|
5d8bb897db | ||
|
|
84c8ecb372 | ||
|
|
61abe5b948 | ||
|
|
a5b573eaaa | ||
|
|
cbb32f82eb | ||
|
|
ca9334b2df | ||
|
|
959ed7f866 | ||
|
|
a5c0411be0 | ||
|
|
32e1303742 | ||
|
|
7263b6fe89 | ||
|
|
46a4070f84 | ||
|
|
c3c155a1ed | ||
|
|
b067105660 | ||
|
|
15ca18848e | ||
|
|
67c9e2ead6 | ||
|
|
3681177be4 | ||
|
|
6eb814ef0b | ||
|
|
bcc695234c | ||
|
|
ad16a6fc1b | ||
|
|
478b7eeb65 | ||
|
|
151a153dc9 | ||
|
|
ad131854ca | ||
|
|
0bd0eb9e59 | ||
|
|
cf16fd0104 | ||
|
|
21b00ac6ca | ||
|
|
57e6f3080c | ||
|
|
89744100ce | ||
|
|
a718f9bbfd | ||
|
|
e81bc4f044 | ||
|
|
4dbacd79ae | ||
|
|
ae74d54451 | ||
|
|
dc316c5669 | ||
|
|
e9f04256c9 |
8
.gitignore
vendored
@@ -3,6 +3,10 @@
|
|||||||
/local.properties
|
/local.properties
|
||||||
.idea/
|
.idea/
|
||||||
/build
|
/build
|
||||||
app/app-release.apk
|
app/release
|
||||||
*.hprof
|
*.hprof
|
||||||
app/.externalNativeBuild/
|
.externalNativeBuild/
|
||||||
|
*.sh
|
||||||
|
public.certificate.x509.pem
|
||||||
|
private.key.pk8
|
||||||
|
*.apk
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
# Magisk Manager
|
# Magisk Manager
|
||||||
The project should be built with Android Studio version 2.2.0+
|
This repo is no longer an independent component. It is a submodule of the [Magisk Project](https://github.com/topjohnwu/Magisk).
|
||||||
I use Java 8 features, which requires Jack compiler and it's only available in 2.2.0+
|
|
||||||
Also, you need to install CMake and NDK to build the zipadjust library for zip preprocessing
|
|
||||||
|
|||||||
1
app/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
/build
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.6)
|
|
||||||
add_library(zipadjust SHARED src/main/jni/zipadjust.c)
|
|
||||||
find_library(libz z)
|
|
||||||
find_library(liblog log)
|
|
||||||
target_link_libraries(zipadjust ${libz} ${liblog})
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
apply plugin: 'com.android.application'
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdkVersion 25
|
|
||||||
buildToolsVersion "25.0.2"
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
applicationId "com.topjohnwu.magisk"
|
|
||||||
minSdkVersion 21
|
|
||||||
targetSdkVersion 25
|
|
||||||
versionCode 12
|
|
||||||
versionName "3.1"
|
|
||||||
jackOptions {
|
|
||||||
enabled true
|
|
||||||
}
|
|
||||||
ndk {
|
|
||||||
moduleName 'zipadjust'
|
|
||||||
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
incremental false
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled true
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
dexOptions {
|
|
||||||
preDexLibraries = false
|
|
||||||
}
|
|
||||||
externalNativeBuild {
|
|
||||||
cmake {
|
|
||||||
path 'CMakeLists.txt'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
repositories {
|
|
||||||
jcenter()
|
|
||||||
maven { url "https://jitpack.io" }
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile fileTree(include: ['*.jar'], dir: 'libs')
|
|
||||||
compile 'com.android.support:recyclerview-v7:25.1.0'
|
|
||||||
compile 'com.android.support:cardview-v7:25.1.0'
|
|
||||||
compile 'com.android.support:design:25.1.0'
|
|
||||||
compile 'com.jakewharton:butterknife:8.4.0'
|
|
||||||
compile 'com.google.code.gson:gson:2.8.0'
|
|
||||||
compile 'com.github.clans:fab:1.6.4'
|
|
||||||
compile 'com.madgag.spongycastle:core:1.54.0.0'
|
|
||||||
compile 'com.madgag.spongycastle:prov:1.54.0.0'
|
|
||||||
compile 'com.madgag.spongycastle:pkix:1.54.0.0'
|
|
||||||
compile 'com.madgag.spongycastle:pg:1.54.0.0'
|
|
||||||
compile 'com.google.android.gms:play-services-safetynet:9.0.1'
|
|
||||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
|
|
||||||
}
|
|
||||||
46
app/proguard-rules.pro
vendored
@@ -1,46 +0,0 @@
|
|||||||
# Add project specific ProGuard rules here.
|
|
||||||
# By default, the flags in this file are appended to flags specified
|
|
||||||
# in /Users/topjohnwu/Library/Android/sdk/tools/proguard/proguard-android.txt
|
|
||||||
# You can edit the include path and order by changing the proguardFiles
|
|
||||||
# directive in build.gradle.
|
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
||||||
|
|
||||||
# Add any project specific keep options here:
|
|
||||||
|
|
||||||
# If your project uses WebView with JS, uncomment the following
|
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
|
||||||
# class:
|
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|
||||||
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
|
||||||
# removes such information by default, so configure it to keep all of it.
|
|
||||||
-keepattributes Signature
|
|
||||||
|
|
||||||
# For using GSON @Expose annotation
|
|
||||||
-keepattributes *Annotation*
|
|
||||||
|
|
||||||
# Gson specific classes
|
|
||||||
-keep class sun.misc.Unsafe { *; }
|
|
||||||
-keep class com.google.gson.** { *; }
|
|
||||||
|
|
||||||
# Application classes that will be serialized/deserialized over Gson
|
|
||||||
-keep class com.topjohnwu.magisk.module.** { *; }
|
|
||||||
-keep class com.topjohnwu.magisk.utils.ModuleHelper$ValueSortedMap { *; }
|
|
||||||
|
|
||||||
# Prevent proguard from stripping interface information from TypeAdapterFactory,
|
|
||||||
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
|
||||||
-keep class * implements com.google.gson.TypeAdapterFactory
|
|
||||||
-keep class * implements com.google.gson.JsonSerializer
|
|
||||||
-keep class * implements com.google.gson.JsonDeserializer
|
|
||||||
|
|
||||||
-keep class android.support.v7.internal.** { *; }
|
|
||||||
-keep interface android.support.v7.internal.** { *; }
|
|
||||||
-keep class android.support.v7.** { *; }
|
|
||||||
-keep interface android.support.v7.** { *; }
|
|
||||||
|
|
||||||
# SpongyCastle
|
|
||||||
-keep class org.spongycastle.** {*;}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest package="com.topjohnwu.magisk"
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
|
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
||||||
<uses-permission
|
|
||||||
android:name="android.permission.PACKAGE_USAGE_STATS"
|
|
||||||
tools:ignore="ProtectedPermissions" />
|
|
||||||
|
|
||||||
<application
|
|
||||||
android:allowBackup="true"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:supportsRtl="true"
|
|
||||||
android:theme="@style/AppTheme"
|
|
||||||
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
|
|
||||||
<activity
|
|
||||||
android:name=".MainActivity"
|
|
||||||
android:configChanges="orientation|screenSize"
|
|
||||||
android:exported="true"/>
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".SplashActivity"
|
|
||||||
android:configChanges="orientation|screenSize"
|
|
||||||
android:exported="true"
|
|
||||||
android:theme="@style/SplashTheme">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".AboutActivity"
|
|
||||||
android:theme="@style/AppTheme.Transparent"/>
|
|
||||||
<activity
|
|
||||||
android:name=".SettingsActivity"
|
|
||||||
android:theme="@style/AppTheme.Transparent" />
|
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="android.support.v4.content.FileProvider"
|
|
||||||
android:authorities="com.topjohnwu.magisk.provider"
|
|
||||||
android:exported="false"
|
|
||||||
android:grantUriPermissions="true">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
||||||
android:resource="@xml/file_paths" />
|
|
||||||
</provider>
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="com.google.android.gms.version"
|
|
||||||
android:value="@integer/google_play_services_version" />
|
|
||||||
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD
|
|
||||||
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
|
|
||||||
VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
|
|
||||||
AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
|
|
||||||
Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET
|
|
||||||
MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
|
|
||||||
A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
|
|
||||||
ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
|
|
||||||
hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM
|
|
||||||
qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4
|
|
||||||
wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy
|
|
||||||
4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU
|
|
||||||
RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s
|
|
||||||
zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw
|
|
||||||
HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ
|
|
||||||
AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
|
|
||||||
CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
|
|
||||||
QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
|
|
||||||
CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud
|
|
||||||
EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa
|
|
||||||
J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y
|
|
||||||
LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe
|
|
||||||
+ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX
|
|
||||||
31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr
|
|
||||||
sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0=
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
|
Before Width: | Height: | Size: 42 KiB |
@@ -1,152 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v7.app.ActionBar;
|
|
||||||
import android.support.v7.app.AppCompatActivity;
|
|
||||||
import android.support.v7.widget.Toolbar;
|
|
||||||
import android.text.Html;
|
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.text.method.LinkMovementMethod;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
|
|
||||||
public class AboutActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
private static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/MagiskManager";
|
|
||||||
private static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382";
|
|
||||||
private static final String DONATION_URL = "http://topjohnwu.github.io/donate";
|
|
||||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
|
||||||
@BindView(R.id.app_version_info) AboutCardRow appVersionInfo;
|
|
||||||
@BindView(R.id.app_changelog) AboutCardRow appChangelog;
|
|
||||||
@BindView(R.id.app_developers) AboutCardRow appDevelopers;
|
|
||||||
@BindView(R.id.app_translators) AboutCardRow appTranslators;
|
|
||||||
@BindView(R.id.app_source_code) AboutCardRow appSourceCode;
|
|
||||||
@BindView(R.id.support_thread) AboutCardRow supportThread;
|
|
||||||
@BindView(R.id.donation) AboutCardRow donation;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
String theme = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("theme", "");
|
|
||||||
Logger.dev("AboutActivity: Theme is " + theme);
|
|
||||||
if (Utils.isDarkTheme) {
|
|
||||||
setTheme(R.style.AppTheme_dh);
|
|
||||||
}
|
|
||||||
setContentView(R.layout.activity_about);
|
|
||||||
ButterKnife.bind(this);
|
|
||||||
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
toolbar.setNavigationOnClickListener(view -> finish());
|
|
||||||
|
|
||||||
ActionBar ab = getSupportActionBar();
|
|
||||||
if (ab != null) {
|
|
||||||
ab.setTitle(R.string.about);
|
|
||||||
ab.setDisplayHomeAsUpEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
appVersionInfo.setSummary(BuildConfig.VERSION_NAME);
|
|
||||||
|
|
||||||
String changes = null;
|
|
||||||
try {
|
|
||||||
InputStream is = getAssets().open("changelog.html");
|
|
||||||
int size = is.available();
|
|
||||||
|
|
||||||
byte[] buffer = new byte[size];
|
|
||||||
is.read(buffer);
|
|
||||||
is.close();
|
|
||||||
|
|
||||||
changes = new String(buffer);
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
appChangelog.removeSummary();
|
|
||||||
if (changes == null) {
|
|
||||||
appChangelog.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
Spanned result;
|
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
|
||||||
result = Html.fromHtml(changes, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE);
|
|
||||||
} else {
|
|
||||||
result = Html.fromHtml(changes);
|
|
||||||
}
|
|
||||||
appChangelog.setOnClickListener(v -> {
|
|
||||||
AlertDialog d = Utils.getAlertDialogBuilder(this)
|
|
||||||
.setTitle(R.string.app_changelog)
|
|
||||||
.setMessage(result)
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show();
|
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
((TextView) d.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
appDevelopers.removeSummary();
|
|
||||||
appDevelopers.setOnClickListener(view -> {
|
|
||||||
Spanned result;
|
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
|
||||||
result = Html.fromHtml(getString(R.string.app_developers_), Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE);
|
|
||||||
} else {
|
|
||||||
result = Html.fromHtml(getString(R.string.app_developers_));
|
|
||||||
}
|
|
||||||
AlertDialog d = Utils.getAlertDialogBuilder(this)
|
|
||||||
.setTitle(R.string.app_developers)
|
|
||||||
.setMessage(result)
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.create();
|
|
||||||
|
|
||||||
d.show();
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
((TextView) d.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
});
|
|
||||||
|
|
||||||
String translators = getString(R.string.translators);
|
|
||||||
if (TextUtils.isEmpty(translators)) {
|
|
||||||
appTranslators.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
appTranslators.setSummary(translators);
|
|
||||||
}
|
|
||||||
|
|
||||||
appSourceCode.removeSummary();
|
|
||||||
appSourceCode.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(SOURCE_CODE_URL))));
|
|
||||||
|
|
||||||
supportThread.removeSummary();
|
|
||||||
supportThread.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(XDA_THREAD))));
|
|
||||||
|
|
||||||
donation.removeSummary();
|
|
||||||
donation.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(DONATION_URL))));
|
|
||||||
|
|
||||||
setFloating();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFloating() {
|
|
||||||
boolean isTablet = getResources().getBoolean(R.bool.isTablet);
|
|
||||||
if (isTablet) {
|
|
||||||
WindowManager.LayoutParams params = getWindow().getAttributes();
|
|
||||||
params.height = getResources().getDimensionPixelSize(R.dimen.floating_height);
|
|
||||||
params.width = getResources().getDimensionPixelSize(R.dimen.floating_width);
|
|
||||||
params.alpha = 1.0f;
|
|
||||||
params.dimAmount = 0.6f;
|
|
||||||
params.flags |= 2;
|
|
||||||
getWindow().setAttributes(params);
|
|
||||||
setFinishOnTouchOutside(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.design.widget.CoordinatorLayout;
|
|
||||||
import android.support.design.widget.Snackbar;
|
|
||||||
import android.support.v4.view.ViewCompat;
|
|
||||||
import android.support.v4.view.ViewPropertyAnimatorCompat;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.github.clans.fab.FloatingActionMenu;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Matteo on 08/08/2015.
|
|
||||||
*
|
|
||||||
* Floating Action Menu Behavior for Clans.FloatingActionButton
|
|
||||||
* https://github.com/Clans/FloatingActionButton/
|
|
||||||
*
|
|
||||||
* Use this behavior as your app:layout_behavior attribute in your Floating Action Menu to use the
|
|
||||||
* FabMenu in a Coordinator Layout.
|
|
||||||
*
|
|
||||||
* Remember to use the correct namespace for the fab:
|
|
||||||
* xmlns:fab="http://schemas.android.com/apk/res-auto"
|
|
||||||
*/
|
|
||||||
public class FABBehavior extends CoordinatorLayout.Behavior {
|
|
||||||
private float mTranslationY;
|
|
||||||
|
|
||||||
public FABBehavior(Context context, AttributeSet attrs) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
|
|
||||||
return dependency instanceof Snackbar.SnackbarLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
|
|
||||||
if (dependency instanceof Snackbar.SnackbarLayout) {
|
|
||||||
updateTranslation(parent, child);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDependentViewRemoved(CoordinatorLayout parent, View child, View dependency) {
|
|
||||||
if (dependency instanceof Snackbar.SnackbarLayout) {
|
|
||||||
revertTranslation(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTranslation(CoordinatorLayout parent, View child) {
|
|
||||||
float translationY = getTranslationY(parent, child);
|
|
||||||
if (translationY != mTranslationY) {
|
|
||||||
ViewPropertyAnimatorCompat anim = ViewCompat.animate(child);
|
|
||||||
anim.cancel();
|
|
||||||
anim.translationY(translationY).setDuration(100);
|
|
||||||
mTranslationY = translationY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void revertTranslation(View child) {
|
|
||||||
if (mTranslationY != 0) {
|
|
||||||
ViewPropertyAnimatorCompat anim = ViewCompat.animate(child);
|
|
||||||
anim.cancel();
|
|
||||||
anim.translationY(0).setDuration(100);
|
|
||||||
mTranslationY = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private float getTranslationY(CoordinatorLayout parent, View child) {
|
|
||||||
float minOffset = 0.0F;
|
|
||||||
List dependencies = parent.getDependencies(child);
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
for (int z = dependencies.size(); i < z; ++i) {
|
|
||||||
View view = (View) dependencies.get(i);
|
|
||||||
if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(child, view)) {
|
|
||||||
minOffset = Math.min(minOffset, ViewCompat.getTranslationY(view) - (float) view.getHeight());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return minOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* onStartNestedScroll and onNestedScroll will hide/show the FabMenu when a scroll is detected.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child,
|
|
||||||
View directTargetChild, View target, int nestedScrollAxes) {
|
|
||||||
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
|
|
||||||
super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target,
|
|
||||||
nestedScrollAxes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target,
|
|
||||||
int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
|
|
||||||
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,
|
|
||||||
dyUnconsumed);
|
|
||||||
FloatingActionMenu fabMenu = (FloatingActionMenu) child;
|
|
||||||
if (dyConsumed > 0 && !fabMenu.isMenuButtonHidden()) {
|
|
||||||
fabMenu.hideMenuButton(true);
|
|
||||||
} else if (dyConsumed < 0 && fabMenu.isMenuButtonHidden()) {
|
|
||||||
fabMenu.showMenuButton(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import android.app.Fragment;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v7.widget.CardView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.Spinner;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.receivers.MagiskDlReceiver;
|
|
||||||
import com.topjohnwu.magisk.utils.CallbackHandler;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
|
|
||||||
public class InstallFragment extends Fragment implements CallbackHandler.EventListener {
|
|
||||||
|
|
||||||
public static final CallbackHandler.Event blockDetectionDone = new CallbackHandler.Event();
|
|
||||||
|
|
||||||
public static List<String> blockList;
|
|
||||||
public static String bootBlock = null;
|
|
||||||
|
|
||||||
@BindView(R.id.current_version_title) TextView currentVersionTitle;
|
|
||||||
@BindView(R.id.install_title) TextView installTitle;
|
|
||||||
@BindView(R.id.block_spinner) Spinner spinner;
|
|
||||||
@BindView(R.id.detect_bootimage) Button detectButton;
|
|
||||||
@BindView(R.id.flash_button) CardView flashButton;
|
|
||||||
@BindView(R.id.keep_force_enc) CheckBox keepEncChkbox;
|
|
||||||
@BindView(R.id.keep_verity) CheckBox keepVerityChkbox;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View v = inflater.inflate(R.layout.install_fragment, container, false);
|
|
||||||
ButterKnife.bind(this, v);
|
|
||||||
detectButton.setOnClickListener(v1 -> toAutoDetect());
|
|
||||||
currentVersionTitle.setText(getString(R.string.current_magisk_title, StatusFragment.magiskVersionString));
|
|
||||||
installTitle.setText(getString(R.string.install_magisk_title, StatusFragment.remoteMagiskVersion));
|
|
||||||
flashButton.setOnClickListener(v1 -> {
|
|
||||||
String bootImage = bootBlock;
|
|
||||||
if (bootImage == null) {
|
|
||||||
bootImage = blockList.get(spinner.getSelectedItemPosition() - 1);
|
|
||||||
}
|
|
||||||
String filename = "Magisk-v" + StatusFragment.remoteMagiskVersion + ".zip";
|
|
||||||
String finalBootImage = bootImage;
|
|
||||||
Utils.getAlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(getString(R.string.repo_install_title, getString(R.string.magisk)))
|
|
||||||
.setMessage(getString(R.string.repo_install_msg, filename))
|
|
||||||
.setCancelable(true)
|
|
||||||
.setPositiveButton(R.string.download_install, (dialogInterface, i) -> Utils.dlAndReceive(
|
|
||||||
getActivity(),
|
|
||||||
new MagiskDlReceiver(finalBootImage, keepEncChkbox.isChecked(), keepVerityChkbox.isChecked()),
|
|
||||||
StatusFragment.magiskLink,
|
|
||||||
Utils.getLegalFilename(filename)))
|
|
||||||
.setNeutralButton(R.string.check_release_notes, (dialog, which) -> {
|
|
||||||
getActivity().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(StatusFragment.releaseNoteLink)));
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.no_thanks, null)
|
|
||||||
.show();
|
|
||||||
});
|
|
||||||
if (blockDetectionDone.isTriggered) {
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrigger(CallbackHandler.Event event) {
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUI() {
|
|
||||||
List<String> items = new ArrayList<>(blockList);
|
|
||||||
items.add(0, getString(R.string.auto_detect, bootBlock));
|
|
||||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),
|
|
||||||
android.R.layout.simple_spinner_item, items);
|
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
|
||||||
spinner.setAdapter(adapter);
|
|
||||||
toAutoDetect();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toAutoDetect() {
|
|
||||||
if (bootBlock != null) {
|
|
||||||
spinner.setSelection(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
getActivity().setTitle(R.string.install);
|
|
||||||
CallbackHandler.register(blockDetectionDone, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
CallbackHandler.unRegister(blockDetectionDone, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Fragment;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.design.widget.Snackbar;
|
|
||||||
import android.support.v4.app.ActivityCompat;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.HorizontalScrollView;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.ScrollView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.Async;
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
|
|
||||||
public class LogFragment extends Fragment {
|
|
||||||
|
|
||||||
private static final String MAGISK_LOG = "/cache/magisk.log";
|
|
||||||
|
|
||||||
@BindView(R.id.txtLog) TextView txtLog;
|
|
||||||
@BindView(R.id.svLog) ScrollView svLog;
|
|
||||||
@BindView(R.id.hsvLog) HorizontalScrollView hsvLog;
|
|
||||||
|
|
||||||
@BindView(R.id.progressBar) ProgressBar progressBar;
|
|
||||||
|
|
||||||
private MenuItem mClickedMenuItem;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View view = inflater.inflate(R.layout.log_fragment, container, false);
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
|
|
||||||
txtLog.setTextIsSelectable(true);
|
|
||||||
|
|
||||||
new LogManager().read();
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
new LogManager().read();
|
|
||||||
getActivity().setTitle(R.string.log);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
inflater.inflate(R.menu.menu_log, menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
mClickedMenuItem = item;
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case R.id.menu_refresh:
|
|
||||||
new LogManager().read();
|
|
||||||
return true;
|
|
||||||
case R.id.menu_send:
|
|
||||||
new LogManager().send();
|
|
||||||
return true;
|
|
||||||
case R.id.menu_save:
|
|
||||||
new LogManager().save();
|
|
||||||
return true;
|
|
||||||
case R.id.menu_clear:
|
|
||||||
new LogManager().clear();
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
if (requestCode == 0) {
|
|
||||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
if (mClickedMenuItem != null) {
|
|
||||||
new Handler().postDelayed(() -> onOptionsItemSelected(mClickedMenuItem), 500);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Snackbar.make(txtLog, R.string.permissionNotGranted, Snackbar.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LogManager extends Async.RootTask<Object, Void, Object> {
|
|
||||||
|
|
||||||
int mode;
|
|
||||||
File targetFile;
|
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
|
||||||
@Override
|
|
||||||
protected Object doInBackground(Object... params) {
|
|
||||||
mode = (int) params[0];
|
|
||||||
switch (mode) {
|
|
||||||
case 0:
|
|
||||||
List<String> logList = Utils.readFile(MAGISK_LOG);
|
|
||||||
|
|
||||||
StringBuilder llog = new StringBuilder(15 * 10 * 1024);
|
|
||||||
for (String s : logList) {
|
|
||||||
llog.append(s).append("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
return llog.toString();
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
Shell.su("echo > " + MAGISK_LOG);
|
|
||||||
Snackbar.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
|
||||||
return "";
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
case 3:
|
|
||||||
if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
Calendar now = Calendar.getInstance();
|
|
||||||
String filename = String.format(
|
|
||||||
"magisk_%s_%04d%02d%02d_%02d%02d%02d.log", "error",
|
|
||||||
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
|
||||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
|
||||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
|
|
||||||
|
|
||||||
targetFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/MagiskManager/" + filename);
|
|
||||||
|
|
||||||
if ((!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs())
|
|
||||||
|| (targetFile.exists() && !targetFile.delete()))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
List<String> in = Utils.readFile(MAGISK_LOG);
|
|
||||||
|
|
||||||
try {
|
|
||||||
FileWriter out = new FileWriter(targetFile);
|
|
||||||
for (String line : in) {
|
|
||||||
out.write(line + "\n");
|
|
||||||
}
|
|
||||||
out.close();
|
|
||||||
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Object o) {
|
|
||||||
boolean bool;
|
|
||||||
String llog;
|
|
||||||
switch (mode) {
|
|
||||||
case 0:
|
|
||||||
case 1:
|
|
||||||
llog = (String) o;
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
if (llog.length() == 0)
|
|
||||||
txtLog.setText(R.string.log_is_empty);
|
|
||||||
else
|
|
||||||
txtLog.setText(llog);
|
|
||||||
svLog.post(() -> svLog.scrollTo(0, txtLog.getHeight()));
|
|
||||||
hsvLog.post(() -> hsvLog.scrollTo(0, 0));
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
bool = (boolean) o;
|
|
||||||
if (bool)
|
|
||||||
Toast.makeText(getActivity(), targetFile.toString(), Toast.LENGTH_LONG).show();
|
|
||||||
else
|
|
||||||
Toast.makeText(getActivity(), getString(R.string.logs_save_failed), Toast.LENGTH_LONG).show();
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
bool = (boolean) o;
|
|
||||||
if (bool) {
|
|
||||||
Intent sendIntent = new Intent();
|
|
||||||
sendIntent.setAction(Intent.ACTION_SEND);
|
|
||||||
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(targetFile));
|
|
||||||
sendIntent.setType("application/html");
|
|
||||||
startActivity(Intent.createChooser(sendIntent, getResources().getString(R.string.menuSend)));
|
|
||||||
} else {
|
|
||||||
Toast.makeText(getActivity(), getString(R.string.logs_save_failed), Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void read() {
|
|
||||||
exec(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
exec(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save() {
|
|
||||||
exec(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send() {
|
|
||||||
exec(3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.Fragment;
|
|
||||||
import android.app.FragmentTransaction;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.annotation.IdRes;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.design.widget.NavigationView;
|
|
||||||
import android.support.v4.app.ActivityCompat;
|
|
||||||
import android.support.v4.view.GravityCompat;
|
|
||||||
import android.support.v4.widget.DrawerLayout;
|
|
||||||
import android.support.v7.app.ActionBarDrawerToggle;
|
|
||||||
import android.support.v7.app.AppCompatActivity;
|
|
||||||
import android.support.v7.widget.Toolbar;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.CallbackHandler;
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity
|
|
||||||
implements NavigationView.OnNavigationItemSelectedListener, CallbackHandler.EventListener {
|
|
||||||
|
|
||||||
private static final String SELECTED_ITEM_ID = "SELECTED_ITEM_ID";
|
|
||||||
|
|
||||||
public static CallbackHandler.Event recreate = new CallbackHandler.Event();
|
|
||||||
|
|
||||||
private final Handler mDrawerHandler = new Handler();
|
|
||||||
private SharedPreferences prefs;
|
|
||||||
|
|
||||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
|
||||||
@BindView(R.id.drawer_layout) DrawerLayout drawer;
|
|
||||||
@BindView(R.id.nav_view) public NavigationView navigationView;
|
|
||||||
|
|
||||||
@IdRes
|
|
||||||
private int mSelectedId = R.id.status;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
|
||||||
|
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
|
||||||
|
|
||||||
if (Utils.isDarkTheme) {
|
|
||||||
setTheme(R.style.AppTheme_dh);
|
|
||||||
}
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_main);
|
|
||||||
ButterKnife.bind(this);
|
|
||||||
|
|
||||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|
|
||||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
|
|
||||||
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) {
|
|
||||||
@Override
|
|
||||||
public void onDrawerOpened(View drawerView) {
|
|
||||||
super.onDrawerOpened(drawerView);
|
|
||||||
super.onDrawerSlide(drawerView, 0); // this disables the arrow @ completed tate
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDrawerSlide(View drawerView, float slideOffset) {
|
|
||||||
super.onDrawerSlide(drawerView, 0); // this disables the animation
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
drawer.addDrawerListener(toggle);
|
|
||||||
toggle.syncState();
|
|
||||||
|
|
||||||
//noinspection ResourceType
|
|
||||||
mSelectedId = savedInstanceState == null ? mSelectedId : savedInstanceState.getInt(SELECTED_ITEM_ID);
|
|
||||||
navigationView.setCheckedItem(mSelectedId);
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
mDrawerHandler.removeCallbacksAndMessages(null);
|
|
||||||
mDrawerHandler.postDelayed(() -> navigate(mSelectedId), 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
navigationView.setNavigationItemSelectedListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
CallbackHandler.register(StatusFragment.updateCheckDone, this);
|
|
||||||
CallbackHandler.register(recreate, this);
|
|
||||||
if (StatusFragment.updateCheckDone.isTriggered) {
|
|
||||||
onTrigger(StatusFragment.updateCheckDone);
|
|
||||||
}
|
|
||||||
checkHideSection();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
CallbackHandler.unRegister(StatusFragment.updateCheckDone, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
CallbackHandler.unRegister(recreate, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putInt(SELECTED_ITEM_ID, mSelectedId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
if (drawer.isDrawerOpen(GravityCompat.START)) {
|
|
||||||
drawer.closeDrawer(GravityCompat.START);
|
|
||||||
} else {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onNavigationItemSelected(@NonNull final MenuItem menuItem) {
|
|
||||||
mSelectedId = menuItem.getItemId();
|
|
||||||
mDrawerHandler.removeCallbacksAndMessages(null);
|
|
||||||
mDrawerHandler.postDelayed(() -> navigate(menuItem.getItemId()), 250);
|
|
||||||
drawer.closeDrawer(GravityCompat.START);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrigger(CallbackHandler.Event event) {
|
|
||||||
if (event == StatusFragment.updateCheckDone) {
|
|
||||||
Menu menu = navigationView.getMenu();
|
|
||||||
menu.findItem(R.id.install).setVisible(StatusFragment.remoteMagiskVersion > 0 &&
|
|
||||||
Shell.rootAccess());
|
|
||||||
} else if (event == recreate) {
|
|
||||||
recreate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkHideSection() {
|
|
||||||
Menu menu = navigationView.getMenu();
|
|
||||||
menu.findItem(R.id.magiskhide).setVisible(StatusFragment.magiskVersion >= 8 &&
|
|
||||||
prefs.getBoolean("magiskhide", false) && Shell.rootAccess());
|
|
||||||
menu.findItem(R.id.modules).setVisible(StatusFragment.magiskVersion >= 4 &&
|
|
||||||
Shell.rootAccess());
|
|
||||||
menu.findItem(R.id.downloads).setVisible(StatusFragment.magiskVersion >= 4 &&
|
|
||||||
Shell.rootAccess());
|
|
||||||
menu.findItem(R.id.log).setVisible(Shell.rootAccess());
|
|
||||||
menu.findItem(R.id.install).setVisible(Shell.rootAccess());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void navigate(final int itemId) {
|
|
||||||
Fragment navFragment = null;
|
|
||||||
String tag = "";
|
|
||||||
switch (itemId) {
|
|
||||||
case R.id.status:
|
|
||||||
tag = "status";
|
|
||||||
navFragment = new StatusFragment();
|
|
||||||
break;
|
|
||||||
case R.id.install:
|
|
||||||
tag = "install";
|
|
||||||
navFragment = new InstallFragment();
|
|
||||||
break;
|
|
||||||
case R.id.modules:
|
|
||||||
tag = "modules";
|
|
||||||
navFragment = new ModulesFragment();
|
|
||||||
break;
|
|
||||||
case R.id.downloads:
|
|
||||||
tag = "downloads";
|
|
||||||
navFragment = new ReposFragment();
|
|
||||||
break;
|
|
||||||
case R.id.magiskhide:
|
|
||||||
tag = "magiskhide";
|
|
||||||
navFragment = new MagiskHideFragment();
|
|
||||||
break;
|
|
||||||
case R.id.log:
|
|
||||||
tag = "log";
|
|
||||||
navFragment = new LogFragment();
|
|
||||||
break;
|
|
||||||
case R.id.settings:
|
|
||||||
startActivity(new Intent(this, SettingsActivity.class));
|
|
||||||
break;
|
|
||||||
case R.id.app_about:
|
|
||||||
startActivity(new Intent(this, AboutActivity.class));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (navFragment != null) {
|
|
||||||
FragmentTransaction transaction = getFragmentManager().beginTransaction();
|
|
||||||
transaction.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
|
|
||||||
try {
|
|
||||||
transaction.replace(R.id.content_frame, navFragment, tag).commit();
|
|
||||||
} catch (IllegalStateException ignored) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Fragment;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.github.clans.fab.FloatingActionButton;
|
|
||||||
import com.topjohnwu.magisk.adapters.ModulesAdapter;
|
|
||||||
import com.topjohnwu.magisk.module.Module;
|
|
||||||
import com.topjohnwu.magisk.utils.Async;
|
|
||||||
import com.topjohnwu.magisk.utils.CallbackHandler;
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.ModuleHelper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
|
|
||||||
public class ModulesFragment extends Fragment implements CallbackHandler.EventListener {
|
|
||||||
|
|
||||||
public static final CallbackHandler.Event moduleLoadDone = new CallbackHandler.Event();
|
|
||||||
|
|
||||||
private static final int FETCH_ZIP_CODE = 2;
|
|
||||||
|
|
||||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
|
||||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
|
||||||
@BindView(R.id.empty_rv) TextView emptyTv;
|
|
||||||
@BindView(R.id.fab) FloatingActionButton fabio;
|
|
||||||
|
|
||||||
private List<Module> listModules = new ArrayList<>();
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
||||||
View view = inflater.inflate(R.layout.modules_fragment, container, false);
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
|
|
||||||
fabio.setOnClickListener(v -> {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
||||||
intent.setType("application/zip");
|
|
||||||
startActivityForResult(intent, FETCH_ZIP_CODE);
|
|
||||||
});
|
|
||||||
|
|
||||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
|
||||||
recyclerView.setVisibility(View.GONE);
|
|
||||||
new Async.LoadModules().exec();
|
|
||||||
});
|
|
||||||
|
|
||||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
|
||||||
@Override
|
|
||||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
|
||||||
mSwipeRefreshLayout.setEnabled(recyclerView.getChildAt(0).getTop() >= 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
|
||||||
super.onScrollStateChanged(recyclerView, newState);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (moduleLoadDone.isTriggered) {
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrigger(CallbackHandler.Event event) {
|
|
||||||
Logger.dev("ModulesFragment: UI refresh triggered");
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (requestCode == FETCH_ZIP_CODE && resultCode == Activity.RESULT_OK && data != null) {
|
|
||||||
// Get the URI of the selected file
|
|
||||||
final Uri uri = data.getData();
|
|
||||||
new Async.FlashZIP(getActivity(), uri).exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
CallbackHandler.register(moduleLoadDone, this);
|
|
||||||
getActivity().setTitle(R.string.modules);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
CallbackHandler.unRegister(moduleLoadDone, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUI() {
|
|
||||||
ModuleHelper.getModuleList(listModules);
|
|
||||||
if (listModules.size() == 0) {
|
|
||||||
emptyTv.setVisibility(View.VISIBLE);
|
|
||||||
recyclerView.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
emptyTv.setVisibility(View.GONE);
|
|
||||||
recyclerView.setVisibility(View.VISIBLE);
|
|
||||||
recyclerView.setAdapter(new ModulesAdapter(listModules));
|
|
||||||
}
|
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import android.app.Fragment;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v4.view.MenuItemCompat;
|
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.SearchView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
|
||||||
import com.topjohnwu.magisk.adapters.SimpleSectionedRecyclerViewAdapter;
|
|
||||||
import com.topjohnwu.magisk.module.Repo;
|
|
||||||
import com.topjohnwu.magisk.utils.Async;
|
|
||||||
import com.topjohnwu.magisk.utils.CallbackHandler;
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.ModuleHelper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
|
|
||||||
public class ReposFragment extends Fragment implements CallbackHandler.EventListener {
|
|
||||||
|
|
||||||
public static final CallbackHandler.Event repoLoadDone = new CallbackHandler.Event();
|
|
||||||
|
|
||||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
|
||||||
@BindView(R.id.empty_rv) TextView emptyTv;
|
|
||||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
|
||||||
|
|
||||||
private List<Repo> mUpdateRepos = new ArrayList<>();
|
|
||||||
private List<Repo> mInstalledRepos = new ArrayList<>();
|
|
||||||
private List<Repo> mOthersRepos = new ArrayList<>();
|
|
||||||
private List<Repo> fUpdateRepos = new ArrayList<>();
|
|
||||||
private List<Repo> fInstalledRepos = new ArrayList<>();
|
|
||||||
private List<Repo> fOthersRepos = new ArrayList<>();
|
|
||||||
|
|
||||||
private SimpleSectionedRecyclerViewAdapter mSectionedAdapter;
|
|
||||||
|
|
||||||
private SearchView.OnQueryTextListener searchListener;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
||||||
View view = inflater.inflate(R.layout.repos_fragment, container, false);
|
|
||||||
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
|
|
||||||
mSectionedAdapter = new
|
|
||||||
SimpleSectionedRecyclerViewAdapter(getActivity(), R.layout.section,
|
|
||||||
R.id.section_text, new ReposAdapter(fUpdateRepos, fInstalledRepos, fOthersRepos));
|
|
||||||
|
|
||||||
recyclerView.setAdapter(mSectionedAdapter);
|
|
||||||
|
|
||||||
mSwipeRefreshLayout.setRefreshing(true);
|
|
||||||
|
|
||||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
|
||||||
recyclerView.setVisibility(View.GONE);
|
|
||||||
new Async.LoadRepos(getActivity()).exec();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (repoLoadDone.isTriggered) {
|
|
||||||
reloadRepos();
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
searchListener = new SearchView.OnQueryTextListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextSubmit(String query) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextChange(String newText) {
|
|
||||||
new FilterApps().exec(newText);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrigger(CallbackHandler.Event event) {
|
|
||||||
Logger.dev("ReposFragment: UI refresh triggered");
|
|
||||||
reloadRepos();
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
inflater.inflate(R.menu.menu_repo, menu);
|
|
||||||
SearchView search = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.repo_search));
|
|
||||||
search.setOnQueryTextListener(searchListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
CallbackHandler.register(repoLoadDone, this);
|
|
||||||
getActivity().setTitle(R.string.downloads);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
CallbackHandler.unRegister(repoLoadDone, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reloadRepos() {
|
|
||||||
ModuleHelper.getRepoLists(mUpdateRepos, mInstalledRepos, mOthersRepos);
|
|
||||||
fUpdateRepos.clear();
|
|
||||||
fInstalledRepos.clear();
|
|
||||||
fOthersRepos.clear();
|
|
||||||
fUpdateRepos.addAll(mUpdateRepos);
|
|
||||||
fInstalledRepos.addAll(mInstalledRepos);
|
|
||||||
fOthersRepos.addAll(mOthersRepos);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUI() {
|
|
||||||
if (fUpdateRepos.size() + fInstalledRepos.size() + fOthersRepos.size() == 0) {
|
|
||||||
emptyTv.setVisibility(View.VISIBLE);
|
|
||||||
recyclerView.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
List<SimpleSectionedRecyclerViewAdapter.Section> sections = new ArrayList<>();
|
|
||||||
if (!fUpdateRepos.isEmpty()) {
|
|
||||||
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(0, getString(R.string.update_available)));
|
|
||||||
}
|
|
||||||
if (!fInstalledRepos.isEmpty()) {
|
|
||||||
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(fUpdateRepos.size(), getString(R.string.installed)));
|
|
||||||
}
|
|
||||||
if (!fOthersRepos.isEmpty()) {
|
|
||||||
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(fUpdateRepos.size() + fInstalledRepos.size(), getString(R.string.not_installed)));
|
|
||||||
}
|
|
||||||
SimpleSectionedRecyclerViewAdapter.Section[] array = sections.toArray(new SimpleSectionedRecyclerViewAdapter.Section[sections.size()]);
|
|
||||||
mSectionedAdapter.setSections(array);
|
|
||||||
emptyTv.setVisibility(View.GONE);
|
|
||||||
recyclerView.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FilterApps extends Async.NormalTask<String, Void, Void> {
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(String... strings) {
|
|
||||||
String newText = strings[0];
|
|
||||||
fUpdateRepos.clear();
|
|
||||||
fInstalledRepos.clear();
|
|
||||||
fOthersRepos.clear();
|
|
||||||
for (Repo repo: mUpdateRepos) {
|
|
||||||
if (repo.getName().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
|| repo.getAuthor().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
|| repo.getDescription().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
) {
|
|
||||||
fUpdateRepos.add(repo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Repo repo: mInstalledRepos) {
|
|
||||||
if (repo.getName().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
|| repo.getAuthor().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
|| repo.getDescription().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
) {
|
|
||||||
fInstalledRepos.add(repo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Repo repo: mOthersRepos) {
|
|
||||||
if (repo.getName().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
|| repo.getAuthor().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
|| repo.getDescription().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
) {
|
|
||||||
fOthersRepos.add(repo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.CheckBoxPreference;
|
|
||||||
import android.preference.ListPreference;
|
|
||||||
import android.preference.Preference;
|
|
||||||
import android.preference.PreferenceFragment;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.v7.app.ActionBar;
|
|
||||||
import android.support.v7.app.AppCompatActivity;
|
|
||||||
import android.support.v7.widget.Toolbar;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.Async;
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.ModuleHelper;
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
|
|
||||||
public class SettingsActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
String theme = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("theme", "");
|
|
||||||
Logger.dev("AboutActivity: Theme is " + theme);
|
|
||||||
if (Utils.isDarkTheme) {
|
|
||||||
setTheme(R.style.AppTheme_dh);
|
|
||||||
}
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_container);
|
|
||||||
ButterKnife.bind(this);
|
|
||||||
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
|
|
||||||
toolbar.setNavigationOnClickListener(view -> finish());
|
|
||||||
|
|
||||||
ActionBar ab = getSupportActionBar();
|
|
||||||
if (ab != null) {
|
|
||||||
ab.setTitle(R.string.settings);
|
|
||||||
ab.setDisplayHomeAsUpEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
setFloating();
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
getFragmentManager().beginTransaction().add(R.id.container, new SettingsFragment()).commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFloating() {
|
|
||||||
boolean isTablet = getResources().getBoolean(R.bool.isTablet);
|
|
||||||
if (isTablet) {
|
|
||||||
WindowManager.LayoutParams params = getWindow().getAttributes();
|
|
||||||
params.height = getResources().getDimensionPixelSize(R.dimen.floating_height);
|
|
||||||
params.width = getResources().getDimensionPixelSize(R.dimen.floating_width);
|
|
||||||
params.alpha = 1.0f;
|
|
||||||
params.dimAmount = 0.6f;
|
|
||||||
params.flags |= 2;
|
|
||||||
getWindow().setAttributes(params);
|
|
||||||
setFinishOnTouchOutside(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SettingsFragment extends PreferenceFragment
|
|
||||||
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
|
||||||
|
|
||||||
private ListPreference themePreference;
|
|
||||||
private SharedPreferences prefs;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
addPreferencesFromResource(R.xml.app_settings);
|
|
||||||
PreferenceManager.setDefaultValues(getActivity(), R.xml.app_settings, false);
|
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
|
||||||
|
|
||||||
themePreference = (ListPreference) findPreference("theme");
|
|
||||||
CheckBoxPreference busyboxPreference = (CheckBoxPreference) findPreference("busybox");
|
|
||||||
CheckBoxPreference magiskhidePreference = (CheckBoxPreference) findPreference("magiskhide");
|
|
||||||
CheckBoxPreference hostsPreference = (CheckBoxPreference) findPreference("hosts");
|
|
||||||
Preference clear = findPreference("clear");
|
|
||||||
|
|
||||||
clear.setOnPreferenceClickListener((pref) -> {
|
|
||||||
SharedPreferences repoMap = getActivity().getSharedPreferences(ModuleHelper.FILE_KEY, Context.MODE_PRIVATE);
|
|
||||||
repoMap.edit()
|
|
||||||
.putString(ModuleHelper.ETAG_KEY, "")
|
|
||||||
.putInt(ModuleHelper.VERSION_KEY, 0)
|
|
||||||
.apply();
|
|
||||||
new Async.LoadRepos(getActivity()).exec();
|
|
||||||
Toast.makeText(getActivity(), R.string.repo_cache_cleared, Toast.LENGTH_LONG).show();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Utils.isDarkTheme) {
|
|
||||||
themePreference.setSummary(R.string.theme_dark);
|
|
||||||
} else {
|
|
||||||
themePreference.setSummary(R.string.theme_default);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StatusFragment.magiskVersion < 9) {
|
|
||||||
hostsPreference.setEnabled(false);
|
|
||||||
busyboxPreference.setEnabled(false);
|
|
||||||
} else if (StatusFragment.magiskVersion < 8) {
|
|
||||||
magiskhidePreference.setEnabled(false);
|
|
||||||
} else if (! Shell.rootAccess()) {
|
|
||||||
busyboxPreference.setEnabled(false);
|
|
||||||
magiskhidePreference.setEnabled(false);
|
|
||||||
hostsPreference.setEnabled(false);
|
|
||||||
} else {
|
|
||||||
busyboxPreference.setEnabled(true);
|
|
||||||
magiskhidePreference.setEnabled(true);
|
|
||||||
hostsPreference.setEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
|
||||||
Logger.dev("Settings: Prefs change " + key);
|
|
||||||
boolean checked;
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case "theme":
|
|
||||||
String theme = prefs.getString("theme", getString(R.string.theme_default_value));
|
|
||||||
if (Utils.isDarkTheme != theme.equalsIgnoreCase(getString(R.string.theme_dark_value))) {
|
|
||||||
Utils.isDarkTheme = !Utils.isDarkTheme;
|
|
||||||
getActivity().recreate();
|
|
||||||
MainActivity.recreate.trigger();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "magiskhide":
|
|
||||||
checked = prefs.getBoolean("magiskhide", false);
|
|
||||||
new Async.RootTask<Void, Void, Void>() {
|
|
||||||
private boolean enable = checked;
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... params) {
|
|
||||||
if (enable) {
|
|
||||||
Utils.createFile("/magisk/.core/magiskhide/enable");
|
|
||||||
} else {
|
|
||||||
Utils.removeItem("/magisk/.core/magiskhide/enable");
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
|
|
||||||
break;
|
|
||||||
case "busybox":
|
|
||||||
checked = prefs.getBoolean("busybox", false);
|
|
||||||
new Async.RootTask<Void, Void, Void>() {
|
|
||||||
private boolean enable = checked;
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... params) {
|
|
||||||
if (enable) {
|
|
||||||
Utils.createFile("/magisk/.core/busybox/enable");
|
|
||||||
} else {
|
|
||||||
Utils.removeItem("/magisk/.core/busybox/enable");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
|
|
||||||
break;
|
|
||||||
case "hosts":
|
|
||||||
checked = prefs.getBoolean("hosts", false);
|
|
||||||
new Async.RootTask<Void, Void, Void>() {
|
|
||||||
private boolean enable = checked;
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... voids) {
|
|
||||||
if (enable) {
|
|
||||||
Shell.su("cp -af /system/etc/hosts /magisk/.core/hosts",
|
|
||||||
"mount -o bind /magisk/.core/hosts /system/etc/hosts");
|
|
||||||
} else {
|
|
||||||
Shell.su("umount -l /system/etc/hosts",
|
|
||||||
"rm -f /magisk/.core/hosts");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
break;
|
|
||||||
case "developer_logging":
|
|
||||||
Logger.devLog = prefs.getBoolean("developer_logging", false);
|
|
||||||
break;
|
|
||||||
case "shell_logging":
|
|
||||||
Logger.logShell = prefs.getBoolean("shell_logging", false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.v7.app.AppCompatActivity;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.Async;
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
public class SplashActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplication());
|
|
||||||
|
|
||||||
String theme = prefs.getString("theme", getString(R.string.theme_default_value));
|
|
||||||
Utils.isDarkTheme = theme.equalsIgnoreCase(getString(R.string.theme_dark_value));
|
|
||||||
|
|
||||||
if (Utils.isDarkTheme) {
|
|
||||||
setTheme(R.style.AppTheme_dh);
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.devLog = prefs.getBoolean("developer_logging", false);
|
|
||||||
Logger.logShell = prefs.getBoolean("shell_logging", false);
|
|
||||||
|
|
||||||
// Initialize prefs
|
|
||||||
prefs.edit()
|
|
||||||
.putBoolean("magiskhide", Utils.itemExist(false, "/magisk/.core/magiskhide/enable"))
|
|
||||||
.putBoolean("busybox", Utils.commandExists("busybox"))
|
|
||||||
.putBoolean("hosts", Utils.itemExist(false, "/magisk/.core/hosts"))
|
|
||||||
.apply();
|
|
||||||
|
|
||||||
// Start all async tasks
|
|
||||||
new Async.GetBootBlocks().exec();
|
|
||||||
new Async.CheckUpdates().exec();
|
|
||||||
new Async.LoadModules() {
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
super.onPostExecute(v);
|
|
||||||
new Async.LoadRepos(getApplicationContext()).exec();
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
new Async.LoadApps(getPackageManager()).exec();
|
|
||||||
|
|
||||||
// Start main activity
|
|
||||||
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,306 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Fragment;
|
|
||||||
import android.app.FragmentTransaction;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.Async;
|
|
||||||
import com.topjohnwu.magisk.utils.CallbackHandler;
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.BindColor;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
|
|
||||||
public class StatusFragment extends Fragment implements CallbackHandler.EventListener {
|
|
||||||
|
|
||||||
public static double magiskVersion, remoteMagiskVersion = -1;
|
|
||||||
public static String magiskVersionString = "(none)", magiskLink, releaseNoteLink;
|
|
||||||
public static int SNCheckResult = -1;
|
|
||||||
|
|
||||||
private static boolean noDialog = false;
|
|
||||||
|
|
||||||
public static final CallbackHandler.Event updateCheckDone = new CallbackHandler.Event();
|
|
||||||
public static final CallbackHandler.Event safetyNetDone = new CallbackHandler.Event();
|
|
||||||
|
|
||||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
|
||||||
|
|
||||||
@BindView(R.id.magisk_status_container) View magiskStatusContainer;
|
|
||||||
@BindView(R.id.magisk_status_icon) ImageView magiskStatusIcon;
|
|
||||||
@BindView(R.id.magisk_version) TextView magiskVersionText;
|
|
||||||
@BindView(R.id.magisk_update_status) TextView magiskUpdateText;
|
|
||||||
@BindView(R.id.magisk_check_updates_progress) ProgressBar magiskCheckUpdatesProgress;
|
|
||||||
|
|
||||||
@BindView(R.id.root_status_container) View rootStatusContainer;
|
|
||||||
@BindView(R.id.root_status_icon) ImageView rootStatusIcon;
|
|
||||||
@BindView(R.id.root_status) TextView rootStatusText;
|
|
||||||
@BindView(R.id.root_info) TextView rootInfoText;
|
|
||||||
|
|
||||||
@BindView(R.id.safetyNet_container) View safetyNetContainer;
|
|
||||||
@BindView(R.id.safetyNet_icon) ImageView safetyNetIcon;
|
|
||||||
@BindView(R.id.safetyNet_status) TextView safetyNetStatusText;
|
|
||||||
@BindView(R.id.safetyNet_check_progress) ProgressBar safetyNetProgress;
|
|
||||||
|
|
||||||
@BindColor(R.color.red500) int colorBad;
|
|
||||||
@BindColor(R.color.green500) int colorOK;
|
|
||||||
@BindColor(R.color.yellow500) int colorWarn;
|
|
||||||
@BindColor(R.color.grey500) int colorNeutral;
|
|
||||||
@BindColor(R.color.blue500) int colorInfo;
|
|
||||||
@BindColor(android.R.color.transparent) int trans;
|
|
||||||
int defaultColor;
|
|
||||||
|
|
||||||
static {
|
|
||||||
checkMagiskInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
private AlertDialog updateMagisk;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
||||||
View v = inflater.inflate(R.layout.status_fragment, container, false);
|
|
||||||
ButterKnife.bind(this, v);
|
|
||||||
|
|
||||||
defaultColor = magiskUpdateText.getCurrentTextColor();
|
|
||||||
|
|
||||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
|
||||||
magiskStatusContainer.setBackgroundColor(trans);
|
|
||||||
magiskStatusIcon.setImageResource(0);
|
|
||||||
magiskUpdateText.setText(R.string.checking_for_updates);
|
|
||||||
magiskCheckUpdatesProgress.setVisibility(View.VISIBLE);
|
|
||||||
magiskUpdateText.setTextColor(defaultColor);
|
|
||||||
|
|
||||||
safetyNetProgress.setVisibility(View.GONE);
|
|
||||||
safetyNetContainer.setBackgroundColor(colorNeutral);
|
|
||||||
safetyNetIcon.setImageResource(R.drawable.ic_safetynet);
|
|
||||||
safetyNetStatusText.setText(R.string.safetyNet_check_text);
|
|
||||||
safetyNetStatusText.setTextColor(defaultColor);
|
|
||||||
|
|
||||||
safetyNetDone.isTriggered = false;
|
|
||||||
noDialog = false;
|
|
||||||
|
|
||||||
updateUI();
|
|
||||||
new Async.CheckUpdates().exec();
|
|
||||||
});
|
|
||||||
|
|
||||||
safetyNetContainer.setOnClickListener(view -> {
|
|
||||||
safetyNetProgress.setVisibility(View.VISIBLE);
|
|
||||||
safetyNetContainer.setBackgroundColor(trans);
|
|
||||||
safetyNetIcon.setImageResource(0);
|
|
||||||
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
|
|
||||||
Async.checkSafetyNet(getActivity());
|
|
||||||
});
|
|
||||||
|
|
||||||
if (magiskVersion < 0 && Shell.rootAccess() && !noDialog) {
|
|
||||||
noDialog = true;
|
|
||||||
Utils.getAlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.no_magisk_title)
|
|
||||||
.setMessage(R.string.no_magisk_msg)
|
|
||||||
.setCancelable(true)
|
|
||||||
.setPositiveButton(R.string.goto_install, (dialogInterface, i) -> {
|
|
||||||
((MainActivity) getActivity()).navigationView.setCheckedItem(R.id.install);
|
|
||||||
FragmentTransaction transaction = getFragmentManager().beginTransaction();
|
|
||||||
transaction.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
|
|
||||||
try {
|
|
||||||
transaction.replace(R.id.content_frame, new InstallFragment(), "install").commit();
|
|
||||||
} catch (IllegalStateException ignored) {}
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.no_thanks, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUI();
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrigger(CallbackHandler.Event event) {
|
|
||||||
if (event == updateCheckDone) {
|
|
||||||
Logger.dev("StatusFragment: Update Check UI refresh triggered");
|
|
||||||
updateCheckUI();
|
|
||||||
} else if (event == safetyNetDone) {
|
|
||||||
Logger.dev("StatusFragment: SafetyNet UI refresh triggered");
|
|
||||||
updateSafetyNetUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
CallbackHandler.register(updateCheckDone, this);
|
|
||||||
CallbackHandler.register(safetyNetDone, this);
|
|
||||||
if (updateCheckDone.isTriggered) {
|
|
||||||
updateCheckUI();
|
|
||||||
}
|
|
||||||
if (safetyNetDone.isTriggered) {
|
|
||||||
updateSafetyNetUI();
|
|
||||||
}
|
|
||||||
getActivity().setTitle(R.string.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
CallbackHandler.unRegister(updateCheckDone, this);
|
|
||||||
CallbackHandler.unRegister(safetyNetDone, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkMagiskInfo() {
|
|
||||||
List<String> ret = Shell.sh("getprop magisk.version");
|
|
||||||
if (ret.get(0).length() == 0) {
|
|
||||||
magiskVersion = -1;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
magiskVersionString = ret.get(0);
|
|
||||||
magiskVersion = Double.parseDouble(ret.get(0));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
// Custom version don't need to receive updates
|
|
||||||
magiskVersion = Double.POSITIVE_INFINITY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUI() {
|
|
||||||
int image, color;
|
|
||||||
|
|
||||||
checkMagiskInfo();
|
|
||||||
|
|
||||||
if (magiskVersion < 0) {
|
|
||||||
magiskVersionText.setText(R.string.magisk_version_error);
|
|
||||||
} else {
|
|
||||||
magiskVersionText.setText(getString(R.string.magisk_version, magiskVersionString));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Shell.rootStatus == 1) {
|
|
||||||
color = colorOK;
|
|
||||||
image = R.drawable.ic_check_circle;
|
|
||||||
rootStatusText.setText(R.string.proper_root);
|
|
||||||
rootInfoText.setText(Shell.sh("su -v").get(0));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
rootInfoText.setText(R.string.root_info_warning);
|
|
||||||
if (Shell.rootStatus == 0) {
|
|
||||||
color = colorBad;
|
|
||||||
image = R.drawable.ic_cancel;
|
|
||||||
rootStatusText.setText(R.string.not_rooted);
|
|
||||||
} else {
|
|
||||||
color = colorNeutral;
|
|
||||||
image = R.drawable.ic_help;
|
|
||||||
rootStatusText.setText(R.string.root_error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rootStatusContainer.setBackgroundColor(color);
|
|
||||||
rootStatusText.setTextColor(color);
|
|
||||||
rootInfoText.setTextColor(color);
|
|
||||||
rootStatusIcon.setImageResource(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateCheckUI() {
|
|
||||||
int image, color;
|
|
||||||
|
|
||||||
if (remoteMagiskVersion < 0) {
|
|
||||||
color = colorNeutral;
|
|
||||||
image = R.drawable.ic_help;
|
|
||||||
magiskUpdateText.setText(R.string.cannot_check_updates);
|
|
||||||
} else if (remoteMagiskVersion > magiskVersion) {
|
|
||||||
color = colorInfo;
|
|
||||||
image = R.drawable.ic_update;
|
|
||||||
magiskUpdateText.setText(getString(R.string.magisk_update_available, remoteMagiskVersion));
|
|
||||||
} else {
|
|
||||||
color = colorOK;
|
|
||||||
image = R.drawable.ic_check_circle;
|
|
||||||
magiskUpdateText.setText(getString(R.string.up_to_date, getString(R.string.magisk)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (magiskVersion < 0) {
|
|
||||||
color = colorBad;
|
|
||||||
image = R.drawable.ic_cancel;
|
|
||||||
}
|
|
||||||
magiskStatusContainer.setBackgroundColor(color);
|
|
||||||
magiskVersionText.setTextColor(color);
|
|
||||||
magiskUpdateText.setTextColor(color);
|
|
||||||
magiskStatusIcon.setImageResource(image);
|
|
||||||
|
|
||||||
magiskCheckUpdatesProgress.setVisibility(View.GONE);
|
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
|
||||||
|
|
||||||
updateMagisk = Utils.getAlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.magisk_update_title)
|
|
||||||
.setMessage(getString(R.string.magisk_update_message, remoteMagiskVersion))
|
|
||||||
.setCancelable(true)
|
|
||||||
.setPositiveButton(R.string.goto_install, (dialogInterface, i) -> {
|
|
||||||
((MainActivity) getActivity()).navigationView.setCheckedItem(R.id.install);
|
|
||||||
FragmentTransaction transaction = getFragmentManager().beginTransaction();
|
|
||||||
transaction.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
|
|
||||||
try {
|
|
||||||
transaction.replace(R.id.content_frame, new InstallFragment(), "install").commit();
|
|
||||||
} catch (IllegalStateException ignored) {}
|
|
||||||
})
|
|
||||||
.setNeutralButton(R.string.check_release_notes, (dialog, which) -> {
|
|
||||||
getActivity().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(releaseNoteLink)));
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.no_thanks, null)
|
|
||||||
.create();
|
|
||||||
|
|
||||||
if (magiskVersion < remoteMagiskVersion && Shell.rootAccess()) {
|
|
||||||
magiskStatusContainer.setOnClickListener(view -> updateMagisk.show());
|
|
||||||
if (!noDialog) {
|
|
||||||
noDialog = true;
|
|
||||||
updateMagisk.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSafetyNetUI() {
|
|
||||||
int image, color;
|
|
||||||
safetyNetProgress.setVisibility(View.GONE);
|
|
||||||
switch (SNCheckResult) {
|
|
||||||
case -3:
|
|
||||||
color = colorNeutral;
|
|
||||||
image = R.drawable.ic_help;
|
|
||||||
safetyNetStatusText.setText(R.string.safetyNet_connection_suspended);
|
|
||||||
break;
|
|
||||||
case -2:
|
|
||||||
color = colorNeutral;
|
|
||||||
image = R.drawable.ic_help;
|
|
||||||
safetyNetStatusText.setText(R.string.safetyNet_connection_failed);
|
|
||||||
break;
|
|
||||||
case -1:
|
|
||||||
color = colorNeutral;
|
|
||||||
image = R.drawable.ic_help;
|
|
||||||
safetyNetStatusText.setText(R.string.safetyNet_error);
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
color = colorBad;
|
|
||||||
image = R.drawable.ic_cancel;
|
|
||||||
safetyNetStatusText.setText(R.string.safetyNet_fail);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
default:
|
|
||||||
color = colorOK;
|
|
||||||
image = R.drawable.ic_check_circle;
|
|
||||||
safetyNetStatusText.setText(R.string.safetyNet_pass);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
safetyNetContainer.setBackgroundColor(color);
|
|
||||||
safetyNetStatusText.setTextColor(color);
|
|
||||||
safetyNetIcon.setImageResource(image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.adapters;
|
|
||||||
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.Filter;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.utils.Async;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
|
|
||||||
public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> {
|
|
||||||
|
|
||||||
private List<ApplicationInfo> mOriginalList, mList;
|
|
||||||
private List<String> mHideList;
|
|
||||||
private PackageManager packageManager;
|
|
||||||
private ApplicationFilter filter;
|
|
||||||
|
|
||||||
public ApplicationAdapter(PackageManager packageManager) {
|
|
||||||
mOriginalList = mList = Collections.emptyList();
|
|
||||||
mHideList = Collections.emptyList();
|
|
||||||
this.packageManager = packageManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLists(List<ApplicationInfo> listApps, List<String> hideList) {
|
|
||||||
mOriginalList = mList = Collections.unmodifiableList(listApps);
|
|
||||||
mHideList = new ArrayList<>(hideList);
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
|
||||||
View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_app, parent, false);
|
|
||||||
ButterKnife.bind(this, mView);
|
|
||||||
return new ViewHolder(mView);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
|
||||||
ApplicationInfo info = mList.get(position);
|
|
||||||
|
|
||||||
holder.appIcon.setImageDrawable(info.loadIcon(packageManager));
|
|
||||||
holder.appName.setText(info.loadLabel(packageManager));
|
|
||||||
holder.appPackage.setText(info.packageName);
|
|
||||||
|
|
||||||
holder.checkBox.setOnCheckedChangeListener(null);
|
|
||||||
holder.checkBox.setChecked(mHideList.contains(info.packageName));
|
|
||||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
|
||||||
if (isChecked) {
|
|
||||||
new Async.MagiskHide().add(info.packageName);
|
|
||||||
mHideList.add(info.packageName);
|
|
||||||
} else {
|
|
||||||
new Async.MagiskHide().rm(info.packageName);
|
|
||||||
mHideList.remove(info.packageName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return mList.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void filter(String constraint) {
|
|
||||||
if (filter == null) {
|
|
||||||
filter = new ApplicationFilter();
|
|
||||||
}
|
|
||||||
filter.filter(constraint);
|
|
||||||
}
|
|
||||||
|
|
||||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
@BindView(R.id.app_icon) ImageView appIcon;
|
|
||||||
@BindView(R.id.app_name) TextView appName;
|
|
||||||
@BindView(R.id.app_package) TextView appPackage;
|
|
||||||
@BindView(R.id.checkbox) CheckBox checkBox;
|
|
||||||
|
|
||||||
ViewHolder(View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
ButterKnife.bind(this, itemView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ApplicationFilter extends Filter {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected FilterResults performFiltering(CharSequence constraint) {
|
|
||||||
List<ApplicationInfo> filteredApps;
|
|
||||||
if (constraint == null || constraint.length() == 0) {
|
|
||||||
filteredApps = mOriginalList;
|
|
||||||
} else {
|
|
||||||
filteredApps = new ArrayList<>();
|
|
||||||
String filter = constraint.toString().toLowerCase();
|
|
||||||
for (ApplicationInfo info : mOriginalList) {
|
|
||||||
if (Utils.lowercaseContains(info.loadLabel(packageManager), filter)
|
|
||||||
|| Utils.lowercaseContains(info.packageName, filter)) {
|
|
||||||
filteredApps.add(info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FilterResults results = new FilterResults();
|
|
||||||
results.values = filteredApps;
|
|
||||||
results.count = filteredApps.size();
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
|
||||||
mList = (List<ApplicationInfo>) results.values;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.adapters;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.module.Repo;
|
|
||||||
import com.topjohnwu.magisk.receivers.RepoDlReceiver;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
import com.topjohnwu.magisk.utils.WebWindow;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
|
|
||||||
public class ReposAdapter extends RecyclerView.Adapter<ReposAdapter.ViewHolder> {
|
|
||||||
|
|
||||||
private List<Repo> mUpdateRepos, mInstalledRepos, mOthersRepos;
|
|
||||||
private HashSet<Repo> expandList = new HashSet<>();
|
|
||||||
|
|
||||||
public ReposAdapter(List<Repo> update, List<Repo> installed, List<Repo> others) {
|
|
||||||
mUpdateRepos = update;
|
|
||||||
mInstalledRepos = installed;
|
|
||||||
mOthersRepos = others;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
|
||||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_repo, parent, false);
|
|
||||||
ButterKnife.bind(this, v);
|
|
||||||
return new ViewHolder(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
|
||||||
Context context = holder.itemView.getContext();
|
|
||||||
Repo repo = getItem(position);
|
|
||||||
|
|
||||||
holder.title.setText(repo.getName());
|
|
||||||
holder.versionName.setText(repo.getVersion());
|
|
||||||
String author = repo.getAuthor();
|
|
||||||
holder.author.setText(TextUtils.isEmpty(author) ? null : context.getString(R.string.author, author));
|
|
||||||
holder.description.setText(repo.getDescription());
|
|
||||||
|
|
||||||
holder.setExpanded(expandList.contains(repo));
|
|
||||||
|
|
||||||
holder.itemView.setOnClickListener(view -> {
|
|
||||||
if (holder.mExpanded) {
|
|
||||||
holder.collapse();
|
|
||||||
expandList.remove(repo);
|
|
||||||
} else {
|
|
||||||
holder.expand();
|
|
||||||
expandList.add(repo);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
holder.changeLog.setOnClickListener(view -> {
|
|
||||||
if (!TextUtils.isEmpty(repo.getLogUrl())) {
|
|
||||||
new WebWindow(context.getString(R.string.changelog), repo.getLogUrl(), context);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
holder.updateImage.setOnClickListener(view -> {
|
|
||||||
String filename = repo.getName() + "-" + repo.getVersion() + ".zip";
|
|
||||||
Utils.getAlertDialogBuilder(context)
|
|
||||||
.setTitle(context.getString(R.string.repo_install_title, repo.getName()))
|
|
||||||
.setMessage(context.getString(R.string.repo_install_msg, filename))
|
|
||||||
.setCancelable(true)
|
|
||||||
.setPositiveButton(R.string.download_install, (dialogInterface, i) -> Utils.dlAndReceive(
|
|
||||||
context,
|
|
||||||
new RepoDlReceiver(),
|
|
||||||
repo.getZipUrl(),
|
|
||||||
Utils.getLegalFilename(filename)))
|
|
||||||
.setNegativeButton(R.string.no_thanks, null)
|
|
||||||
.show();
|
|
||||||
});
|
|
||||||
holder.authorLink.setOnClickListener(view -> {
|
|
||||||
if (!TextUtils.isEmpty(repo.getDonateUrl())) {
|
|
||||||
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(repo.getDonateUrl())));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
holder.supportLink.setOnClickListener(view -> {
|
|
||||||
if (!TextUtils.isEmpty(repo.getSupportUrl())) {
|
|
||||||
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(repo.getSupportUrl())));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return mUpdateRepos.size() + mInstalledRepos.size() + mOthersRepos.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Repo getItem(int position) {
|
|
||||||
if (position >= mUpdateRepos.size()) {
|
|
||||||
position -= mUpdateRepos.size();
|
|
||||||
if (position >= mInstalledRepos.size()) {
|
|
||||||
position -= mInstalledRepos.size();
|
|
||||||
return mOthersRepos.get(position);
|
|
||||||
} else {
|
|
||||||
return mInstalledRepos.get(position);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return mUpdateRepos.get(position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
@BindView(R.id.title) TextView title;
|
|
||||||
@BindView(R.id.version_name) TextView versionName;
|
|
||||||
@BindView(R.id.description) TextView description;
|
|
||||||
@BindView(R.id.author) TextView author;
|
|
||||||
@BindView(R.id.expand_layout) LinearLayout expandLayout;
|
|
||||||
@BindView(R.id.update) ImageView updateImage;
|
|
||||||
@BindView(R.id.changeLog) ImageView changeLog;
|
|
||||||
@BindView(R.id.authorLink) ImageView authorLink;
|
|
||||||
@BindView(R.id.supportLink) ImageView supportLink;
|
|
||||||
|
|
||||||
private ValueAnimator mAnimator;
|
|
||||||
private ObjectAnimator animY2;
|
|
||||||
private boolean mExpanded = false;
|
|
||||||
private static int expandHeight = 0;
|
|
||||||
|
|
||||||
ViewHolder(View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
ButterKnife.bind(this, itemView);
|
|
||||||
expandLayout.getViewTreeObserver().addOnPreDrawListener(
|
|
||||||
new ViewTreeObserver.OnPreDrawListener() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPreDraw() {
|
|
||||||
if (expandHeight == 0) {
|
|
||||||
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
|
||||||
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
|
||||||
expandLayout.measure(widthSpec, heightSpec);
|
|
||||||
expandHeight = expandLayout.getMeasuredHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
expandLayout.getViewTreeObserver().removeOnPreDrawListener(this);
|
|
||||||
expandLayout.setVisibility(View.GONE);
|
|
||||||
mAnimator = slideAnimator(0, expandHeight);
|
|
||||||
animY2 = ObjectAnimator.ofFloat(updateImage, "translationY", expandHeight / 2);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setExpanded(boolean expanded) {
|
|
||||||
mExpanded = expanded;
|
|
||||||
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
|
|
||||||
layoutParams.height = expanded ? expandHeight : 0;
|
|
||||||
expandLayout.setLayoutParams(layoutParams);
|
|
||||||
expandLayout.setVisibility(expanded ? View.VISIBLE : View.GONE);
|
|
||||||
if (expanded) {
|
|
||||||
updateImage.setTranslationY(expandHeight / 2);
|
|
||||||
} else {
|
|
||||||
updateImage.setTranslationY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expand() {
|
|
||||||
expandLayout.setVisibility(View.VISIBLE);
|
|
||||||
mAnimator.start();
|
|
||||||
animY2.start();
|
|
||||||
mExpanded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void collapse() {
|
|
||||||
if (!mExpanded) return;
|
|
||||||
int finalHeight = expandLayout.getHeight();
|
|
||||||
ValueAnimator mAnimator = slideAnimator(finalHeight, 0);
|
|
||||||
mAnimator.addListener(new Animator.AnimatorListener() {
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animator) {
|
|
||||||
expandLayout.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationStart(Animator animator) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationCancel(Animator animator) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationRepeat(Animator animator) {}
|
|
||||||
});
|
|
||||||
mAnimator.start();
|
|
||||||
animY2.reverse();
|
|
||||||
mExpanded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValueAnimator slideAnimator(int start, int end) {
|
|
||||||
|
|
||||||
ValueAnimator animator = ValueAnimator.ofInt(start, end);
|
|
||||||
|
|
||||||
animator.addUpdateListener(valueAnimator -> {
|
|
||||||
int value = (Integer) valueAnimator.getAnimatedValue();
|
|
||||||
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
|
|
||||||
layoutParams.height = value;
|
|
||||||
expandLayout.setLayoutParams(layoutParams);
|
|
||||||
});
|
|
||||||
return animator;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.adapters;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.util.SparseArray;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
public class SimpleSectionedRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
|
||||||
|
|
||||||
private final Context mContext;
|
|
||||||
private static final int SECTION_TYPE = 0;
|
|
||||||
|
|
||||||
private boolean mValid = true;
|
|
||||||
private int mSectionResourceId;
|
|
||||||
private int mTextResourceId;
|
|
||||||
private LayoutInflater mLayoutInflater;
|
|
||||||
private RecyclerView.Adapter mBaseAdapter;
|
|
||||||
private SparseArray<Section> mSections = new SparseArray<Section>();
|
|
||||||
|
|
||||||
|
|
||||||
public SimpleSectionedRecyclerViewAdapter(Context context, int sectionResourceId, int textResourceId,
|
|
||||||
RecyclerView.Adapter baseAdapter) {
|
|
||||||
|
|
||||||
mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
|
||||||
mSectionResourceId = sectionResourceId;
|
|
||||||
mTextResourceId = textResourceId;
|
|
||||||
mBaseAdapter = baseAdapter;
|
|
||||||
mContext = context;
|
|
||||||
|
|
||||||
mBaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
|
||||||
@Override
|
|
||||||
public void onChanged() {
|
|
||||||
mValid = mBaseAdapter.getItemCount()>0;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemRangeChanged(int positionStart, int itemCount) {
|
|
||||||
mValid = mBaseAdapter.getItemCount()>0;
|
|
||||||
notifyItemRangeChanged(positionStart, itemCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
|
||||||
mValid = mBaseAdapter.getItemCount()>0;
|
|
||||||
notifyItemRangeInserted(positionStart, itemCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemRangeRemoved(int positionStart, int itemCount) {
|
|
||||||
mValid = mBaseAdapter.getItemCount()>0;
|
|
||||||
notifyItemRangeRemoved(positionStart, itemCount);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class SectionViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
public TextView title;
|
|
||||||
|
|
||||||
public SectionViewHolder(View view, int mTextResourceid) {
|
|
||||||
super(view);
|
|
||||||
title = (TextView) view.findViewById(mTextResourceid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int typeView) {
|
|
||||||
if (typeView == SECTION_TYPE) {
|
|
||||||
final View view = LayoutInflater.from(mContext).inflate(mSectionResourceId, parent, false);
|
|
||||||
return new SectionViewHolder(view,mTextResourceId);
|
|
||||||
}else{
|
|
||||||
return mBaseAdapter.onCreateViewHolder(parent, typeView -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(RecyclerView.ViewHolder sectionViewHolder, int position) {
|
|
||||||
if (isSectionHeaderPosition(position)) {
|
|
||||||
((SectionViewHolder)sectionViewHolder).title.setText(mSections.get(position).title);
|
|
||||||
}else{
|
|
||||||
mBaseAdapter.onBindViewHolder(sectionViewHolder,sectionedPositionToPosition(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(int position) {
|
|
||||||
return isSectionHeaderPosition(position)
|
|
||||||
? SECTION_TYPE
|
|
||||||
: mBaseAdapter.getItemViewType(sectionedPositionToPosition(position)) +1 ;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class Section {
|
|
||||||
int firstPosition;
|
|
||||||
int sectionedPosition;
|
|
||||||
CharSequence title;
|
|
||||||
|
|
||||||
public Section(int firstPosition, CharSequence title) {
|
|
||||||
this.firstPosition = firstPosition;
|
|
||||||
this.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CharSequence getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setSections(Section[] sections) {
|
|
||||||
mSections.clear();
|
|
||||||
|
|
||||||
Arrays.sort(sections, new Comparator<Section>() {
|
|
||||||
@Override
|
|
||||||
public int compare(Section o, Section o1) {
|
|
||||||
return (o.firstPosition == o1.firstPosition)
|
|
||||||
? 0
|
|
||||||
: ((o.firstPosition < o1.firstPosition) ? -1 : 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
int offset = 0; // offset positions for the headers we're adding
|
|
||||||
for (Section section : sections) {
|
|
||||||
section.sectionedPosition = section.firstPosition + offset;
|
|
||||||
mSections.append(section.sectionedPosition, section);
|
|
||||||
++offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int positionToSectionedPosition(int position) {
|
|
||||||
int offset = 0;
|
|
||||||
for (int i = 0; i < mSections.size(); i++) {
|
|
||||||
if (mSections.valueAt(i).firstPosition > position) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++offset;
|
|
||||||
}
|
|
||||||
return position + offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int sectionedPositionToPosition(int sectionedPosition) {
|
|
||||||
if (isSectionHeaderPosition(sectionedPosition)) {
|
|
||||||
return RecyclerView.NO_POSITION;
|
|
||||||
}
|
|
||||||
|
|
||||||
int offset = 0;
|
|
||||||
for (int i = 0; i < mSections.size(); i++) {
|
|
||||||
if (mSections.valueAt(i).sectionedPosition > sectionedPosition) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
--offset;
|
|
||||||
}
|
|
||||||
return sectionedPosition + offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSectionHeaderPosition(int position) {
|
|
||||||
return mSections.get(position) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getItemId(int position) {
|
|
||||||
return isSectionHeaderPosition(position)
|
|
||||||
? Integer.MAX_VALUE - mSections.indexOfKey(position)
|
|
||||||
: mBaseAdapter.getItemId(sectionedPositionToPosition(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return (mValid ? mBaseAdapter.getItemCount() + mSections.size() : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.module;
|
|
||||||
|
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public abstract class BaseModule implements Comparable<BaseModule> {
|
|
||||||
|
|
||||||
protected String mId, mName, mVersion, mAuthor, mDescription, mSupportUrl, mDonateUrl;
|
|
||||||
protected boolean mIsCacheModule = false;
|
|
||||||
protected int mVersionCode = 0;
|
|
||||||
|
|
||||||
protected void parseProps(List<String> props) throws CacheModException { parseProps(props.toArray(new String[props.size()])); }
|
|
||||||
|
|
||||||
protected void parseProps(String[] props) throws CacheModException {
|
|
||||||
for (String line : props) {
|
|
||||||
String[] prop = line.split("=", 2);
|
|
||||||
if (prop.length != 2) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String key = prop[0].trim();
|
|
||||||
if (key.charAt(0) == '#') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case "id":
|
|
||||||
this.mId = prop[1];
|
|
||||||
break;
|
|
||||||
case "name":
|
|
||||||
this.mName = prop[1];
|
|
||||||
break;
|
|
||||||
case "version":
|
|
||||||
this.mVersion = prop[1];
|
|
||||||
break;
|
|
||||||
case "versionCode":
|
|
||||||
try {
|
|
||||||
this.mVersionCode = Integer.parseInt(prop[1]);
|
|
||||||
} catch (NumberFormatException ignored) {}
|
|
||||||
break;
|
|
||||||
case "author":
|
|
||||||
this.mAuthor = prop[1];
|
|
||||||
break;
|
|
||||||
case "description":
|
|
||||||
this.mDescription = prop[1];
|
|
||||||
break;
|
|
||||||
case "support":
|
|
||||||
this.mSupportUrl = prop[1];
|
|
||||||
break;
|
|
||||||
case "donate":
|
|
||||||
this.mDonateUrl = prop[1];
|
|
||||||
break;
|
|
||||||
case "cacheModule":
|
|
||||||
this.mIsCacheModule = Boolean.parseBoolean(prop[1]);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mIsCacheModule)
|
|
||||||
throw new CacheModException(mId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return mName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVersion() {
|
|
||||||
return mVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAuthor() {
|
|
||||||
return mAuthor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {return mId; }
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return mDescription;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getVersionCode() {
|
|
||||||
return mVersionCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDonateUrl() {
|
|
||||||
return mDonateUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSupportUrl() {
|
|
||||||
return mSupportUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CacheModException extends Exception {
|
|
||||||
public CacheModException(String id) {
|
|
||||||
Logger.dev("Cache mods are no longer supported! id: " + id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(@NonNull BaseModule o) {
|
|
||||||
return this.getName().toLowerCase().compareTo(o.getName().toLowerCase());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.module;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.WebService;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class Repo extends BaseModule {
|
|
||||||
private String mLogUrl, mManifestUrl, mZipUrl;
|
|
||||||
private Date mLastUpdate;
|
|
||||||
|
|
||||||
public Repo(Context context, String name, Date lastUpdate) throws CacheModException {
|
|
||||||
mLastUpdate = lastUpdate;
|
|
||||||
mLogUrl = context.getString(R.string.file_url, name, "changelog.txt");
|
|
||||||
mManifestUrl = context.getString(R.string.file_url, name, "module.prop");
|
|
||||||
mZipUrl = context.getString(R.string.zip_url, name);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update() throws CacheModException {
|
|
||||||
Logger.dev("Repo: Re-fetch prop");
|
|
||||||
String props = WebService.request(mManifestUrl, WebService.GET, true);
|
|
||||||
String lines[] = props.split("\\n");
|
|
||||||
parseProps(lines);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update(Date lastUpdate) throws CacheModException {
|
|
||||||
Logger.dev("Repo: Old: " + mLastUpdate);
|
|
||||||
Logger.dev("Repo: New: " + lastUpdate);
|
|
||||||
if (mIsCacheModule)
|
|
||||||
throw new CacheModException(mId);
|
|
||||||
if (lastUpdate.after(mLastUpdate)) {
|
|
||||||
mLastUpdate = lastUpdate;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getZipUrl() {
|
|
||||||
return mZipUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLogUrl() {
|
|
||||||
return mLogUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getManifestUrl() {
|
|
||||||
return mManifestUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getLastUpdate() {
|
|
||||||
return mLastUpdate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.receivers;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.StatusFragment;
|
|
||||||
import com.topjohnwu.magisk.utils.Async;
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
|
||||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class MagiskDlReceiver extends DownloadReceiver {
|
|
||||||
|
|
||||||
String mBoot;
|
|
||||||
boolean mEnc, mVerity;
|
|
||||||
|
|
||||||
public MagiskDlReceiver(String bootImage, boolean keepEnc, boolean keepVerity) {
|
|
||||||
mBoot = bootImage;
|
|
||||||
mEnc = keepEnc;
|
|
||||||
mVerity = keepVerity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDownloadDone(Uri uri) {
|
|
||||||
new Async.FlashZIP(mContext, uri, mFilename) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void preProcessing() throws Throwable {
|
|
||||||
Shell.su(
|
|
||||||
"echo \"BOOTIMAGE=/dev/block/" + mBoot + "\" > /dev/.magisk",
|
|
||||||
"echo \"KEEPFORCEENCRYPT=" + String.valueOf(mEnc) + "\" >> /dev/.magisk",
|
|
||||||
"echo \"KEEPVERITY=" + String.valueOf(mVerity) + "\" >> /dev/.magisk"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean unzipAndCheck() {
|
|
||||||
publishProgress(mContext.getString(R.string.zip_install_unzip_zip_msg));
|
|
||||||
if (Shell.rootAccess()) {
|
|
||||||
// We might not have busybox yet, unzip with Java
|
|
||||||
// We will have complete busybox after Magisk installation
|
|
||||||
ZipUtils.unzip(mCachedFile, new File(mCachedFile.getParent(), "magisk"));
|
|
||||||
Shell.su(
|
|
||||||
"mkdir -p " + Async.TMP_FOLDER_PATH + "/magisk",
|
|
||||||
"cp -af " + mCachedFile.getParent() + "/magisk/. " + Async.TMP_FOLDER_PATH + "/magisk",
|
|
||||||
"mv -f " + mCachedFile.getParent() + "/magisk/META-INF " + mCachedFile.getParent() + "/META-INF"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSuccess() {
|
|
||||||
new Async.RootTask<Void, Void, Void>() {
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... params) {
|
|
||||||
Shell.su("setprop magisk.version "
|
|
||||||
+ String.valueOf(StatusFragment.remoteMagiskVersion));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
super.onSuccess();
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.receivers;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.utils.Async;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils.ByteArrayInOutStream;
|
|
||||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
|
||||||
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public class RepoDlReceiver extends DownloadReceiver {
|
|
||||||
@Override
|
|
||||||
public void onDownloadDone(Uri uri) {
|
|
||||||
// Flash the zip
|
|
||||||
new Async.FlashZIP(mContext, uri, mFilename){
|
|
||||||
@Override
|
|
||||||
protected void preProcessing() throws Throwable {
|
|
||||||
// Process and sign the zip
|
|
||||||
publishProgress(mContext.getString(R.string.zip_install_process_zip_msg));
|
|
||||||
ByteArrayInOutStream buffer = new ByteArrayInOutStream();
|
|
||||||
|
|
||||||
// First remove top folder (the folder with the repo name) in Github source zip
|
|
||||||
ZipUtils.removeTopFolder(mContext.getContentResolver().openInputStream(mUri), buffer);
|
|
||||||
|
|
||||||
// Then sign the zip for the first time
|
|
||||||
ZipUtils.signZip(mContext, buffer.getInputStream(), buffer, false);
|
|
||||||
|
|
||||||
// Adjust the zip to prevent unzip issues
|
|
||||||
ZipUtils.adjustZip(buffer);
|
|
||||||
|
|
||||||
// Finally, sign the whole zip file again
|
|
||||||
ZipUtils.signZip(mContext, buffer.getInputStream(), buffer, true);
|
|
||||||
|
|
||||||
// Write it back to the downloaded zip
|
|
||||||
OutputStream out = mContext.getContentResolver().openOutputStream(mUri);
|
|
||||||
buffer.writeTo(out);
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,340 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.provider.OpenableColumns;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.InstallFragment;
|
|
||||||
import com.topjohnwu.magisk.MagiskHideFragment;
|
|
||||||
import com.topjohnwu.magisk.ModulesFragment;
|
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.ReposFragment;
|
|
||||||
import com.topjohnwu.magisk.StatusFragment;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class Async {
|
|
||||||
|
|
||||||
public abstract static class RootTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
|
|
||||||
@SafeVarargs
|
|
||||||
public final void exec(Params... params) {
|
|
||||||
if (!Shell.rootAccess()) return;
|
|
||||||
executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract static class NormalTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
|
|
||||||
@SafeVarargs
|
|
||||||
public final void exec(Params... params) {
|
|
||||||
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final String UPDATE_JSON = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/updates/magisk_update.json";
|
|
||||||
public static final String MAGISK_HIDE_PATH = "/magisk/.core/magiskhide/";
|
|
||||||
public static final String TMP_FOLDER_PATH = "/dev/tmp";
|
|
||||||
|
|
||||||
public static class CheckUpdates extends NormalTask<Void, Void, Void> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... voids) {
|
|
||||||
String jsonStr = WebService.request(UPDATE_JSON, WebService.GET);
|
|
||||||
try {
|
|
||||||
JSONObject json = new JSONObject(jsonStr);
|
|
||||||
JSONObject magisk = json.getJSONObject("magisk");
|
|
||||||
StatusFragment.remoteMagiskVersion = magisk.getDouble("versionCode");
|
|
||||||
StatusFragment.magiskLink = magisk.getString("link");
|
|
||||||
StatusFragment.releaseNoteLink = magisk.getString("note");
|
|
||||||
} catch (JSONException ignored) {}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
StatusFragment.updateCheckDone.trigger();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void checkSafetyNet(Context context) {
|
|
||||||
new SafetyNetHelper(context) {
|
|
||||||
@Override
|
|
||||||
public void handleResults(int i) {
|
|
||||||
StatusFragment.SNCheckResult = i;
|
|
||||||
StatusFragment.safetyNetDone.trigger();
|
|
||||||
}
|
|
||||||
}.requestTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class LoadModules extends RootTask<Void, Void, Void> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... voids) {
|
|
||||||
ModuleHelper.createModuleMap();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
ModulesFragment.moduleLoadDone.trigger();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class LoadRepos extends NormalTask<Void, Void, Void> {
|
|
||||||
|
|
||||||
private Context mContext;
|
|
||||||
|
|
||||||
public LoadRepos(Context context) {
|
|
||||||
mContext = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... voids) {
|
|
||||||
ModuleHelper.createRepoMap(mContext);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
ReposFragment.repoLoadDone.trigger();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class LoadApps extends RootTask<Void, Void, LoadApps.Result> {
|
|
||||||
|
|
||||||
private PackageManager pm;
|
|
||||||
|
|
||||||
public LoadApps(PackageManager packageManager) {
|
|
||||||
pm = packageManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Result doInBackground(Void... voids) {
|
|
||||||
List<ApplicationInfo> listApps = pm.getInstalledApplications(PackageManager.GET_META_DATA);
|
|
||||||
for (Iterator<ApplicationInfo> i = listApps.iterator(); i.hasNext(); ) {
|
|
||||||
ApplicationInfo info = i.next();
|
|
||||||
if (MagiskHideFragment.BLACKLIST.contains(info.packageName) || !info.enabled)
|
|
||||||
i.remove();
|
|
||||||
}
|
|
||||||
Collections.sort(listApps, (a, b) -> a.loadLabel(pm).toString().toLowerCase()
|
|
||||||
.compareTo(b.loadLabel(pm).toString().toLowerCase()));
|
|
||||||
List<String> hideList = Shell.su(Async.MAGISK_HIDE_PATH + "list");
|
|
||||||
return new Result(listApps, hideList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Result result) {
|
|
||||||
MagiskHideFragment.packageLoadDone.trigger(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Result {
|
|
||||||
|
|
||||||
public final List<ApplicationInfo> listApps;
|
|
||||||
public final List<String> hideList;
|
|
||||||
|
|
||||||
Result(List<ApplicationInfo> listApps, List<String> hideList) {
|
|
||||||
this.listApps = listApps;
|
|
||||||
this.hideList = hideList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class FlashZIP extends RootTask<Void, String, Integer> {
|
|
||||||
|
|
||||||
protected Uri mUri;
|
|
||||||
protected File mCachedFile;
|
|
||||||
private String mFilename;
|
|
||||||
protected ProgressDialog progress;
|
|
||||||
private Context mContext;
|
|
||||||
|
|
||||||
public FlashZIP(Context context, Uri uri, String filename) {
|
|
||||||
mContext = context;
|
|
||||||
mUri = uri;
|
|
||||||
mFilename = filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FlashZIP(Context context, Uri uri) {
|
|
||||||
mContext = context;
|
|
||||||
mUri = uri;
|
|
||||||
|
|
||||||
// Try to get the filename ourselves
|
|
||||||
Cursor c = mContext.getContentResolver().query(uri, null, null, null, null);
|
|
||||||
if (c != null) {
|
|
||||||
int nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
|
||||||
c.moveToFirst();
|
|
||||||
if (nameIndex != -1) {
|
|
||||||
mFilename = c.getString(nameIndex);
|
|
||||||
}
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
if (mFilename == null) {
|
|
||||||
int idx = uri.getPath().lastIndexOf('/');
|
|
||||||
mFilename = uri.getPath().substring(idx + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void preProcessing() throws Throwable {}
|
|
||||||
|
|
||||||
protected void copyToCache() throws Throwable {
|
|
||||||
publishProgress(mContext.getString(R.string.copying_msg));
|
|
||||||
try {
|
|
||||||
InputStream in = mContext.getContentResolver().openInputStream(mUri);
|
|
||||||
mCachedFile = new File(mContext.getCacheDir().getAbsolutePath() + "/install.zip");
|
|
||||||
if (mCachedFile.exists() && !mCachedFile.delete()) {
|
|
||||||
throw new IOException();
|
|
||||||
}
|
|
||||||
OutputStream outputStream = new FileOutputStream(mCachedFile);
|
|
||||||
byte buffer[] = new byte[1024];
|
|
||||||
int length;
|
|
||||||
while ((length = in.read(buffer)) > 0) {
|
|
||||||
outputStream.write(buffer, 0, length);
|
|
||||||
}
|
|
||||||
outputStream.close();
|
|
||||||
Logger.dev("FlashZip: File created successfully - " + mCachedFile.getPath());
|
|
||||||
in.close();
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
Log.e(Logger.TAG, "FlashZip: Invalid Uri");
|
|
||||||
throw e;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(Logger.TAG, "FlashZip: Error in creating file");
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean unzipAndCheck() {
|
|
||||||
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android");
|
|
||||||
return Utils.readFile(mCachedFile.getParent() + "/META-INF/com/google/android/updater-script")
|
|
||||||
.get(0).contains("#MAGISK");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
progress = new ProgressDialog(mContext);
|
|
||||||
progress.setTitle(R.string.zip_install_progress_title);
|
|
||||||
progress.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onProgressUpdate(String... values) {
|
|
||||||
progress.setMessage(values[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Integer doInBackground(Void... voids) {
|
|
||||||
Logger.dev("FlashZip Running... " + mFilename);
|
|
||||||
List<String> ret;
|
|
||||||
try {
|
|
||||||
preProcessing();
|
|
||||||
copyToCache();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (!unzipAndCheck()) return 0;
|
|
||||||
publishProgress(mContext.getString(R.string.zip_install_progress_msg, mFilename));
|
|
||||||
ret = Shell.su(
|
|
||||||
"BOOTMODE=true sh " + mCachedFile.getParent() +
|
|
||||||
"/META-INF/com/google/android/update-binary dummy 1 " + mCachedFile.getPath(),
|
|
||||||
"if [ $? -eq 0 ]; then echo true; else echo false; fi"
|
|
||||||
);
|
|
||||||
Logger.dev("FlashZip: Console log:");
|
|
||||||
for (String line : ret) {
|
|
||||||
Logger.dev(line);
|
|
||||||
}
|
|
||||||
Shell.su(
|
|
||||||
"rm -rf " + mCachedFile.getParent() + "/*",
|
|
||||||
"rm -rf " + TMP_FOLDER_PATH
|
|
||||||
);
|
|
||||||
if (Boolean.parseBoolean(ret.get(ret.size() - 1))) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -1 = error, manual install; 0 = invalid zip; 1 = success
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Integer result) {
|
|
||||||
super.onPostExecute(result);
|
|
||||||
progress.dismiss();
|
|
||||||
switch (result) {
|
|
||||||
case -1:
|
|
||||||
Toast.makeText(mContext, mContext.getString(R.string.install_error), Toast.LENGTH_LONG).show();
|
|
||||||
Toast.makeText(mContext, mContext.getString(R.string.manual_install_1, mUri.getPath()), Toast.LENGTH_LONG).show();
|
|
||||||
Toast.makeText(mContext, mContext.getString(R.string.manual_install_2), Toast.LENGTH_LONG).show();
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
Toast.makeText(mContext, mContext.getString(R.string.invalid_zip), Toast.LENGTH_LONG).show();
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
onSuccess();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onSuccess() {
|
|
||||||
StatusFragment.updateCheckDone.trigger();
|
|
||||||
new LoadModules().exec();
|
|
||||||
|
|
||||||
Utils.getAlertDialogBuilder(mContext)
|
|
||||||
.setTitle(R.string.reboot_title)
|
|
||||||
.setMessage(R.string.reboot_msg)
|
|
||||||
.setPositiveButton(R.string.reboot, (dialogInterface1, i) -> Shell.sh("su -c reboot"))
|
|
||||||
.setNegativeButton(R.string.no_thanks, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class MagiskHide extends RootTask<Object, Void, Void> {
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Object... params) {
|
|
||||||
boolean add = (boolean) params[0];
|
|
||||||
String packageName = (String) params[1];
|
|
||||||
Shell.su(MAGISK_HIDE_PATH + (add ? "add " : "rm ") + packageName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(CharSequence packageName) {
|
|
||||||
exec(true, packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void rm(CharSequence packageName) {
|
|
||||||
exec(false, packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class GetBootBlocks extends RootTask<Void, Void, Void> {
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... params) {
|
|
||||||
if (Shell.rootAccess()) {
|
|
||||||
InstallFragment.blockList = Shell.su("ls /dev/block | grep mmc");
|
|
||||||
if (InstallFragment.bootBlock == null) {
|
|
||||||
InstallFragment.bootBlock = Utils.detectBootImage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void aVoid) {
|
|
||||||
InstallFragment.blockDetectionDone.trigger();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
public class CallbackHandler {
|
|
||||||
|
|
||||||
private static HashMap<Event, HashSet<EventListener>> listeners = new HashMap<>();
|
|
||||||
|
|
||||||
public static void register(Event event, EventListener listener) {
|
|
||||||
HashSet<EventListener> list = listeners.get(event);
|
|
||||||
if (list == null) {
|
|
||||||
list = new HashSet<>();
|
|
||||||
listeners.put(event, list);
|
|
||||||
}
|
|
||||||
list.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void unRegister(Event event, EventListener listener) {
|
|
||||||
HashSet<EventListener> list = listeners.get(event);
|
|
||||||
if (list != null) {
|
|
||||||
list.remove(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void triggerCallback(Event event) {
|
|
||||||
HashSet<EventListener> list = listeners.get(event);
|
|
||||||
if (list != null) {
|
|
||||||
for (EventListener listener : list) {
|
|
||||||
listener.onTrigger(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Event {
|
|
||||||
|
|
||||||
public boolean isTriggered = false;
|
|
||||||
private Object result;
|
|
||||||
|
|
||||||
public void trigger() {
|
|
||||||
trigger(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void trigger(Object result) {
|
|
||||||
this.result = result;
|
|
||||||
isTriggered = true;
|
|
||||||
triggerCallback(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getResult() {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface EventListener {
|
|
||||||
void onTrigger(Event event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public class Logger {
|
|
||||||
|
|
||||||
public static final String TAG = "Magisk";
|
|
||||||
public static final String DEV_TAG = "Magisk: DEV";
|
|
||||||
public static final String DEBUG_TAG = "Magisk: DEBUG";
|
|
||||||
|
|
||||||
public static boolean logShell, devLog;
|
|
||||||
|
|
||||||
public static void debug(String msg) {
|
|
||||||
Log.d(DEBUG_TAG, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void dev(String msg, Object... args) {
|
|
||||||
if (devLog) {
|
|
||||||
if (args.length == 1 && args[0] instanceof Throwable) {
|
|
||||||
Log.d(DEV_TAG, msg, (Throwable) args[0]);
|
|
||||||
} else {
|
|
||||||
Log.d(DEV_TAG, String.format(msg, args));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void dev(String msg) {
|
|
||||||
if (devLog) {
|
|
||||||
Log.d(DEV_TAG, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void shell(boolean root, String msg) {
|
|
||||||
if (logShell) {
|
|
||||||
Log.d(root ? "SU" : "SH", msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.module.BaseModule;
|
|
||||||
import com.topjohnwu.magisk.module.Module;
|
|
||||||
import com.topjohnwu.magisk.module.Repo;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class ModuleHelper {
|
|
||||||
private static final String MAGISK_PATH = "/magisk";
|
|
||||||
public static final String FILE_KEY = "RepoMap";
|
|
||||||
private static final String REPO_KEY = "repomap";
|
|
||||||
public static final String VERSION_KEY = "version";
|
|
||||||
public static final String ETAG_KEY = "ETag";
|
|
||||||
private static final int DATABASE_VER = 1;
|
|
||||||
|
|
||||||
private static ValueSortedMap<String, Repo> repoMap = new ValueSortedMap<>();
|
|
||||||
private static ValueSortedMap<String, Module> moduleMap = new ValueSortedMap<>();
|
|
||||||
|
|
||||||
|
|
||||||
public static void createModuleMap() {
|
|
||||||
Logger.dev("ModuleHelper: Loading modules");
|
|
||||||
|
|
||||||
moduleMap.clear();
|
|
||||||
|
|
||||||
for (String path : Utils.getModList(MAGISK_PATH)) {
|
|
||||||
Logger.dev("ModuleHelper: Adding modules from " + path);
|
|
||||||
Module module;
|
|
||||||
try {
|
|
||||||
module = new Module(path);
|
|
||||||
moduleMap.put(module.getId(), module);
|
|
||||||
} catch (BaseModule.CacheModException ignored) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.dev("ModuleHelper: Module load done");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void createRepoMap(Context context) {
|
|
||||||
Logger.dev("ModuleHelper: Loading repos");
|
|
||||||
|
|
||||||
SharedPreferences prefs = context.getSharedPreferences(FILE_KEY, Context.MODE_PRIVATE);
|
|
||||||
|
|
||||||
repoMap.clear();
|
|
||||||
|
|
||||||
Gson gson = new Gson();
|
|
||||||
String jsonString;
|
|
||||||
|
|
||||||
int cachedVersion = prefs.getInt(VERSION_KEY, 0);
|
|
||||||
if (cachedVersion != DATABASE_VER) {
|
|
||||||
// Ignore incompatible cached database
|
|
||||||
jsonString = null;
|
|
||||||
} else {
|
|
||||||
jsonString = prefs.getString(REPO_KEY, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
ValueSortedMap<String, Repo> cached = null;
|
|
||||||
|
|
||||||
if (jsonString != null) {
|
|
||||||
cached = gson.fromJson(jsonString, new TypeToken<ValueSortedMap<String, Repo>>(){}.getType());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cached == null) {
|
|
||||||
cached = new ValueSortedMap<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get cached ETag to add in the request header
|
|
||||||
String etag = prefs.getString(ETAG_KEY, "");
|
|
||||||
HashMap<String, String> header = new HashMap<>();
|
|
||||||
header.put("If-None-Match", etag);
|
|
||||||
|
|
||||||
// Making a request to main URL for repo info
|
|
||||||
jsonString = WebService.request(
|
|
||||||
context.getString(R.string.url_main), WebService.GET, null, header, false);
|
|
||||||
|
|
||||||
if (!jsonString.isEmpty()) {
|
|
||||||
try {
|
|
||||||
JSONArray jsonArray = new JSONArray(jsonString);
|
|
||||||
// If it gets to this point, the response is valid, update ETag
|
|
||||||
etag = WebService.getLastResponseHeader().get(ETAG_KEY).get(0);
|
|
||||||
// Maybe bug in Android build tools, sometimes the ETag has crap in it...
|
|
||||||
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
|
|
||||||
|
|
||||||
// Update repo info
|
|
||||||
for (int i = 0; i < jsonArray.length(); i++) {
|
|
||||||
JSONObject jsonobject = jsonArray.getJSONObject(i);
|
|
||||||
String id = jsonobject.getString("description");
|
|
||||||
String name = jsonobject.getString("name");
|
|
||||||
String lastUpdate = jsonobject.getString("pushed_at");
|
|
||||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
|
||||||
Date updatedDate;
|
|
||||||
try {
|
|
||||||
updatedDate = format.parse(lastUpdate);
|
|
||||||
} catch (ParseException e) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Repo repo = cached.get(id);
|
|
||||||
try {
|
|
||||||
if (repo == null) {
|
|
||||||
Logger.dev("ModuleHelper: Create new repo " + id);
|
|
||||||
repo = new Repo(context, name, updatedDate);
|
|
||||||
} else {
|
|
||||||
Logger.dev("ModuleHelper: Update cached repo " + id);
|
|
||||||
repo.update(updatedDate);
|
|
||||||
}
|
|
||||||
if (repo.getId() != null) {
|
|
||||||
repoMap.put(id, repo);
|
|
||||||
}
|
|
||||||
} catch (BaseModule.CacheModException ignored) {}
|
|
||||||
}
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Use cached if no internet or no updates
|
|
||||||
Logger.dev("ModuleHelper: No updates, use cached");
|
|
||||||
repoMap.putAll(cached);
|
|
||||||
}
|
|
||||||
|
|
||||||
prefs.edit()
|
|
||||||
.putInt(VERSION_KEY, DATABASE_VER)
|
|
||||||
.putString(REPO_KEY, gson.toJson(repoMap))
|
|
||||||
.putString(ETAG_KEY, etag)
|
|
||||||
.apply();
|
|
||||||
|
|
||||||
Logger.dev("ModuleHelper: Repo load done");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void getModuleList(List<Module> moduleList) {
|
|
||||||
moduleList.clear();
|
|
||||||
moduleList.addAll(moduleMap.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void getRepoLists(List<Repo> update, List<Repo> installed, List<Repo> others) {
|
|
||||||
update.clear();
|
|
||||||
installed.clear();
|
|
||||||
others.clear();
|
|
||||||
for (Repo repo : repoMap.values()) {
|
|
||||||
Module module = moduleMap.get(repo.getId());
|
|
||||||
if (module != null) {
|
|
||||||
if (repo.getVersionCode() > module.getVersionCode()) {
|
|
||||||
update.add(repo);
|
|
||||||
} else {
|
|
||||||
installed.add(repo);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
others.add(repo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ValueSortedMap<K, V extends Comparable > extends HashMap<K, V> {
|
|
||||||
|
|
||||||
private List<V> sorted = new ArrayList<>();
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Collection<V> values() {
|
|
||||||
if (sorted.isEmpty()) {
|
|
||||||
sorted.addAll(super.values());
|
|
||||||
Collections.sort(sorted);
|
|
||||||
}
|
|
||||||
return sorted;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V put(K key, V value) {
|
|
||||||
sorted.clear();
|
|
||||||
return super.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putAll(Map<? extends K, ? extends V> m) {
|
|
||||||
sorted.clear();
|
|
||||||
super.putAll(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V remove(Object key) {
|
|
||||||
sorted.clear();
|
|
||||||
return super.remove(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.util.Base64;
|
|
||||||
|
|
||||||
import com.google.android.gms.common.ConnectionResult;
|
|
||||||
import com.google.android.gms.common.api.GoogleApiClient;
|
|
||||||
import com.google.android.gms.common.api.Status;
|
|
||||||
import com.google.android.gms.safetynet.SafetyNet;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
public abstract class SafetyNetHelper
|
|
||||||
implements GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks {
|
|
||||||
|
|
||||||
private GoogleApiClient mGoogleApiClient;
|
|
||||||
|
|
||||||
public SafetyNetHelper(Context context) {
|
|
||||||
mGoogleApiClient = new GoogleApiClient.Builder(context)
|
|
||||||
.addApi(SafetyNet.API)
|
|
||||||
.addConnectionCallbacks(this)
|
|
||||||
.addOnConnectionFailedListener(this)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionFailed(@NonNull ConnectionResult result) {
|
|
||||||
Logger.dev("SN: Google API fail");
|
|
||||||
handleResults(-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnected(@Nullable Bundle bundle) {
|
|
||||||
Logger.dev("SN: Google API Connected");
|
|
||||||
safetyNetCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionSuspended(int i) {
|
|
||||||
Logger.dev("SN: Google API Suspended");
|
|
||||||
handleResults(-3);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void requestTest() {
|
|
||||||
// Connect Google Service
|
|
||||||
mGoogleApiClient.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void safetyNetCheck() {
|
|
||||||
// Create nonce
|
|
||||||
byte[] nonce = new byte[24];
|
|
||||||
new SecureRandom().nextBytes(nonce);
|
|
||||||
|
|
||||||
Logger.dev("SN: Check with nonce: " + Base64.encodeToString(nonce, Base64.DEFAULT));
|
|
||||||
|
|
||||||
// Call SafetyNet
|
|
||||||
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
|
|
||||||
.setResultCallback(result -> {
|
|
||||||
Status status = result.getStatus();
|
|
||||||
if (status.isSuccess()) {
|
|
||||||
String json = new String(Base64.decode(result.getJwsResult().split("\\.")[1], Base64.DEFAULT));
|
|
||||||
Logger.dev("SN: Response: " + json);
|
|
||||||
try {
|
|
||||||
JSONObject decoded = new JSONObject(json);
|
|
||||||
handleResults(decoded.getBoolean("ctsProfileMatch") ? 1 : 0);
|
|
||||||
} catch (JSONException ignored) {}
|
|
||||||
} else {
|
|
||||||
Logger.dev("SN: No response");
|
|
||||||
handleResults(-1);
|
|
||||||
}
|
|
||||||
// Disconnect
|
|
||||||
mGoogleApiClient.disconnect();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void handleResults(int i);
|
|
||||||
}
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modified by topjohnwu, based on Chainfire's libsuperuser
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class Shell {
|
|
||||||
|
|
||||||
// -1 = problematic/unknown issue; 0 = not rooted; 1 = properly rooted
|
|
||||||
public static int rootStatus;
|
|
||||||
|
|
||||||
private static Process rootShell;
|
|
||||||
private static DataOutputStream rootSTDIN;
|
|
||||||
private static StreamGobbler rootSTDOUT;
|
|
||||||
private static List<String> rootOutList = new ArrayList<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void init() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
rootShell = Runtime.getRuntime().exec("su");
|
|
||||||
rootStatus = 1;
|
|
||||||
} catch (IOException err) {
|
|
||||||
// No root
|
|
||||||
rootStatus = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
rootSTDIN = new DataOutputStream(rootShell.getOutputStream());
|
|
||||||
rootSTDOUT = new StreamGobbler(rootShell.getInputStream(), rootOutList, true);
|
|
||||||
rootSTDOUT.start();
|
|
||||||
|
|
||||||
// Setup umask and PATH
|
|
||||||
su("umask 022");
|
|
||||||
su("PATH=/data/busybox:$PATH");
|
|
||||||
|
|
||||||
List<String> ret = su("echo -BOC-", "id");
|
|
||||||
|
|
||||||
if (ret == null) {
|
|
||||||
// Something wrong with root, not allowed?
|
|
||||||
rootStatus = -1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String line : ret) {
|
|
||||||
if (line.contains("uid=")) {
|
|
||||||
// id command is working, let's see if we are actually root
|
|
||||||
rootStatus = line.contains("uid=0") ? rootStatus : -1;
|
|
||||||
return;
|
|
||||||
} else if (!line.contains("-BOC-")) {
|
|
||||||
rootStatus = -1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean rootAccess() {
|
|
||||||
return rootStatus > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<String> sh(String... commands) {
|
|
||||||
List<String> res = Collections.synchronizedList(new ArrayList<String>());
|
|
||||||
|
|
||||||
try {
|
|
||||||
Process process = Runtime.getRuntime().exec("sh");
|
|
||||||
DataOutputStream STDIN = new DataOutputStream(process.getOutputStream());
|
|
||||||
StreamGobbler STDOUT = new StreamGobbler(process.getInputStream(), res);
|
|
||||||
|
|
||||||
STDOUT.start();
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (String write : commands) {
|
|
||||||
STDIN.write((write + "\n").getBytes("UTF-8"));
|
|
||||||
STDIN.flush();
|
|
||||||
Logger.shell(false, write);
|
|
||||||
}
|
|
||||||
STDIN.write("exit\n".getBytes("UTF-8"));
|
|
||||||
STDIN.flush();
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (!e.getMessage().contains("EPIPE")) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
process.waitFor();
|
|
||||||
|
|
||||||
try {
|
|
||||||
STDIN.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// might be closed already
|
|
||||||
}
|
|
||||||
STDOUT.join();
|
|
||||||
process.destroy();
|
|
||||||
|
|
||||||
} catch (IOException | InterruptedException e) {
|
|
||||||
// shell probably not found
|
|
||||||
res = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run with the same shell by default
|
|
||||||
public static List<String> su(String... commands) {
|
|
||||||
return su(false, commands);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<String> su(boolean newShell, String... commands) {
|
|
||||||
List<String> res;
|
|
||||||
Process process;
|
|
||||||
DataOutputStream STDIN;
|
|
||||||
StreamGobbler STDOUT;
|
|
||||||
|
|
||||||
if (!rootAccess()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newShell) {
|
|
||||||
res = Collections.synchronizedList(new ArrayList<String>());
|
|
||||||
try {
|
|
||||||
process = Runtime.getRuntime().exec("su");
|
|
||||||
STDIN = new DataOutputStream(process.getOutputStream());
|
|
||||||
STDOUT = new StreamGobbler(process.getInputStream(), res);
|
|
||||||
} catch (IOException err) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
STDOUT.start();
|
|
||||||
} else {
|
|
||||||
process = rootShell;
|
|
||||||
STDIN = rootSTDIN;
|
|
||||||
STDOUT = rootSTDOUT;
|
|
||||||
res = rootOutList;
|
|
||||||
res.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (String write : commands) {
|
|
||||||
STDIN.write((write + "\n").getBytes("UTF-8"));
|
|
||||||
STDIN.flush();
|
|
||||||
Logger.shell(true, write);
|
|
||||||
}
|
|
||||||
if (newShell) {
|
|
||||||
STDIN.write("exit\n".getBytes("UTF-8"));
|
|
||||||
STDIN.flush();
|
|
||||||
process.waitFor();
|
|
||||||
|
|
||||||
try {
|
|
||||||
STDIN.close();
|
|
||||||
} catch (IOException ignore) {
|
|
||||||
// might be closed already
|
|
||||||
}
|
|
||||||
|
|
||||||
STDOUT.join();
|
|
||||||
process.destroy();
|
|
||||||
} else {
|
|
||||||
STDIN.write(("echo\n").getBytes("UTF-8"));
|
|
||||||
STDIN.flush();
|
|
||||||
STDIN.write(("echo \'-root-done-\'\n").getBytes("UTF-8"));
|
|
||||||
STDIN.flush();
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
// Process terminated, it means the interactive shell has some issues
|
|
||||||
process.exitValue();
|
|
||||||
return null;
|
|
||||||
} catch (IllegalThreadStateException e) {
|
|
||||||
// Process still running, gobble output until done
|
|
||||||
int end = res.size() - 1;
|
|
||||||
if (end > 0) {
|
|
||||||
if (res.get(end).equals("-root-done-")) {
|
|
||||||
res.remove(end);
|
|
||||||
if (res.get(end -1).isEmpty()) {
|
|
||||||
res.remove(end -1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try { STDOUT.join(100); } catch (InterruptedException err) { return null; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (!e.getMessage().contains("EPIPE")) {
|
|
||||||
Logger.dev("Shell: Root shell error...");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch(InterruptedException e) {
|
|
||||||
Logger.dev("Shell: Root shell error...");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArrayList<>(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.DownloadManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.support.v4.app.ActivityCompat;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class Utils {
|
|
||||||
|
|
||||||
public static boolean isDownloading = false;
|
|
||||||
public static boolean isDarkTheme;
|
|
||||||
|
|
||||||
public static boolean itemExist(String path) {
|
|
||||||
return itemExist(true, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean itemExist(boolean root, String path) {
|
|
||||||
String command = "if [ -e " + path + " ]; then echo true; else echo false; fi";
|
|
||||||
if (Shell.rootAccess() && root) {
|
|
||||||
return Boolean.parseBoolean(Shell.su(command).get(0));
|
|
||||||
} else {
|
|
||||||
return new File(path).exists();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean commandExists(String s) {
|
|
||||||
List<String> ret;
|
|
||||||
String command = "if [ -z $(which " + s + ") ]; then echo false; else echo true; fi";
|
|
||||||
ret = Shell.sh(command);
|
|
||||||
return Boolean.parseBoolean(ret.get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean createFile(String path) {
|
|
||||||
String folder = path.substring(0, path.lastIndexOf('/'));
|
|
||||||
String command = "mkdir -p " + folder + " 2>/dev/null; touch " + path + " 2>/dev/null; if [ -f \"" + path + "\" ]; then echo true; else echo false; fi";
|
|
||||||
return Shell.rootAccess() && Boolean.parseBoolean(Shell.su(command).get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean removeItem(String path) {
|
|
||||||
String command = "rm -rf " + path + " 2>/dev/null; if [ -e " + path + " ]; then echo false; else echo true; fi";
|
|
||||||
return Shell.rootAccess() && Boolean.parseBoolean(Shell.su(command).get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<String> getModList(String path) {
|
|
||||||
List<String> ret;
|
|
||||||
String command = "find " + path + " -type d -maxdepth 1 ! -name \"*.core\" ! -name \"*lost+found\" ! -name \"*magisk\"";
|
|
||||||
ret = Shell.su(command);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<String> readFile(String path) {
|
|
||||||
List<String> ret;
|
|
||||||
String command = "cat " + path;
|
|
||||||
if (Shell.rootAccess()) {
|
|
||||||
ret = Shell.su(command);
|
|
||||||
} else {
|
|
||||||
ret = Shell.sh(command);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void dlAndReceive(Context context, DownloadReceiver receiver, String link, String filename) {
|
|
||||||
if (isDownloading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
Toast.makeText(context, R.string.permissionNotGranted, Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File file = new File(Environment.getExternalStorageDirectory() + "/MagiskManager/" + filename);
|
|
||||||
|
|
||||||
if ((!file.getParentFile().exists() && !file.getParentFile().mkdirs()) || (file.exists() && !file.delete())) {
|
|
||||||
Toast.makeText(context, R.string.permissionNotGranted, Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toast.makeText(context, context.getString(R.string.downloading_toast, filename), Toast.LENGTH_LONG).show();
|
|
||||||
isDownloading = true;
|
|
||||||
|
|
||||||
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
||||||
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(link));
|
|
||||||
request.setDestinationUri(Uri.fromFile(file));
|
|
||||||
|
|
||||||
receiver.setDownloadID(downloadManager.enqueue(request));
|
|
||||||
receiver.setFilename(filename);
|
|
||||||
context.registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getLegalFilename(CharSequence filename) {
|
|
||||||
return filename.toString().replace(" ", "_").replace("'", "").replace("\"", "")
|
|
||||||
.replace("$", "").replace("`", "").replace("(", "").replace(")", "")
|
|
||||||
.replace("#", "").replace("@", "").replace("*", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String detectBootImage() {
|
|
||||||
String[] commands = {
|
|
||||||
"for PARTITION in kern-a KERN-A android_boot ANDROID_BOOT kernel KERNEL boot BOOT lnx LNX; do",
|
|
||||||
"BOOTIMAGE=`readlink /dev/block/by-name/$PARTITION || readlink /dev/block/platform/*/by-name/$PARTITION || readlink /dev/block/platform/*/*/by-name/$PARTITION`",
|
|
||||||
"if [ ! -z \"$BOOTIMAGE\" ]; then break; fi",
|
|
||||||
"done",
|
|
||||||
"echo \"${BOOTIMAGE##*/}\""
|
|
||||||
};
|
|
||||||
List<String> ret = Shell.su(commands);
|
|
||||||
if (!ret.isEmpty()) {
|
|
||||||
return ret.get(0);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AlertDialog.Builder getAlertDialogBuilder(Context context) {
|
|
||||||
if (isDarkTheme) {
|
|
||||||
return new AlertDialog.Builder(context, R.style.AlertDialog_dh);
|
|
||||||
} else {
|
|
||||||
return new AlertDialog.Builder(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean lowercaseContains(CharSequence string, CharSequence nonNullLowercaseSearch) {
|
|
||||||
return !TextUtils.isEmpty(string) && string.toString().toLowerCase().contains(nonNullLowercaseSearch);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ByteArrayInOutStream extends ByteArrayOutputStream {
|
|
||||||
public ByteArrayInputStream getInputStream() {
|
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(buf, 0, count);
|
|
||||||
count = 0;
|
|
||||||
buf = new byte[32];
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
public void setBuffer(byte[] buffer) {
|
|
||||||
buf = buffer;
|
|
||||||
count = buffer.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
|
|
||||||
public class WebService {
|
|
||||||
|
|
||||||
public final static int GET = 1;
|
|
||||||
public final static int POST = 2;
|
|
||||||
|
|
||||||
private static Map<String, List<String>> responseHeader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Making web service call
|
|
||||||
*
|
|
||||||
* @url - url to make request
|
|
||||||
* @requestmethod - http request method
|
|
||||||
*/
|
|
||||||
public static String request(String url, int method) {
|
|
||||||
return request(url, method, null, null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String request(String url, int method, boolean newline) {
|
|
||||||
return request(url, method, null, null, newline);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Making service call
|
|
||||||
*
|
|
||||||
* @url - url to make request
|
|
||||||
* @requestmethod - http request method
|
|
||||||
* @params - http request params
|
|
||||||
* @header - http request header
|
|
||||||
* @newline - true to append a newline each line
|
|
||||||
*/
|
|
||||||
public static String request(String urlAddress, int method,
|
|
||||||
Map<String, String> params, Map<String, String> header,
|
|
||||||
boolean newline) {
|
|
||||||
Logger.dev("WebService: Service call " + urlAddress);
|
|
||||||
URL url;
|
|
||||||
StringBuilder response = new StringBuilder();
|
|
||||||
try {
|
|
||||||
url = new URL(urlAddress);
|
|
||||||
|
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
||||||
conn.setReadTimeout(15000);
|
|
||||||
conn.setConnectTimeout(15000);
|
|
||||||
conn.setDoInput(true);
|
|
||||||
|
|
||||||
if (method == POST) {
|
|
||||||
conn.setRequestMethod("POST");
|
|
||||||
} else if (method == GET) {
|
|
||||||
conn.setRequestMethod("GET");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (header != null) {
|
|
||||||
for (Map.Entry<String, String> entry : header.entrySet()) {
|
|
||||||
conn.setRequestProperty(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params != null) {
|
|
||||||
OutputStream os = conn.getOutputStream();
|
|
||||||
BufferedWriter writer = new BufferedWriter(
|
|
||||||
new OutputStreamWriter(os, "UTF-8"));
|
|
||||||
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
boolean first = true;
|
|
||||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
|
||||||
if (first)
|
|
||||||
first = false;
|
|
||||||
else
|
|
||||||
result.append("&");
|
|
||||||
|
|
||||||
result.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
|
|
||||||
result.append("=");
|
|
||||||
result.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.write(result.toString());
|
|
||||||
|
|
||||||
writer.flush();
|
|
||||||
writer.close();
|
|
||||||
os.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
int responseCode = conn.getResponseCode();
|
|
||||||
|
|
||||||
if (responseCode == HttpsURLConnection.HTTP_OK) {
|
|
||||||
String line;
|
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
|
||||||
while ((line = br.readLine()) != null) {
|
|
||||||
if (newline) {
|
|
||||||
response.append(line).append("\n");
|
|
||||||
} else {
|
|
||||||
response.append(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
responseHeader = conn.getHeaderFields();
|
|
||||||
} else {
|
|
||||||
responseHeader = null;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, List<String>> getLastResponseHeader() {
|
|
||||||
return responseHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.webkit.WebResourceRequest;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
import android.webkit.WebViewClient;
|
|
||||||
|
|
||||||
public class WebWindow {
|
|
||||||
|
|
||||||
public WebWindow(String title, String url, Context context) {
|
|
||||||
AlertDialog.Builder alert = Utils.getAlertDialogBuilder(context);
|
|
||||||
alert.setTitle(title);
|
|
||||||
|
|
||||||
Logger.dev("WebView: URL = " + url);
|
|
||||||
|
|
||||||
WebView wv = new WebView(context);
|
|
||||||
wv.loadUrl(url);
|
|
||||||
wv.setWebViewClient(new WebViewClient() {
|
|
||||||
@Override
|
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
|
||||||
view.loadUrl(url);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
alert.setView(wv);
|
|
||||||
alert.setNegativeButton("Close", (dialog, id) -> dialog.dismiss());
|
|
||||||
alert.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,747 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.Utils.ByteArrayInOutStream;
|
|
||||||
|
|
||||||
import org.spongycastle.asn1.ASN1InputStream;
|
|
||||||
import org.spongycastle.asn1.ASN1ObjectIdentifier;
|
|
||||||
import org.spongycastle.asn1.DEROutputStream;
|
|
||||||
import org.spongycastle.asn1.cms.CMSObjectIdentifiers;
|
|
||||||
import org.spongycastle.cert.jcajce.JcaCertStore;
|
|
||||||
import org.spongycastle.cms.CMSException;
|
|
||||||
import org.spongycastle.cms.CMSProcessableByteArray;
|
|
||||||
import org.spongycastle.cms.CMSSignedData;
|
|
||||||
import org.spongycastle.cms.CMSSignedDataGenerator;
|
|
||||||
import org.spongycastle.cms.CMSTypedData;
|
|
||||||
import org.spongycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
|
|
||||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
|
||||||
import org.spongycastle.operator.ContentSigner;
|
|
||||||
import org.spongycastle.operator.OperatorCreationException;
|
|
||||||
import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
|
|
||||||
import org.spongycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
|
|
||||||
import org.spongycastle.util.encoders.Base64;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.FilterOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.security.DigestOutputStream;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.Key;
|
|
||||||
import java.security.KeyFactory;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.Provider;
|
|
||||||
import java.security.Security;
|
|
||||||
import java.security.cert.CertificateEncodingException;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
import java.security.spec.KeySpec;
|
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.jar.Attributes;
|
|
||||||
import java.util.jar.JarEntry;
|
|
||||||
import java.util.jar.JarFile;
|
|
||||||
import java.util.jar.JarInputStream;
|
|
||||||
import java.util.jar.JarOutputStream;
|
|
||||||
import java.util.jar.Manifest;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
|
||||||
import javax.crypto.SecretKeyFactory;
|
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
|
||||||
|
|
||||||
public class ZipUtils {
|
|
||||||
private static final String CERT_SF_NAME = "META-INF/CERT.SF";
|
|
||||||
private static final String CERT_SIG_NAME = "META-INF/CERT.%s";
|
|
||||||
private static final String OTACERT_NAME = "META-INF/com/android/otacert";
|
|
||||||
private static final String PUBLIC_KEY_NAME = "public.certificate.x509.pem";
|
|
||||||
private static final String PRIVATE_KEY_NAME = "private.key.pk8";
|
|
||||||
private static Provider sBouncyCastleProvider;
|
|
||||||
// bitmasks for which hash algorithms we need the manifest to include.
|
|
||||||
private static final int USE_SHA1 = 1;
|
|
||||||
private static final int USE_SHA256 = 2;
|
|
||||||
|
|
||||||
static {
|
|
||||||
System.loadLibrary("zipadjust");
|
|
||||||
sBouncyCastleProvider = new BouncyCastleProvider();
|
|
||||||
Security.insertProviderAt(sBouncyCastleProvider, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public native static byte[] zipAdjust(byte[] bytes, int size);
|
|
||||||
|
|
||||||
// Wrapper function for the JNI function
|
|
||||||
public static void adjustZip(ByteArrayInOutStream buffer) {
|
|
||||||
buffer.setBuffer(zipAdjust(buffer.toByteArray(), buffer.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void removeTopFolder(InputStream in, OutputStream out) {
|
|
||||||
try {
|
|
||||||
JarInputStream source = new JarInputStream(in);
|
|
||||||
JarOutputStream dest = new JarOutputStream(out);
|
|
||||||
JarEntry entry;
|
|
||||||
String path;
|
|
||||||
int size;
|
|
||||||
byte buffer[] = new byte[4096];
|
|
||||||
while ((entry = source.getNextJarEntry()) != null) {
|
|
||||||
// Remove the top directory from the path
|
|
||||||
path = entry.getName().substring(entry.getName().indexOf("/") + 1);
|
|
||||||
// If it's the top folder, ignore it
|
|
||||||
if (path.isEmpty())
|
|
||||||
continue;
|
|
||||||
// Don't include placeholder
|
|
||||||
if (path.contains("system/placeholder"))
|
|
||||||
continue;
|
|
||||||
dest.putNextEntry(new JarEntry(path));
|
|
||||||
while((size = source.read(buffer, 0, 2048)) != -1)
|
|
||||||
dest.write(buffer, 0, size);
|
|
||||||
}
|
|
||||||
source.close();
|
|
||||||
dest.close();
|
|
||||||
in.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
Logger.dev("ZipUtils: removeTopFolder IO error!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void unzip(File file, File folder) {
|
|
||||||
unzip(file, folder, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void unzip(File file, File folder, String path) {
|
|
||||||
try {
|
|
||||||
int count;
|
|
||||||
FileOutputStream out;
|
|
||||||
File dest;
|
|
||||||
InputStream is;
|
|
||||||
JarEntry entry;
|
|
||||||
byte data[] = new byte[4096];
|
|
||||||
JarFile zipfile = new JarFile(file);
|
|
||||||
Enumeration e = zipfile.entries();
|
|
||||||
while(e.hasMoreElements()) {
|
|
||||||
entry = (JarEntry) e.nextElement();
|
|
||||||
if (!entry.getName().contains(path)
|
|
||||||
|| entry.getName().charAt(entry.getName().length() - 1) == '/') {
|
|
||||||
// Ignore directories, only create files
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Logger.dev("ZipUtils: Extracting: " + entry);
|
|
||||||
is = zipfile.getInputStream(entry);
|
|
||||||
dest = new File(folder, entry.getName());
|
|
||||||
if (dest.getParentFile().mkdirs()) {
|
|
||||||
dest.createNewFile();
|
|
||||||
}
|
|
||||||
out = new FileOutputStream(dest);
|
|
||||||
while ((count = is.read(data, 0, 4096)) != -1) {
|
|
||||||
out.write(data, 0, count);
|
|
||||||
}
|
|
||||||
out.flush();
|
|
||||||
out.close();
|
|
||||||
is.close();
|
|
||||||
}
|
|
||||||
} catch(Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void signZip(Context context, InputStream inputStream,
|
|
||||||
OutputStream outputStream, boolean signWholeFile) {
|
|
||||||
JarMap inputJar;
|
|
||||||
int hashes = 0;
|
|
||||||
try {
|
|
||||||
X509Certificate publicKey = readPublicKey(context.getAssets().open(PUBLIC_KEY_NAME));
|
|
||||||
hashes |= getDigestAlgorithm(publicKey);
|
|
||||||
// Set the ZIP file timestamp to the starting valid time
|
|
||||||
// of the 0th certificate plus one hour (to match what
|
|
||||||
// we've historically done).
|
|
||||||
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
|
|
||||||
PrivateKey privateKey = readPrivateKey(context.getAssets().open(PRIVATE_KEY_NAME));
|
|
||||||
inputJar = new JarMap(new JarInputStream(inputStream));
|
|
||||||
if (signWholeFile) {
|
|
||||||
if (!"RSA".equalsIgnoreCase(privateKey.getAlgorithm())) {
|
|
||||||
System.err.println("Cannot sign OTA packages with non-RSA keys");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
signWholeFile(inputJar, context.getAssets().open(PUBLIC_KEY_NAME),
|
|
||||||
publicKey, privateKey, outputStream);
|
|
||||||
} else {
|
|
||||||
JarOutputStream outputJar = new JarOutputStream(outputStream);
|
|
||||||
// For signing .apks, use the maximum compression to make
|
|
||||||
// them as small as possible (since they live forever on
|
|
||||||
// the system partition). For OTA packages, use the
|
|
||||||
// default compression level, which is much much faster
|
|
||||||
// and produces output that is only a tiny bit larger
|
|
||||||
// (~0.1% on full OTA packages I tested).
|
|
||||||
outputJar.setLevel(9);
|
|
||||||
Manifest manifest = addDigestsToManifest(inputJar, hashes);
|
|
||||||
copyFiles(manifest, inputJar, outputJar, timestamp);
|
|
||||||
signFile(manifest, inputJar, publicKey, privateKey, outputJar);
|
|
||||||
outputJar.close();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class JarMap extends TreeMap<String, Pair<JarEntry, ByteArrayOutputStream> > {
|
|
||||||
|
|
||||||
private Manifest manifest;
|
|
||||||
|
|
||||||
public JarMap(JarInputStream in) throws IOException {
|
|
||||||
super();
|
|
||||||
manifest = in.getManifest();
|
|
||||||
byte[] buffer = new byte[4096];
|
|
||||||
int num;
|
|
||||||
JarEntry entry;
|
|
||||||
while ((entry = in.getNextJarEntry()) != null) {
|
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
||||||
while ((num = in.read(buffer)) > 0) {
|
|
||||||
stream.write(buffer, 0, num);
|
|
||||||
}
|
|
||||||
put(entry.getName(), entry, stream);
|
|
||||||
}
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public JarEntry getJarEntry(String name) {
|
|
||||||
return get(name).first;
|
|
||||||
}
|
|
||||||
public ByteArrayOutputStream getStream(String name) {
|
|
||||||
return get(name).second;
|
|
||||||
}
|
|
||||||
public void put(String name, JarEntry entry, ByteArrayOutputStream stream) {
|
|
||||||
put(name, new Pair<>(entry, stream));
|
|
||||||
}
|
|
||||||
public Manifest getManifest() {
|
|
||||||
return manifest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return one of USE_SHA1 or USE_SHA256 according to the signature
|
|
||||||
* algorithm specified in the cert.
|
|
||||||
*/
|
|
||||||
private static int getDigestAlgorithm(X509Certificate cert) {
|
|
||||||
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
|
|
||||||
if ("SHA1WITHRSA".equals(sigAlg) ||
|
|
||||||
"MD5WITHRSA".equals(sigAlg)) { // see "HISTORICAL NOTE" above.
|
|
||||||
return USE_SHA1;
|
|
||||||
} else if (sigAlg.startsWith("SHA256WITH")) {
|
|
||||||
return USE_SHA256;
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("unsupported signature algorithm \"" + sigAlg +
|
|
||||||
"\" in cert [" + cert.getSubjectDN());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** Returns the expected signature algorithm for this key type. */
|
|
||||||
private static String getSignatureAlgorithm(X509Certificate cert) {
|
|
||||||
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
|
|
||||||
String keyType = cert.getPublicKey().getAlgorithm().toUpperCase(Locale.US);
|
|
||||||
if ("RSA".equalsIgnoreCase(keyType)) {
|
|
||||||
if (getDigestAlgorithm(cert) == USE_SHA256) {
|
|
||||||
return "SHA256withRSA";
|
|
||||||
} else {
|
|
||||||
return "SHA1withRSA";
|
|
||||||
}
|
|
||||||
} else if ("DSA".equalsIgnoreCase(keyType)) {
|
|
||||||
return "SHA256withDSA";
|
|
||||||
} else if ("EC".equalsIgnoreCase(keyType)) {
|
|
||||||
return "SHA256withECDSA";
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("unsupported key type: " + keyType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Files matching this pattern are not copied to the output.
|
|
||||||
private static Pattern stripPattern =
|
|
||||||
Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA|EC)|com/android/otacert))|(" +
|
|
||||||
Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
|
|
||||||
private static X509Certificate readPublicKey(InputStream input)
|
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
try {
|
|
||||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
|
||||||
return (X509Certificate) cf.generateCertificate(input);
|
|
||||||
} finally {
|
|
||||||
input.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypt an encrypted PKCS 8 format private key.
|
|
||||||
*
|
|
||||||
* Based on ghstark's post on Aug 6, 2006 at
|
|
||||||
* http://forums.sun.com/thread.jspa?threadID=758133&messageID=4330949
|
|
||||||
*
|
|
||||||
* @param encryptedPrivateKey The raw data of the private key
|
|
||||||
* @param keyFile The file containing the private key
|
|
||||||
*/
|
|
||||||
private static KeySpec decryptPrivateKey(byte[] encryptedPrivateKey, File keyFile)
|
|
||||||
throws GeneralSecurityException {
|
|
||||||
EncryptedPrivateKeyInfo epkInfo;
|
|
||||||
try {
|
|
||||||
epkInfo = new EncryptedPrivateKeyInfo(encryptedPrivateKey);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
// Probably not an encrypted key.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// We no longer have console, so need to use another way to input password
|
|
||||||
// This function is left here if needed in the future, so no use for now
|
|
||||||
char[] password = new char[0];
|
|
||||||
SecretKeyFactory skFactory = SecretKeyFactory.getInstance(epkInfo.getAlgName());
|
|
||||||
Key key = skFactory.generateSecret(new PBEKeySpec(password));
|
|
||||||
Cipher cipher = Cipher.getInstance(epkInfo.getAlgName());
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, key, epkInfo.getAlgParameters());
|
|
||||||
try {
|
|
||||||
return epkInfo.getKeySpec(cipher);
|
|
||||||
} catch (InvalidKeySpecException ex) {
|
|
||||||
System.err.println("signapk: Password for " + keyFile + " may be bad.");
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Read a PKCS 8 format private key. */
|
|
||||||
private static PrivateKey readPrivateKey(InputStream input)
|
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
try {
|
|
||||||
byte[] buffer = new byte[4096];
|
|
||||||
int size = input.read(buffer);
|
|
||||||
byte[] bytes = Arrays.copyOf(buffer, size);
|
|
||||||
KeySpec spec = new PKCS8EncodedKeySpec(bytes);
|
|
||||||
PrivateKey key;
|
|
||||||
key = decodeAsKeyType(spec, "RSA");
|
|
||||||
if (key != null) {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
key = decodeAsKeyType(spec, "DSA");
|
|
||||||
if (key != null) {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
key = decodeAsKeyType(spec, "EC");
|
|
||||||
if (key != null) {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
throw new NoSuchAlgorithmException("Must be an RSA, DSA, or EC key");
|
|
||||||
} finally {
|
|
||||||
input.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private static PrivateKey decodeAsKeyType(KeySpec spec, String keyType)
|
|
||||||
throws GeneralSecurityException {
|
|
||||||
try {
|
|
||||||
return KeyFactory.getInstance(keyType).generatePrivate(spec);
|
|
||||||
} catch (InvalidKeySpecException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the hash(es) of every file to the manifest, creating it if
|
|
||||||
* necessary.
|
|
||||||
*/
|
|
||||||
private static Manifest addDigestsToManifest(JarMap jar, int hashes)
|
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
Manifest input = jar.getManifest();
|
|
||||||
Manifest output = new Manifest();
|
|
||||||
Attributes main = output.getMainAttributes();
|
|
||||||
if (input != null) {
|
|
||||||
main.putAll(input.getMainAttributes());
|
|
||||||
} else {
|
|
||||||
main.putValue("Manifest-Version", "1.0");
|
|
||||||
main.putValue("Created-By", "1.0 (Android SignApk)");
|
|
||||||
}
|
|
||||||
MessageDigest md_sha1 = null;
|
|
||||||
MessageDigest md_sha256 = null;
|
|
||||||
if ((hashes & USE_SHA1) != 0) {
|
|
||||||
md_sha1 = MessageDigest.getInstance("SHA1");
|
|
||||||
}
|
|
||||||
if ((hashes & USE_SHA256) != 0) {
|
|
||||||
md_sha256 = MessageDigest.getInstance("SHA256");
|
|
||||||
}
|
|
||||||
// We sort the input entries by name, and add them to the
|
|
||||||
// output manifest in sorted order. We expect that the output
|
|
||||||
// map will be deterministic.
|
|
||||||
/* JarMap is a TreeMap, so it's already sorted */
|
|
||||||
for (String name : jar.keySet()) {
|
|
||||||
JarEntry entry = jar.getJarEntry(name);
|
|
||||||
if (!entry.isDirectory() &&
|
|
||||||
(stripPattern == null || !stripPattern.matcher(name).matches())) {
|
|
||||||
byte[] buffer = jar.getStream(name).toByteArray();
|
|
||||||
if (md_sha1 != null) md_sha1.update(buffer, 0, buffer.length);
|
|
||||||
if (md_sha256 != null) md_sha256.update(buffer, 0, buffer.length);
|
|
||||||
Attributes attr = null;
|
|
||||||
if (input != null) attr = input.getAttributes(name);
|
|
||||||
attr = attr != null ? new Attributes(attr) : new Attributes();
|
|
||||||
if (md_sha1 != null) {
|
|
||||||
attr.putValue("SHA1-Digest",
|
|
||||||
new String(Base64.encode(md_sha1.digest()), "ASCII"));
|
|
||||||
}
|
|
||||||
if (md_sha256 != null) {
|
|
||||||
attr.putValue("SHA-256-Digest",
|
|
||||||
new String(Base64.encode(md_sha256.digest()), "ASCII"));
|
|
||||||
}
|
|
||||||
output.getEntries().put(name, attr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a copy of the public key to the archive; this should
|
|
||||||
* exactly match one of the files in
|
|
||||||
* /system/etc/security/otacerts.zip on the device. (The same
|
|
||||||
* cert can be extracted from the CERT.RSA file but this is much
|
|
||||||
* easier to get at.)
|
|
||||||
*/
|
|
||||||
private static void addOtacert(JarOutputStream outputJar,
|
|
||||||
InputStream input,
|
|
||||||
long timestamp,
|
|
||||||
Manifest manifest,
|
|
||||||
int hash)
|
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
MessageDigest md = MessageDigest.getInstance(hash == USE_SHA1 ? "SHA1" : "SHA256");
|
|
||||||
JarEntry je = new JarEntry(OTACERT_NAME);
|
|
||||||
je.setTime(timestamp);
|
|
||||||
outputJar.putNextEntry(je);
|
|
||||||
byte[] b = new byte[4096];
|
|
||||||
int read;
|
|
||||||
while ((read = input.read(b)) != -1) {
|
|
||||||
outputJar.write(b, 0, read);
|
|
||||||
md.update(b, 0, read);
|
|
||||||
}
|
|
||||||
input.close();
|
|
||||||
Attributes attr = new Attributes();
|
|
||||||
attr.putValue(hash == USE_SHA1 ? "SHA1-Digest" : "SHA-256-Digest",
|
|
||||||
new String(Base64.encode(md.digest()), "ASCII"));
|
|
||||||
manifest.getEntries().put(OTACERT_NAME, attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Write to another stream and track how many bytes have been
|
|
||||||
* written.
|
|
||||||
*/
|
|
||||||
private static class CountOutputStream extends FilterOutputStream {
|
|
||||||
private int mCount;
|
|
||||||
public CountOutputStream(OutputStream out) {
|
|
||||||
super(out);
|
|
||||||
mCount = 0;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void write(int b) throws IOException {
|
|
||||||
super.write(b);
|
|
||||||
mCount++;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void write(byte[] b, int off, int len) throws IOException {
|
|
||||||
super.write(b, off, len);
|
|
||||||
mCount += len;
|
|
||||||
}
|
|
||||||
public int size() {
|
|
||||||
return mCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Write a .SF file with a digest of the specified manifest. */
|
|
||||||
private static void writeSignatureFile(Manifest manifest, OutputStream out,
|
|
||||||
int hash)
|
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
Manifest sf = new Manifest();
|
|
||||||
Attributes main = sf.getMainAttributes();
|
|
||||||
main.putValue("Signature-Version", "1.0");
|
|
||||||
main.putValue("Created-By", "1.0 (Android SignApk)");
|
|
||||||
MessageDigest md = MessageDigest.getInstance(
|
|
||||||
hash == USE_SHA256 ? "SHA256" : "SHA1");
|
|
||||||
PrintStream print = new PrintStream(
|
|
||||||
new DigestOutputStream(new ByteArrayOutputStream(), md),
|
|
||||||
true, "UTF-8");
|
|
||||||
// Digest of the entire manifest
|
|
||||||
manifest.write(print);
|
|
||||||
print.flush();
|
|
||||||
main.putValue(hash == USE_SHA256 ? "SHA-256-Digest-Manifest" : "SHA1-Digest-Manifest",
|
|
||||||
new String(Base64.encode(md.digest()), "ASCII"));
|
|
||||||
Map<String, Attributes> entries = manifest.getEntries();
|
|
||||||
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
|
|
||||||
// Digest of the manifest stanza for this entry.
|
|
||||||
print.print("Name: " + entry.getKey() + "\r\n");
|
|
||||||
for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
|
|
||||||
print.print(att.getKey() + ": " + att.getValue() + "\r\n");
|
|
||||||
}
|
|
||||||
print.print("\r\n");
|
|
||||||
print.flush();
|
|
||||||
Attributes sfAttr = new Attributes();
|
|
||||||
sfAttr.putValue(hash == USE_SHA256 ? "SHA-256-Digest" : "SHA1-Digest-Manifest",
|
|
||||||
new String(Base64.encode(md.digest()), "ASCII"));
|
|
||||||
sf.getEntries().put(entry.getKey(), sfAttr);
|
|
||||||
}
|
|
||||||
CountOutputStream cout = new CountOutputStream(out);
|
|
||||||
sf.write(cout);
|
|
||||||
// A bug in the java.util.jar implementation of Android platforms
|
|
||||||
// up to version 1.6 will cause a spurious IOException to be thrown
|
|
||||||
// if the length of the signature file is a multiple of 1024 bytes.
|
|
||||||
// As a workaround, add an extra CRLF in this case.
|
|
||||||
if ((cout.size() % 1024) == 0) {
|
|
||||||
cout.write('\r');
|
|
||||||
cout.write('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sign data and write the digital signature to 'out'. */
|
|
||||||
private static void writeSignatureBlock(
|
|
||||||
CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey, OutputStream out)
|
|
||||||
throws IOException, CertificateEncodingException, OperatorCreationException, CMSException {
|
|
||||||
ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>(1);
|
|
||||||
certList.add(publicKey);
|
|
||||||
JcaCertStore certs = new JcaCertStore(certList);
|
|
||||||
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
|
|
||||||
ContentSigner signer = new JcaContentSignerBuilder(getSignatureAlgorithm(publicKey))
|
|
||||||
.setProvider(sBouncyCastleProvider)
|
|
||||||
.build(privateKey);
|
|
||||||
gen.addSignerInfoGenerator(
|
|
||||||
new JcaSignerInfoGeneratorBuilder(
|
|
||||||
new JcaDigestCalculatorProviderBuilder()
|
|
||||||
.setProvider(sBouncyCastleProvider)
|
|
||||||
.build())
|
|
||||||
.setDirectSignature(true)
|
|
||||||
.build(signer, publicKey));
|
|
||||||
gen.addCertificates(certs);
|
|
||||||
CMSSignedData sigData = gen.generate(data, false);
|
|
||||||
ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
|
|
||||||
DEROutputStream dos = new DEROutputStream(out);
|
|
||||||
dos.writeObject(asn1.readObject());
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Copy all the files in a manifest from input to output. We set
|
|
||||||
* the modification times in the output to a fixed time, so as to
|
|
||||||
* reduce variation in the output file and make incremental OTAs
|
|
||||||
* more efficient.
|
|
||||||
*/
|
|
||||||
private static void copyFiles(Manifest manifest,
|
|
||||||
JarMap in, JarOutputStream out, long timestamp) throws IOException {
|
|
||||||
Map<String, Attributes> entries = manifest.getEntries();
|
|
||||||
ArrayList<String> names = new ArrayList<>(entries.keySet());
|
|
||||||
Collections.sort(names);
|
|
||||||
for (String name : names) {
|
|
||||||
JarEntry inEntry = in.getJarEntry(name);
|
|
||||||
JarEntry outEntry;
|
|
||||||
if (inEntry.getMethod() == JarEntry.STORED) {
|
|
||||||
// Preserve the STORED method of the input entry.
|
|
||||||
outEntry = new JarEntry(inEntry);
|
|
||||||
} else {
|
|
||||||
// Create a new entry so that the compressed len is recomputed.
|
|
||||||
outEntry = new JarEntry(name);
|
|
||||||
}
|
|
||||||
outEntry.setTime(timestamp);
|
|
||||||
out.putNextEntry(outEntry);
|
|
||||||
in.getStream(name).writeTo(out);
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class WholeFileSignerOutputStream extends FilterOutputStream {
|
|
||||||
private boolean closing = false;
|
|
||||||
private ByteArrayOutputStream footer = new ByteArrayOutputStream();
|
|
||||||
private OutputStream tee;
|
|
||||||
public WholeFileSignerOutputStream(OutputStream out, OutputStream tee) {
|
|
||||||
super(out);
|
|
||||||
this.tee = tee;
|
|
||||||
}
|
|
||||||
public void notifyClosing() {
|
|
||||||
closing = true;
|
|
||||||
}
|
|
||||||
public void finish() throws IOException {
|
|
||||||
closing = false;
|
|
||||||
byte[] data = footer.toByteArray();
|
|
||||||
if (data.length < 2)
|
|
||||||
throw new IOException("Less than two bytes written to footer");
|
|
||||||
write(data, 0, data.length - 2);
|
|
||||||
}
|
|
||||||
public byte[] getTail() {
|
|
||||||
return footer.toByteArray();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void write(byte[] b) throws IOException {
|
|
||||||
write(b, 0, b.length);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void write(byte[] b, int off, int len) throws IOException {
|
|
||||||
if (closing) {
|
|
||||||
// if the jar is about to close, save the footer that will be written
|
|
||||||
footer.write(b, off, len);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// write to both output streams. out is the CMSTypedData signer and tee is the file.
|
|
||||||
out.write(b, off, len);
|
|
||||||
tee.write(b, off, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void write(int b) throws IOException {
|
|
||||||
if (closing) {
|
|
||||||
// if the jar is about to close, save the footer that will be written
|
|
||||||
footer.write(b);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// write to both output streams. out is the CMSTypedData signer and tee is the file.
|
|
||||||
out.write(b);
|
|
||||||
tee.write(b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class CMSSigner implements CMSTypedData {
|
|
||||||
private JarMap inputJar;
|
|
||||||
private InputStream publicKeyFile;
|
|
||||||
private X509Certificate publicKey;
|
|
||||||
private PrivateKey privateKey;
|
|
||||||
private OutputStream outputStream;
|
|
||||||
private final ASN1ObjectIdentifier type;
|
|
||||||
private WholeFileSignerOutputStream signer;
|
|
||||||
public CMSSigner(JarMap inputJar, InputStream publicKeyFile,
|
|
||||||
X509Certificate publicKey, PrivateKey privateKey,
|
|
||||||
OutputStream outputStream) {
|
|
||||||
this.inputJar = inputJar;
|
|
||||||
this.publicKeyFile = publicKeyFile;
|
|
||||||
this.publicKey = publicKey;
|
|
||||||
this.privateKey = privateKey;
|
|
||||||
this.outputStream = outputStream;
|
|
||||||
this.type = new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId());
|
|
||||||
}
|
|
||||||
public Object getContent() {
|
|
||||||
// Not supported, but still don't crash or return null
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
public ASN1ObjectIdentifier getContentType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
try {
|
|
||||||
signer = new WholeFileSignerOutputStream(out, outputStream);
|
|
||||||
JarOutputStream outputJar = new JarOutputStream(signer);
|
|
||||||
int hash = getDigestAlgorithm(publicKey);
|
|
||||||
// Assume the certificate is valid for at least an hour.
|
|
||||||
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
|
|
||||||
Manifest manifest = addDigestsToManifest(inputJar, hash);
|
|
||||||
copyFiles(manifest, inputJar, outputJar, timestamp);
|
|
||||||
// Don't add Otacert, it's not an OTA
|
|
||||||
// addOtacert(outputJar, publicKeyFile, timestamp, manifest, hash);
|
|
||||||
signFile(manifest, inputJar, publicKey, privateKey, outputJar);
|
|
||||||
signer.notifyClosing();
|
|
||||||
outputJar.close();
|
|
||||||
signer.finish();
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void writeSignatureBlock(ByteArrayOutputStream temp)
|
|
||||||
throws IOException,
|
|
||||||
CertificateEncodingException,
|
|
||||||
OperatorCreationException,
|
|
||||||
CMSException {
|
|
||||||
ZipUtils.writeSignatureBlock(this, publicKey, privateKey, temp);
|
|
||||||
}
|
|
||||||
public WholeFileSignerOutputStream getSigner() {
|
|
||||||
return signer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void signWholeFile(JarMap inputJar, InputStream publicKeyFile,
|
|
||||||
X509Certificate publicKey, PrivateKey privateKey,
|
|
||||||
OutputStream outputStream) throws Exception {
|
|
||||||
CMSSigner cmsOut = new CMSSigner(inputJar, publicKeyFile,
|
|
||||||
publicKey, privateKey, outputStream);
|
|
||||||
ByteArrayOutputStream temp = new ByteArrayOutputStream();
|
|
||||||
// put a readable message and a null char at the start of the
|
|
||||||
// archive comment, so that tools that display the comment
|
|
||||||
// (hopefully) show something sensible.
|
|
||||||
// TODO: anything more useful we can put in this message?
|
|
||||||
byte[] message = "signed by SignApk".getBytes("UTF-8");
|
|
||||||
temp.write(message);
|
|
||||||
temp.write(0);
|
|
||||||
cmsOut.writeSignatureBlock(temp);
|
|
||||||
byte[] zipData = cmsOut.getSigner().getTail();
|
|
||||||
// For a zip with no archive comment, the
|
|
||||||
// end-of-central-directory record will be 22 bytes long, so
|
|
||||||
// we expect to find the EOCD marker 22 bytes from the end.
|
|
||||||
if (zipData[zipData.length-22] != 0x50 ||
|
|
||||||
zipData[zipData.length-21] != 0x4b ||
|
|
||||||
zipData[zipData.length-20] != 0x05 ||
|
|
||||||
zipData[zipData.length-19] != 0x06) {
|
|
||||||
throw new IllegalArgumentException("zip data already has an archive comment");
|
|
||||||
}
|
|
||||||
int total_size = temp.size() + 6;
|
|
||||||
if (total_size > 0xffff) {
|
|
||||||
throw new IllegalArgumentException("signature is too big for ZIP file comment");
|
|
||||||
}
|
|
||||||
// signature starts this many bytes from the end of the file
|
|
||||||
int signature_start = total_size - message.length - 1;
|
|
||||||
temp.write(signature_start & 0xff);
|
|
||||||
temp.write((signature_start >> 8) & 0xff);
|
|
||||||
// Why the 0xff bytes? In a zip file with no archive comment,
|
|
||||||
// bytes [-6:-2] of the file are the little-endian offset from
|
|
||||||
// the start of the file to the central directory. So for the
|
|
||||||
// two high bytes to be 0xff 0xff, the archive would have to
|
|
||||||
// be nearly 4GB in size. So it's unlikely that a real
|
|
||||||
// commentless archive would have 0xffs here, and lets us tell
|
|
||||||
// an old signed archive from a new one.
|
|
||||||
temp.write(0xff);
|
|
||||||
temp.write(0xff);
|
|
||||||
temp.write(total_size & 0xff);
|
|
||||||
temp.write((total_size >> 8) & 0xff);
|
|
||||||
temp.flush();
|
|
||||||
// Signature verification checks that the EOCD header is the
|
|
||||||
// last such sequence in the file (to avoid minzip finding a
|
|
||||||
// fake EOCD appended after the signature in its scan). The
|
|
||||||
// odds of producing this sequence by chance are very low, but
|
|
||||||
// let's catch it here if it does.
|
|
||||||
byte[] b = temp.toByteArray();
|
|
||||||
for (int i = 0; i < b.length-3; ++i) {
|
|
||||||
if (b[i] == 0x50 && b[i+1] == 0x4b && b[i+2] == 0x05 && b[i+3] == 0x06) {
|
|
||||||
throw new IllegalArgumentException("found spurious EOCD header at " + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outputStream.write(total_size & 0xff);
|
|
||||||
outputStream.write((total_size >> 8) & 0xff);
|
|
||||||
temp.writeTo(outputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void signFile(Manifest manifest, JarMap inputJar,
|
|
||||||
X509Certificate publicKey, PrivateKey privateKey,
|
|
||||||
JarOutputStream outputJar)
|
|
||||||
throws Exception {
|
|
||||||
// Assume the certificate is valid for at least an hour.
|
|
||||||
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
|
|
||||||
// MANIFEST.MF
|
|
||||||
JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);
|
|
||||||
je.setTime(timestamp);
|
|
||||||
outputJar.putNextEntry(je);
|
|
||||||
manifest.write(outputJar);
|
|
||||||
// CERT.SF / CERT#.SF
|
|
||||||
je = new JarEntry(CERT_SF_NAME);
|
|
||||||
je.setTime(timestamp);
|
|
||||||
outputJar.putNextEntry(je);
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
writeSignatureFile(manifest, baos, getDigestAlgorithm(publicKey));
|
|
||||||
byte[] signedData = baos.toByteArray();
|
|
||||||
outputJar.write(signedData);
|
|
||||||
// CERT.{DSA,EC,RSA} / CERT#.{DSA,EC,RSA}
|
|
||||||
je = new JarEntry((String.format(CERT_SIG_NAME, privateKey.getAlgorithm())));
|
|
||||||
je.setTime(timestamp);
|
|
||||||
outputJar.putNextEntry(je);
|
|
||||||
writeSignatureBlock(new CMSProcessableByteArray(signedData),
|
|
||||||
publicKey, privateKey, outputJar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,307 +0,0 @@
|
|||||||
#include <jni.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <zlib.h>
|
|
||||||
#include <android/log.h>
|
|
||||||
|
|
||||||
#define LOG_TAG "zipadjust"
|
|
||||||
|
|
||||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
|
||||||
|
|
||||||
size_t insize, outsize = 0, alloc = 0;
|
|
||||||
unsigned char *fin, *fout;
|
|
||||||
|
|
||||||
int zipadjust(int decompress) ;
|
|
||||||
|
|
||||||
JNIEXPORT jbyteArray JNICALL
|
|
||||||
Java_com_topjohnwu_magisk_utils_ZipUtils_zipAdjust(JNIEnv *env, jclass type, jbyteArray jbytes,
|
|
||||||
jint size) {
|
|
||||||
fin = (*env)->GetPrimitiveArrayCritical(env, jbytes, NULL);
|
|
||||||
insize = (size_t) size;
|
|
||||||
|
|
||||||
zipadjust(0);
|
|
||||||
|
|
||||||
(*env)->ReleasePrimitiveArrayCritical(env, jbytes, fin, 0);
|
|
||||||
|
|
||||||
jbyteArray ret = (*env)->NewByteArray(env, outsize);
|
|
||||||
(*env)->SetByteArrayRegion(env, ret, 0, outsize, (const jbyte*) fout);
|
|
||||||
free(fout);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma pack(1)
|
|
||||||
struct local_header_struct {
|
|
||||||
uint32_t signature;
|
|
||||||
uint16_t extract_version;
|
|
||||||
uint16_t flags;
|
|
||||||
uint16_t compression_method;
|
|
||||||
uint16_t last_modified_time;
|
|
||||||
uint16_t last_modified_date;
|
|
||||||
uint32_t crc32;
|
|
||||||
uint32_t size_compressed;
|
|
||||||
uint32_t size_uncompressed;
|
|
||||||
uint16_t length_filename;
|
|
||||||
uint16_t length_extra;
|
|
||||||
// filename
|
|
||||||
// extra
|
|
||||||
};
|
|
||||||
typedef struct local_header_struct local_header_t;
|
|
||||||
|
|
||||||
#pragma pack(1)
|
|
||||||
struct data_descriptor_struct {
|
|
||||||
uint32_t signature;
|
|
||||||
uint32_t crc32;
|
|
||||||
uint32_t size_compressed;
|
|
||||||
uint32_t size_uncompressed;
|
|
||||||
};
|
|
||||||
typedef struct data_descriptor_struct data_descriptor_t;
|
|
||||||
|
|
||||||
#pragma pack(1)
|
|
||||||
struct central_header_struct {
|
|
||||||
uint32_t signature;
|
|
||||||
uint16_t version_made;
|
|
||||||
uint16_t version_needed;
|
|
||||||
uint16_t flags;
|
|
||||||
uint16_t compression_method;
|
|
||||||
uint16_t last_modified_time;
|
|
||||||
uint16_t last_modified_date;
|
|
||||||
uint32_t crc32;
|
|
||||||
uint32_t size_compressed;
|
|
||||||
uint32_t size_uncompressed;
|
|
||||||
uint16_t length_filename;
|
|
||||||
uint16_t length_extra;
|
|
||||||
uint16_t length_comment;
|
|
||||||
uint16_t disk_start;
|
|
||||||
uint16_t attr_internal;
|
|
||||||
uint32_t attr_external;
|
|
||||||
uint32_t offset;
|
|
||||||
// filename
|
|
||||||
// extra
|
|
||||||
// comment
|
|
||||||
};
|
|
||||||
typedef struct central_header_struct central_header_t;
|
|
||||||
|
|
||||||
#pragma pack(1)
|
|
||||||
struct central_footer_struct {
|
|
||||||
uint32_t signature;
|
|
||||||
uint16_t disk_number;
|
|
||||||
uint16_t disk_number_central_directory;
|
|
||||||
uint16_t central_directory_entries_this_disk;
|
|
||||||
uint16_t central_directory_entries_total;
|
|
||||||
uint32_t central_directory_size;
|
|
||||||
uint32_t central_directory_offset;
|
|
||||||
uint16_t length_comment;
|
|
||||||
// comment
|
|
||||||
};
|
|
||||||
typedef struct central_footer_struct central_footer_t;
|
|
||||||
|
|
||||||
#define MAGIC_LOCAL_HEADER 0x04034b50
|
|
||||||
#define MAGIC_DATA_DESCRIPTOR 0x08074b50
|
|
||||||
#define MAGIC_CENTRAL_HEADER 0x02014b50
|
|
||||||
#define MAGIC_CENTRAL_FOOTER 0x06054b50
|
|
||||||
|
|
||||||
static int xerror(char* message) {
|
|
||||||
LOGE("%s\n", message);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int xseekread(off_t offset, void* buf, size_t bytes) {
|
|
||||||
memcpy(buf, fin + offset, bytes);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int xseekwrite(off_t offset, const void* buf, size_t bytes) {
|
|
||||||
if (offset + bytes > outsize) outsize = offset + bytes;
|
|
||||||
if (outsize > alloc) {
|
|
||||||
fout = realloc(fout, outsize);
|
|
||||||
alloc = outsize;
|
|
||||||
}
|
|
||||||
memcpy(fout + offset, buf, bytes);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int xfilecopy(off_t offsetIn, off_t offsetOut, size_t bytes) {
|
|
||||||
unsigned int CHUNK = 256 * 1024;
|
|
||||||
unsigned char* buf = malloc(CHUNK);
|
|
||||||
if (buf == NULL) return xerror("malloc failed");
|
|
||||||
size_t left = bytes;
|
|
||||||
while (left > 0) {
|
|
||||||
size_t wanted = (left < CHUNK) ? left : CHUNK;
|
|
||||||
xseekread(offsetIn, buf, wanted);
|
|
||||||
xseekwrite(offsetOut, buf, wanted);
|
|
||||||
offsetIn += wanted;
|
|
||||||
offsetOut += wanted;
|
|
||||||
left -= wanted;
|
|
||||||
}
|
|
||||||
free(buf);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int xdecompress(off_t offsetIn, off_t offsetOut, size_t bytes) {
|
|
||||||
unsigned int CHUNK = 256 * 1024;
|
|
||||||
|
|
||||||
int ret;
|
|
||||||
unsigned have;
|
|
||||||
z_stream strm;
|
|
||||||
unsigned char in[CHUNK];
|
|
||||||
unsigned char out[CHUNK];
|
|
||||||
|
|
||||||
strm.zalloc = Z_NULL;
|
|
||||||
strm.zfree = Z_NULL;
|
|
||||||
strm.opaque = Z_NULL;
|
|
||||||
strm.avail_in = 0;
|
|
||||||
strm.next_in = Z_NULL;
|
|
||||||
ret = inflateInit2(&strm, -15);
|
|
||||||
if (ret != Z_OK) return xerror("ret != Z_OK");
|
|
||||||
|
|
||||||
do {
|
|
||||||
strm.avail_in = insize - offsetIn;
|
|
||||||
if (strm.avail_in == 0) break;
|
|
||||||
strm.avail_in = (strm.avail_in > CHUNK) ? CHUNK : strm.avail_in;
|
|
||||||
xseekread(offsetIn, in, strm.avail_in);
|
|
||||||
strm.next_in = in;
|
|
||||||
offsetIn += strm.avail_in;
|
|
||||||
|
|
||||||
do {
|
|
||||||
strm.avail_out = CHUNK;
|
|
||||||
strm.next_out = out;
|
|
||||||
|
|
||||||
ret = inflate(&strm, Z_NO_FLUSH);
|
|
||||||
if (ret == Z_STREAM_ERROR) return xerror("Stream error");
|
|
||||||
switch (ret) {
|
|
||||||
case Z_NEED_DICT:
|
|
||||||
ret = Z_DATA_ERROR;
|
|
||||||
case Z_DATA_ERROR:
|
|
||||||
case Z_MEM_ERROR:
|
|
||||||
(void)inflateEnd(&strm);
|
|
||||||
return xerror("DICT/DATA/MEM error");
|
|
||||||
}
|
|
||||||
|
|
||||||
have = CHUNK - strm.avail_out;
|
|
||||||
xseekwrite(offsetOut, out, have);
|
|
||||||
offsetOut += have;
|
|
||||||
} while (strm.avail_out == 0);
|
|
||||||
} while (ret != Z_STREAM_END);
|
|
||||||
(void)inflateEnd(&strm);
|
|
||||||
|
|
||||||
return ret == Z_STREAM_END ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int zipadjust(int decompress) {
|
|
||||||
int ok = 0;
|
|
||||||
|
|
||||||
char filename[1024];
|
|
||||||
|
|
||||||
central_footer_t central_footer;
|
|
||||||
uint32_t central_directory_in_position = 0;
|
|
||||||
uint32_t central_directory_in_size = 0;
|
|
||||||
uint32_t central_directory_out_size = 0;
|
|
||||||
|
|
||||||
int i;
|
|
||||||
for (i = insize - 4; i >= 0; i--) {
|
|
||||||
uint32_t magic = 0;
|
|
||||||
if (!xseekread(i, &magic, sizeof(uint32_t))) return 0;
|
|
||||||
if (magic == MAGIC_CENTRAL_FOOTER) {
|
|
||||||
LOGD("central footer @ %08X\n", i);
|
|
||||||
if (!xseekread(i, ¢ral_footer, sizeof(central_footer_t))) return 0;
|
|
||||||
|
|
||||||
central_header_t central_header;
|
|
||||||
if (!xseekread(central_footer.central_directory_offset, ¢ral_header, sizeof(central_header_t))) return 0;
|
|
||||||
if ( central_header.signature == MAGIC_CENTRAL_HEADER ) {
|
|
||||||
central_directory_in_position = central_footer.central_directory_offset;
|
|
||||||
central_directory_in_size = insize - central_footer.central_directory_offset;
|
|
||||||
LOGD("central header @ %08X (%d)\n", central_footer.central_directory_offset, central_footer.central_directory_size);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (central_directory_in_position == 0) return 0;
|
|
||||||
|
|
||||||
unsigned char* central_directory_in = (unsigned char*)malloc(central_directory_in_size);
|
|
||||||
unsigned char* central_directory_out = (unsigned char*)malloc(central_directory_in_size);
|
|
||||||
if (!xseekread(central_directory_in_position, central_directory_in, central_directory_in_size)) return 0;
|
|
||||||
memset(central_directory_out, 0, central_directory_in_size);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fout = (unsigned char*) malloc(insize);
|
|
||||||
alloc = insize;
|
|
||||||
|
|
||||||
uintptr_t central_directory_in_index = 0;
|
|
||||||
uintptr_t central_directory_out_index = 0;
|
|
||||||
|
|
||||||
central_header_t* central_header = NULL;
|
|
||||||
|
|
||||||
uint32_t out_index = 0;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
central_header = (central_header_t*)¢ral_directory_in[central_directory_in_index];
|
|
||||||
if (central_header->signature != MAGIC_CENTRAL_HEADER) break;
|
|
||||||
|
|
||||||
filename[central_header->length_filename] = (char)0;
|
|
||||||
memcpy(filename, ¢ral_directory_in[central_directory_in_index + sizeof(central_header_t)], central_header->length_filename);
|
|
||||||
LOGD("%s (%d --> %d) [%08X] (%d)\n", filename, central_header->size_uncompressed, central_header->size_compressed, central_header->crc32, central_header->length_extra + central_header->length_comment);
|
|
||||||
|
|
||||||
local_header_t local_header;
|
|
||||||
if (!xseekread(central_header->offset, &local_header, sizeof(local_header_t))) return 0;
|
|
||||||
|
|
||||||
// save and update to next index before we clobber the data
|
|
||||||
uint16_t compression_method_old = central_header->compression_method;
|
|
||||||
uint32_t size_compressed_old = central_header->size_compressed;
|
|
||||||
uint32_t offset_old = central_header->offset;
|
|
||||||
uint32_t length_extra_old = central_header->length_extra;
|
|
||||||
central_directory_in_index += sizeof(central_header_t) + central_header->length_filename + central_header->length_extra + central_header->length_comment;
|
|
||||||
|
|
||||||
// copying, rewriting, and correcting local and central headers so all the information matches, and no data descriptors are necessary
|
|
||||||
central_header->offset = out_index;
|
|
||||||
central_header->flags = central_header->flags & !8;
|
|
||||||
if (decompress && (compression_method_old == 8)) {
|
|
||||||
central_header->compression_method = 0;
|
|
||||||
central_header->size_compressed = central_header->size_uncompressed;
|
|
||||||
}
|
|
||||||
central_header->length_extra = 0;
|
|
||||||
central_header->length_comment = 0;
|
|
||||||
local_header.compression_method = central_header->compression_method;
|
|
||||||
local_header.flags = central_header->flags;
|
|
||||||
local_header.crc32 = central_header->crc32;
|
|
||||||
local_header.size_uncompressed = central_header->size_uncompressed;
|
|
||||||
local_header.size_compressed = central_header->size_compressed;
|
|
||||||
local_header.length_extra = 0;
|
|
||||||
|
|
||||||
if (!xseekwrite(out_index, &local_header, sizeof(local_header_t))) return 0;
|
|
||||||
out_index += sizeof(local_header_t);
|
|
||||||
if (!xseekwrite(out_index, &filename[0], central_header->length_filename)) return 0;
|
|
||||||
out_index += central_header->length_filename;
|
|
||||||
|
|
||||||
if (decompress && (compression_method_old == 8)) {
|
|
||||||
if (!xdecompress(offset_old + sizeof(local_header_t) + central_header->length_filename + length_extra_old, out_index, size_compressed_old)) return 0;
|
|
||||||
} else {
|
|
||||||
if (!xfilecopy(offset_old + sizeof(local_header_t) + central_header->length_filename + length_extra_old, out_index, size_compressed_old)) return 0;
|
|
||||||
}
|
|
||||||
out_index += local_header.size_compressed;
|
|
||||||
|
|
||||||
memcpy(¢ral_directory_out[central_directory_out_index], central_header, sizeof(central_header_t) + central_header->length_filename);
|
|
||||||
central_directory_out_index += sizeof(central_header_t) + central_header->length_filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
central_directory_out_size = central_directory_out_index;
|
|
||||||
central_footer.central_directory_size = central_directory_out_size;
|
|
||||||
central_footer.central_directory_offset = out_index;
|
|
||||||
central_footer.length_comment = 0;
|
|
||||||
if (!xseekwrite(out_index, central_directory_out, central_directory_out_size)) return 0;
|
|
||||||
out_index += central_directory_out_size;
|
|
||||||
if (!xseekwrite(out_index, ¢ral_footer, sizeof(central_footer_t))) return 0;
|
|
||||||
|
|
||||||
LOGD("central header @ %08X (%d)\n", central_footer.central_directory_offset, central_footer.central_directory_size);
|
|
||||||
LOGD("central footer @ %08X\n", out_index);
|
|
||||||
|
|
||||||
ok = 1;
|
|
||||||
|
|
||||||
free(central_directory_in);
|
|
||||||
free(central_directory_out);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_marginTop="?attr/actionBarSize"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/install_info_card"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dp"
|
|
||||||
android:layout_marginRight="5dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:padding="5dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/install_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="5dp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/current_version_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="5dp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:maxWidth="400dp"
|
|
||||||
android:minWidth="400dp">
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/bootimage_card"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dp"
|
|
||||||
android:layout_marginRight="5dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true"
|
|
||||||
android:layout_width="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="15dp"
|
|
||||||
android:layout_marginBottom="15dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:paddingBottom="10dp"
|
|
||||||
android:text="@string/boot_image_title"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginEnd="15dp"
|
|
||||||
android:layout_marginStart="15dp">
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:id="@+id/block_spinner"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:text="@string/detect_button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/detect_bootimage"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/install_option_card"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dp"
|
|
||||||
android:layout_marginRight="5dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true"
|
|
||||||
android:layout_width="match_parent">
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="15dp"
|
|
||||||
android:layout_marginBottom="15dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:paddingBottom="10dp"
|
|
||||||
android:text="@string/advanced_settings_title"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:text="@string/keep_force_encryption"
|
|
||||||
android:id="@+id/keep_force_enc"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="25dp"
|
|
||||||
android:layout_marginStart="25dp" />
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:text="@string/keep_dm_verity"
|
|
||||||
android:id="@+id/keep_verity"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="25dp"
|
|
||||||
android:layout_marginStart="25dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/flash_button"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dp"
|
|
||||||
android:layout_marginRight="5dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:foreground="?android:attr/selectableItemBackground"
|
|
||||||
android:clickable="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="5dp"
|
|
||||||
android:layout_marginBottom="5dp"
|
|
||||||
android:layout_marginEnd="25dp"
|
|
||||||
android:layout_marginStart="25dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="50dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:srcCompat="@mipmap/ic_launcher"
|
|
||||||
android:id="@+id/imageView"
|
|
||||||
android:layout_weight="0"
|
|
||||||
android:layout_marginEnd="20dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:text="@string/magiskify"
|
|
||||||
android:ems="10"
|
|
||||||
android:gravity="center"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textAllCaps="false"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:fontFamily="sans-serif" />
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="50dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="0"
|
|
||||||
android:layout_marginStart="20dp">
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginBottom="@dimen/card_vertical_margin"
|
|
||||||
android:layout_marginEnd="@dimen/card_horizontal_margin"
|
|
||||||
android:layout_marginStart="@dimen/card_horizontal_margin"
|
|
||||||
android:layout_marginTop="@dimen/card_vertical_margin"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
|
||||||
card_view:cardCornerRadius="@dimen/card_corner_radius"
|
|
||||||
card_view:cardElevation="@dimen/card_elevation">
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:padding="@dimen/card_layout_padding">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/app_icon"
|
|
||||||
android:layout_width="@dimen/card_appicon_size"
|
|
||||||
android:layout_height="@dimen/card_appicon_size"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:scaleType="centerCrop"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:layout_alignBottom="@+id/app_icon"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingEnd="@dimen/card_appicon_size"
|
|
||||||
android:paddingStart="65dp"
|
|
||||||
android:weightSum="1">
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/app_name"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:paddingEnd="3dp"
|
|
||||||
android:paddingStart="3dp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/app_package"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="25dp"
|
|
||||||
android:ellipsize="marquee"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:marqueeRepeatLimit="marquee_forever"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:paddingEnd="3dp"
|
|
||||||
android:paddingStart="3dp"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_centerVertical="true">
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/checkbox"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:focusable="false"
|
|
||||||
android:gravity="center"
|
|
||||||
android:src="@drawable/ic_menu_overflow_material"
|
|
||||||
android:checked="false" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginBottom="@dimen/card_vertical_margin"
|
|
||||||
android:layout_marginEnd="@dimen/card_horizontal_margin"
|
|
||||||
android:layout_marginStart="@dimen/card_horizontal_margin"
|
|
||||||
android:layout_marginTop="@dimen/card_vertical_margin"
|
|
||||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
|
||||||
card_view:cardCornerRadius="@dimen/card_corner_radius"
|
|
||||||
card_view:cardElevation="@dimen/card_elevation">
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
|
||||||
android:padding="@dimen/card_layout_padding">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:id="@+id/info_layout">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="0dp"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
|
||||||
android:textIsSelectable="false"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/version_name"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/no_info_provided"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
|
||||||
android:textColor="@android:color/tertiary_text_dark"
|
|
||||||
android:textIsSelectable="false"
|
|
||||||
android:textStyle="bold|italic"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/author"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/no_info_provided"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
|
||||||
android:textColor="@android:color/tertiary_text_dark"
|
|
||||||
android:textIsSelectable="false"
|
|
||||||
android:textStyle="bold|italic"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/description"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:text="@string/no_info_provided"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
|
||||||
android:textIsSelectable="false" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/update"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:background="@drawable/ic_file_download_black"
|
|
||||||
android:backgroundTint="@color/icon_grey"
|
|
||||||
android:focusable="false"
|
|
||||||
android:gravity="end" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/expand_layout"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_below="@id/info_layout"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/changeLog"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="@dimen/card_imageview_margin"
|
|
||||||
android:layout_marginStart="@dimen/card_imageview_margin"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:padding="15dp"
|
|
||||||
android:src="@drawable/ic_changelog"
|
|
||||||
android:tint="@color/icon_grey"/>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/authorLink"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="@dimen/card_imageview_margin"
|
|
||||||
android:layout_marginStart="@dimen/card_imageview_margin"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:padding="15dp"
|
|
||||||
android:src="@drawable/ic_person"
|
|
||||||
android:tint="@color/icon_grey"/>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/supportLink"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="@dimen/card_imageview_margin"
|
|
||||||
android:layout_marginStart="@dimen/card_imageview_margin"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:padding="15dp"
|
|
||||||
android:src="@drawable/ic_help"
|
|
||||||
android:tint="@color/icon_grey"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<android.support.v4.widget.SwipeRefreshLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:id="@+id/swipeRefreshLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:layout_marginTop="?attr/actionBarSize"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:layout_marginTop="?attr/actionBarSize"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/magiskStatusView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dip"
|
|
||||||
android:layout_marginRight="5dip"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/magisk_version"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="6dp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/magisk_status_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="100dp"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:foregroundGravity="center"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/magisk_status_icon"
|
|
||||||
android:layout_width="84dp"
|
|
||||||
android:layout_height="84dp"
|
|
||||||
android:layout_gravity="center"/>
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/magisk_check_updates_progress"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/magisk_update_status"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="6dp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:text="@string/checking_for_updates" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/rootStatusView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dip"
|
|
||||||
android:layout_marginRight="5dip"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/root_status"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="6dp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/root_status_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="100dp"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:foregroundGravity="center"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/root_status_icon"
|
|
||||||
android:layout_width="84dp"
|
|
||||||
android:layout_height="84dp"
|
|
||||||
android:layout_gravity="center" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/root_info"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="6dp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/safetyNetView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dip"
|
|
||||||
android:layout_marginRight="5dip"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/safetyNet_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="100dp"
|
|
||||||
android:foregroundGravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/grey500">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/safetyNet_icon"
|
|
||||||
android:layout_width="84dp"
|
|
||||||
android:layout_height="84dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:src="@drawable/ic_safetynet"/>
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/safetyNet_check_progress"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/safetyNet_status"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="6dp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:text="@string/safetyNet_check_text" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
</android.support.v4.widget.SwipeRefreshLayout>
|
|
||||||
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 13 KiB |
@@ -1,126 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<!--Universal-->
|
|
||||||
|
|
||||||
<!--Welcome Activity-->
|
|
||||||
<string name="navigation_drawer_open">فتح درج التنقل</string>
|
|
||||||
<string name="navigation_drawer_close">إغلاق درج التنقل</string>
|
|
||||||
<string name="magiskhide">Magisk إخفاء</string>
|
|
||||||
<string name="modules">الإضافات</string>
|
|
||||||
<string name="downloads">التنزيلات</string>
|
|
||||||
<string name="log">السجل</string>
|
|
||||||
<string name="settings">الإعدادات</string>
|
|
||||||
|
|
||||||
<!--Magisk Fragment-->
|
|
||||||
<string name="magisk_version">المثبت Magisk v%1$s</string>
|
|
||||||
<string name="magisk_version_error">هل قمت بتثبيت Magisk؟</string>
|
|
||||||
|
|
||||||
<string name="magisk_update_available">Magisk v%1$.1f تحديث!</string>
|
|
||||||
<string name="cannot_check_updates">لا يمكن التحقق من التحديثات</string>
|
|
||||||
<string name="up_to_date">أحدث إصدار من %1$s مثبت</string>
|
|
||||||
|
|
||||||
<!--Root Fragment-->
|
|
||||||
|
|
||||||
<!--Module Fragment-->
|
|
||||||
<string name="no_info_provided">(لم يتم توفير أي معلومات)</string>
|
|
||||||
<string name="no_modules_found">لم يعثر على الإضافات</string>
|
|
||||||
<string name="update_file_created">سيتم تحديث الإضافة في إعادة التشغيل التالي</string>
|
|
||||||
<string name="remove_file_created">سيتم حذف الإضافة في إعادة التشغيل التالي</string>
|
|
||||||
<string name="remove_file_deleted">لن يتم حذف الإضافة في إعادة التشغيل التالي</string>
|
|
||||||
<string name="disable_file_created">سيتم تعطيل الإضافة في إعادة التشغيل التالي</string>
|
|
||||||
<string name="disable_file_removed">سيتم تمكين الإضافة في إعادة التشغيل التالي</string>
|
|
||||||
<string name="author">إنشئ بواسطة %1$s</string>
|
|
||||||
<string name="fab_flash_zip">تثبيت الملف المضغوط للإضافة</string>
|
|
||||||
|
|
||||||
<!--Repo Fragment-->
|
|
||||||
<string name="update_available">يتوفر تحديث</string>
|
|
||||||
<string name="installed">مثبت</string>
|
|
||||||
<string name="not_installed">غير مثبت</string>
|
|
||||||
<string name="changelog">التغييرات</string>
|
|
||||||
|
|
||||||
<!--Log Fragment-->
|
|
||||||
<string name="menuSaveToSd">حفظ إلى بطاقة ذاكرة SD</string>
|
|
||||||
<string name="menuSend">إرسال</string>
|
|
||||||
<string name="menuReload">إعادة تحميل</string>
|
|
||||||
<string name="menuClearLog">حذف السجل الآن</string>
|
|
||||||
<string name="logs_cleared">تم حذف السجل بنجاح</string>
|
|
||||||
<string name="log_is_empty">السجل فارغ</string>
|
|
||||||
<string name="logs_save_failed">لا يمكن كتابة السجل على بطاقة ذاكرة SD:</string>
|
|
||||||
|
|
||||||
<!--About Activity-->
|
|
||||||
<string name="about">حول</string>
|
|
||||||
<string name="app_developers">المطورين الرئيسيين</string>
|
|
||||||
<string name="app_developers_"><![CDATA[التطبيق إنشئ بواسطة <a href="https://github.com/topjohnwu">topjohnwu</a> بالتعاون مع <a href="https://github.com/d8ahazard">Digitalhigh</a> و <a href="https://github.com/dvdandroid">Dvdandroid</a>.]]></string>
|
|
||||||
<string name="app_changelog">تغييرات التطبيق</string>
|
|
||||||
<string name="translators">xx6600xx ,silent_6600</string>
|
|
||||||
<string name="app_version">إصدار التطبيق</string>
|
|
||||||
<string name="app_source_code">الشفرة المصدرية</string>
|
|
||||||
<string name="donation">التبرع</string>
|
|
||||||
<string name="app_translators">مترجم التطبيق</string>
|
|
||||||
<string name="support_thread">منتدى الدعم</string>
|
|
||||||
|
|
||||||
<!--Toasts, Dialogs-->
|
|
||||||
<string name="permissionNotGranted">أن هذه الميزة لا تعمل دون الحصول على إذن الكتابة على التخزين الخارجي.</string>
|
|
||||||
<string name="no_thanks">لا شكراً</string>
|
|
||||||
<string name="repo_install_title">تثبيت %1$s</string>
|
|
||||||
<string name="repo_install_msg">هل تريد تثبيت %1$s ?</string>
|
|
||||||
<string name="download_install">تنزيل وتثبيت</string>
|
|
||||||
<string name="download_file_error">خطأ تنزيل الملف</string>
|
|
||||||
<string name="install_error">خطأ في التثبيت!</string>
|
|
||||||
<string name="manual_install_1">الملف المضغوط وضع في %1$s</string>
|
|
||||||
<string name="manual_install_2">التثبيت في الإسترداد يدوياً</string>
|
|
||||||
<string name="invalid_zip">الملف المضغوط ليس إضافة Magisk!!</string>
|
|
||||||
<string name="reboot_title">التثبيت نجح!</string>
|
|
||||||
<string name="reboot_msg">هل تريد إعادة التشغيل الآن؟</string>
|
|
||||||
<string name="reboot">إعادة التشغيل</string>
|
|
||||||
<string name="zip_install_progress_title">تثبيت</string>
|
|
||||||
<string name="zip_install_unzip_zip_msg">فك الضغط عن الملف المضغوط …</string>
|
|
||||||
<string name="zip_install_process_zip_msg">معالجة الملف المضغوط …</string>
|
|
||||||
<string name="zip_install_progress_msg">"تثبيت %1$s …"</string>
|
|
||||||
<string name="no_magisk_title">لا يوجد Magisk مثبت!</string>
|
|
||||||
<string name="no_magisk_msg">هل ترغب في تنزيل وتثبيت Magisk؟</string>
|
|
||||||
|
|
||||||
<!--URL Templates-->
|
|
||||||
|
|
||||||
<!--Settings Activity -->
|
|
||||||
<string name="settings_general_category">عام</string>
|
|
||||||
<string name="settings_theme_title">السمة</string>
|
|
||||||
<string name="settings_theme_summary">أختر سمة</string>
|
|
||||||
<string name="theme_default">إفتراضي</string>
|
|
||||||
<string name="theme_dark">غامق</string>
|
|
||||||
|
|
||||||
<string name="settings_magiskhide_title">تمكين إخفاء Magisk</string>
|
|
||||||
<string name="settings_magiskhide_summary">إخفاء Magisk من مختلف حالات الإكتشاف</string>
|
|
||||||
<string name="settings_busybox_title">تمكين BusyBox</string>
|
|
||||||
<string name="settings_busybox_summary">ربط تحميل Magisk\'s المدمج في busybox إلى xbin</string>
|
|
||||||
<string name="settings_hosts_title">تمكين المضيفين(الهوست) لـ systemless</string>
|
|
||||||
<string name="settings_hosts_summary">Systemless يدعم تطبيقات حجب الإعلانات</string>
|
|
||||||
|
|
||||||
<string name="settings_development_category">التطوير</string>
|
|
||||||
<string name="settings_developer_logging_title">تمكين تصحيح السجلات المتقدمة</string>
|
|
||||||
<string name="settings_developer_logging_summary">حدد هذا الخيار لتمكين سجل مطول أكثر.</string>
|
|
||||||
<string name="settings_shell_logging_title">تمكين سجل تصحيح الأوامر الدفعية</string>
|
|
||||||
<string name="settings_shell_logging_summary">حدد هذا الخيار لتمكين سجل جميع الأوامر الدفعية والمخرجات</string>
|
|
||||||
|
|
||||||
<string name="settings_reboot_toast">إعادة التشغيل لتطبيق الإعدادات</string>
|
|
||||||
<string name="auto_detect">\"(تلقائي) %1$s\"</string>
|
|
||||||
<string name="checking_for_updates">البحث عن تحديثات…</string>
|
|
||||||
<string name="install">التثبيت</string>
|
|
||||||
<string name="not_rooted">غير مروت</string>
|
|
||||||
<string name="proper_root">مروت فعلاً</string>
|
|
||||||
<string name="advanced_settings_title">إعدادات متقدمة</string>
|
|
||||||
<string name="boot_image_title">موقع ملف الإقلاع</string>
|
|
||||||
<string name="checking_safetyNet_status">التحقق من حالة SafetyNet...</string>
|
|
||||||
<string name="copying_msg">نسخ الملف المضغوط إلى دليل مؤقت</string>
|
|
||||||
<string name="detect_button">تحقق</string>
|
|
||||||
<string name="downloading_toast">جاري التنزيل %1$s</string>
|
|
||||||
<string name="install_magisk_title">تثبيت Magisk الإصدار: v%1$.1f</string>
|
|
||||||
<string name="keep_force_encryption">إبقاء التشفير القوى</string>
|
|
||||||
<string name="keep_dm_verity">إبقاء dm-verity</string>
|
|
||||||
<string name="root_error">مروت لكن لا يوجد إذن الروت، غير مسموح به؟</string>
|
|
||||||
<string name="root_info_warning">وظائف محدودة إلى حد كبير</string>
|
|
||||||
<string name="safetyNet_error">تعذر التحقق من SafetyNet، لا يوجد إنترنت؟</string>
|
|
||||||
<string name="safetyNet_pass">SafetyNet تخطى</string>
|
|
||||||
<string name="safetyNet_fail">فشل SafetyNet: عدم تطابق التشكيل الجانبي CTS</string>
|
|
||||||
<string name="status">الحالة</string>
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<!--Universal-->
|
|
||||||
|
|
||||||
<!--Welcome Activity-->
|
|
||||||
<string name="navigation_drawer_open">Navigationsleiste öffnen</string>
|
|
||||||
<string name="navigation_drawer_close">Navigationsleiste schließen</string>
|
|
||||||
<string name="modules">Module</string>
|
|
||||||
<string name="downloads">Downloads</string>
|
|
||||||
<string name="log">Log</string>
|
|
||||||
<string name="settings">Einstellungen</string>
|
|
||||||
|
|
||||||
<!--Magisk Fragment-->
|
|
||||||
<string name="magisk_version">Magisk v%1$s installiert</string>
|
|
||||||
<string name="magisk_version_error">Haben Sie Magisk installiert?</string>
|
|
||||||
|
|
||||||
<string name="magisk_update_available">Magisk v%1$.1f update!</string>
|
|
||||||
<string name="cannot_check_updates">Kann nicht auf Aktualisierungen prüfen</string>
|
|
||||||
<string name="up_to_date">Aktuellste Version von %1$s installiert</string>
|
|
||||||
|
|
||||||
<!--Root Fragment-->
|
|
||||||
|
|
||||||
<!--Module Fragment-->
|
|
||||||
<string name="no_info_provided">(Nichts angegeben)</string>
|
|
||||||
<string name="no_modules_found">Keine Module gefunden</string>
|
|
||||||
<string name="update_file_created">Modul wird beim nächsten Neustart aktualisiert</string>
|
|
||||||
<string name="remove_file_created">Modul wird beim nächsten Neustart entfernt</string>
|
|
||||||
<string name="remove_file_deleted">Modul wird beim nächsten Neustart nicht entfernt</string>
|
|
||||||
<string name="disable_file_created">Modul wird beim nächsten Neustart deaktiviert</string>
|
|
||||||
<string name="disable_file_removed">Modul wird beim nächsten Neustart aktiviert</string>
|
|
||||||
<string name="author">Erstellt von %1$s</string>
|
|
||||||
|
|
||||||
<!--Repo Fragment-->
|
|
||||||
<string name="update_available">Aktualisierung verfügbar</string>
|
|
||||||
<string name="installed">Installiert</string>
|
|
||||||
<string name="not_installed">Nicht Installiert</string>
|
|
||||||
<string name="changelog">Changelog</string>
|
|
||||||
|
|
||||||
<!--Log Fragment-->
|
|
||||||
<string name="menuSaveToSd">Auf SD Karte speichern</string>
|
|
||||||
<string name="menuSend">Senden</string>
|
|
||||||
<string name="menuReload">erneut laden</string>
|
|
||||||
<string name="menuClearLog">Log jetzt löschen</string>
|
|
||||||
<string name="logs_cleared">Log erfolgreich gelöscht</string>
|
|
||||||
<string name="log_is_empty">Log ist leer</string>
|
|
||||||
<string name="logs_save_failed">Konnte Log nicht auf SD Karte speichern:</string>
|
|
||||||
|
|
||||||
<!--About Activity-->
|
|
||||||
<string name="about">Über</string>
|
|
||||||
<string name="app_developers">Entwickler</string>
|
|
||||||
<string name="app_developers_"><![CDATA[Anwendung entwickelt von <a href="https://github.com/topjohnwu">topjohnwu</a> in Zusammenarbeit mit <a href="https://github.com/d8ahazard">Digitalhigh</a> und <a href="https://github.com/dvdandroid">Dvdandroid</a>.]]></string>
|
|
||||||
<string name="app_changelog">Anwendungs changelog</string>
|
|
||||||
<string name="translators">skalnet</string>
|
|
||||||
<string name="app_version">Anwendungs Version</string>
|
|
||||||
<string name="app_source_code">Quelltext</string>
|
|
||||||
<string name="donation">Spende</string>
|
|
||||||
<string name="app_translators">Anwendungs Übersetzer</string>
|
|
||||||
<string name="support_thread">Support thread</string>
|
|
||||||
|
|
||||||
<!--Toasts, Dialogs-->
|
|
||||||
<string name="permissionNotGranted">Diese Funktion wird ohne erlaubnis auf den externen Speicher zu schreiben nicht funktionieren.</string>
|
|
||||||
<string name="no_thanks">Nein Danke</string>
|
|
||||||
<string name="repo_install_title">Installiere %1$s</string>
|
|
||||||
<string name="repo_install_msg">Wollen Sie %1$s installieren?</string>
|
|
||||||
<string name="download_install">Herunterladen und Installieren</string>
|
|
||||||
<string name="download_file_error">Fehler beim Herunterladen</string>
|
|
||||||
<string name="install_error">Fehler beim Installieren!</string>
|
|
||||||
<string name="manual_install_1">Fehler beim Flashen. Datei in %1$s abgelegt\n Bitte in recovery manuell flashen</string>
|
|
||||||
<string name="invalid_zip">Diese Datei ist kein Magisk Modul!!</string>
|
|
||||||
<string name="reboot_title">Installation erfolgreich!</string>
|
|
||||||
<string name="reboot_msg">Wollen Sie jetzt neu starten?</string>
|
|
||||||
<string name="reboot">Neustart</string>
|
|
||||||
<string name="zip_install_progress_title">Installiere</string>
|
|
||||||
<string name="zip_install_progress_msg">"Installiere %1$s …"</string>
|
|
||||||
<string name="no_magisk_title">Kein Magisk Installiert!</string>
|
|
||||||
<string name="no_magisk_msg">Wollen Sie Magisk herunterladen und installieren?</string>
|
|
||||||
|
|
||||||
<!--URL Templates-->
|
|
||||||
|
|
||||||
<!--Settings Fragment -->
|
|
||||||
<string name="settings_general_category">Allgemein</string>
|
|
||||||
<string name="settings_theme_title">Aussehen</string>
|
|
||||||
<string name="settings_theme_summary">Wähle ein Theme aus</string>
|
|
||||||
<string name="theme_default">Standard</string>
|
|
||||||
<string name="theme_dark">Dunkel</string>
|
|
||||||
|
|
||||||
<string name="settings_busybox_title">BusyBox aktivieren</string>
|
|
||||||
<string name="settings_busybox_summary">Magisks eingebautes BusyBox zum PATH hinzufügen</string>
|
|
||||||
|
|
||||||
<string name="settings_development_category">Entwicklung</string>
|
|
||||||
<string name="settings_developer_logging_title">Aktiviere erweitertes logging zum debuggen</string>
|
|
||||||
<string name="settings_developer_logging_summary">Aktivieren um ausführlichere logs zu erhalten.</string>
|
|
||||||
<string name="settings_shell_logging_title">Aktiviere debug logging für Shell Befehle</string>
|
|
||||||
<string name="settings_shell_logging_summary">Aktivieren um alle Shell Befehle und Ausgaben zu loggen.</string>
|
|
||||||
<string name="settings_magiskhide_title">Aktiviere Magisk Hide</string>
|
|
||||||
<string name="settings_magiskhide_summary">Neu starten um Einstellungen zu aktivieren</string>
|
|
||||||
|
|
||||||
<!-- Strings related to Settings -->
|
|
||||||
|
|
||||||
<!-- Example General settings -->
|
|
||||||
|
|
||||||
<!-- Example settings for Data & Sync -->
|
|
||||||
|
|
||||||
<!-- Example settings for Notifications -->
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<!--Universal-->
|
|
||||||
|
|
||||||
<!--Welcome Activity-->
|
|
||||||
<string name="navigation_drawer_open">Abrir menú lateral</string>
|
|
||||||
<string name="navigation_drawer_close">Cerrar menú lateral</string>
|
|
||||||
<string name="magiskhide">Magisk Hide</string>
|
|
||||||
<string name="modules">Modulos</string>
|
|
||||||
<string name="downloads">Descargas</string>
|
|
||||||
<string name="log">Log</string>
|
|
||||||
<string name="settings">Ajustes</string>
|
|
||||||
|
|
||||||
<!--Magisk Fragment-->
|
|
||||||
<string name="magisk_version">Instalado Magisk v%1$s</string>
|
|
||||||
<string name="magisk_version_error">¿Has instalado Magisk?</string>
|
|
||||||
|
|
||||||
<string name="magisk_update_available">¡Actualización Magisk v%1$.1f!</string>
|
|
||||||
<string name="cannot_check_updates">No se pueden buscar actualizaciones</string>
|
|
||||||
<string name="up_to_date">Última versión de %1$s instalada</string>
|
|
||||||
|
|
||||||
<!--Root Fragment-->
|
|
||||||
|
|
||||||
<!--Module Fragment-->
|
|
||||||
<string name="no_info_provided">(No hay información)</string>
|
|
||||||
<string name="no_modules_found">No se han encontrado módulos</string>
|
|
||||||
<string name="update_file_created">El módulo se actualizará en el siguiente reinicio</string>
|
|
||||||
<string name="remove_file_created">El módulo se eliminará en el siguiente reinicio</string>
|
|
||||||
<string name="remove_file_deleted">El módulo no se eliminirá en el siguiente reinicio</string>
|
|
||||||
<string name="disable_file_created">El módulo se desactivará en el siguiente reinicio</string>
|
|
||||||
<string name="disable_file_removed">El móodulo se activará en el siguiente reinicio</string>
|
|
||||||
<string name="author">Creado por %1$s</string>
|
|
||||||
<string name="fab_flash_zip">Flashear Módulo Zip</string>
|
|
||||||
|
|
||||||
<!--Repo Fragment-->
|
|
||||||
<string name="update_available">Actualización disponible</string>
|
|
||||||
<string name="installed">Instalado</string>
|
|
||||||
<string name="not_installed">No Instalado</string>
|
|
||||||
<string name="changelog">Cambios</string>
|
|
||||||
|
|
||||||
<!--Log Fragment-->
|
|
||||||
<string name="menuSaveToSd">Salvar a SD</string>
|
|
||||||
<string name="menuSend">Enviar</string>
|
|
||||||
<string name="menuReload">Recargar</string>
|
|
||||||
<string name="menuClearLog">Vaciar Log ahora</string>
|
|
||||||
<string name="logs_cleared">Log vaciado correctamente</string>
|
|
||||||
<string name="log_is_empty">El Log está vacio</string>
|
|
||||||
<string name="logs_save_failed">No se ha podido escribir el log en la tarjeta SD:</string>
|
|
||||||
|
|
||||||
<!--About Activity-->
|
|
||||||
<string name="about">Acerca de</string>
|
|
||||||
<string name="app_developers">Desarroladores principales</string>
|
|
||||||
<string name="app_developers_"><![CDATA[App created by <a href="https://github.com/topjohnwu">topjohnwu</a> in collaboration with <a href="https://github.com/d8ahazard">Digitalhigh</a> and <a href="https://github.com/dvdandroid">Dvdandroid</a>.]]></string>
|
|
||||||
<string name="app_changelog">Cambios en la aplicación</string>
|
|
||||||
<string name="translators">Gawenda, netizen</string>
|
|
||||||
<string name="app_version">Versión de la aplicación</string>
|
|
||||||
<string name="app_source_code">Código fuente</string>
|
|
||||||
<string name="donation">Donar</string>
|
|
||||||
<string name="app_translators">Traductores de la aplicación</string>
|
|
||||||
<string name="support_thread">Hilo de soporte</string>
|
|
||||||
|
|
||||||
<!--Toasts, Dialogs-->
|
|
||||||
<string name="permissionNotGranted">Esta opción no funcionará sin permiso de escritura en la memoria externa</string>
|
|
||||||
<string name="no_thanks">No gracias</string>
|
|
||||||
<string name="repo_install_title">Instalar %1$s</string>
|
|
||||||
<string name="repo_install_msg">¿ Quieres instalar %1$s ?</string>
|
|
||||||
<string name="download_install">Descargar e instalar</string>
|
|
||||||
<string name="download_file_error">Error descargando fichero</string>
|
|
||||||
<string name="install_error">¡Error en la instalación!</string>
|
|
||||||
<string name="manual_install_1">Error flasheando fichero, zip colocado en %1$s\nFlashear manualmente desde recovery</string>
|
|
||||||
<string name="invalid_zip">¡El zip no es un Módulo Magisk!</string>
|
|
||||||
<string name="reboot_title">¡Instalación correcta!</string>
|
|
||||||
<string name="reboot_msg">¿Deseas reiniciar ahora?</string>
|
|
||||||
<string name="reboot">Reinciar</string>
|
|
||||||
<string name="zip_install_progress_title">Instalando</string>
|
|
||||||
<string name="zip_install_progress_msg">"Instalando %1$s …"</string>
|
|
||||||
<string name="no_magisk_title">¡Magisk no instalado!</string>
|
|
||||||
<string name="no_magisk_msg">¿Deseas descargar e instalar Magisk?</string>
|
|
||||||
|
|
||||||
<!--URL Templates-->
|
|
||||||
|
|
||||||
<!--Settings Activity -->
|
|
||||||
<string name="settings_general_category">General</string>
|
|
||||||
<string name="settings_theme_title">Tema</string>
|
|
||||||
<string name="settings_theme_summary">Selecciona un tema</string>
|
|
||||||
<string name="theme_default">Por defecto</string>
|
|
||||||
<string name="theme_dark">Oscuro</string>
|
|
||||||
|
|
||||||
<string name="settings_magiskhide_title">Habilitar Magisk Hide</string>
|
|
||||||
<string name="settings_magiskhide_summary">Ocultar Magisk de varias detecciones</string>
|
|
||||||
<string name="settings_busybox_title">Habilitar BusyBox</string>
|
|
||||||
<string name="settings_busybox_summary">Montar el busybox interno de Magisk en xbin</string>
|
|
||||||
<string name="settings_hosts_title">Habilitar fichero hosts fuera de la partición de sistema</string>
|
|
||||||
<string name="settings_hosts_summary">Soporte para aplicaciones de bloqueo de publicidad fuera de la partición de sistema</string>
|
|
||||||
|
|
||||||
<string name="settings_development_category">Desarrollo</string>
|
|
||||||
<string name="settings_developer_logging_title">Habilitar información de depuración en el log</string>
|
|
||||||
<string name="settings_developer_logging_summary">Activar esto para grabar más información en el log</string>
|
|
||||||
<string name="settings_shell_logging_title">Grabar comandos de terminal en el log</string>
|
|
||||||
<string name="settings_shell_logging_summary">Activar esto para grabar en el log todos los comandos ejecutados y su resultado</string>
|
|
||||||
|
|
||||||
<string name="settings_reboot_toast">Reiniciar para aplicar ajustes</string>
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<!--Universal-->
|
|
||||||
|
|
||||||
<!--Welcome Activity-->
|
|
||||||
<string name="navigation_drawer_open">Apri drawer di navigazione</string>
|
|
||||||
<string name="navigation_drawer_close">Chiudi drawer di navigazione</string>
|
|
||||||
<string name="modules">Moduli</string>
|
|
||||||
<string name="downloads">Downloads</string>
|
|
||||||
<string name="log">Log</string>
|
|
||||||
<string name="settings">Impostazioni</string>
|
|
||||||
|
|
||||||
<!--Magisk Fragment-->
|
|
||||||
<string name="magisk_version">Versione Magisk: v%1$s</string>
|
|
||||||
<string name="magisk_version_error">Hai installato Magisk?</string>
|
|
||||||
|
|
||||||
<string name="magisk_update_available">Magisk v%1$.1f update!</string>
|
|
||||||
<string name="cannot_check_updates">Impossibile controllare aggiornamenti</string>
|
|
||||||
<string name="up_to_date">L\'ultima versione di %1$s è installata</string>
|
|
||||||
|
|
||||||
<!--Root Fragment-->
|
|
||||||
|
|
||||||
<!--Module Fragment-->
|
|
||||||
<string name="no_info_provided">(Nessuna informazione)</string>
|
|
||||||
<string name="no_modules_found">Nessun modulo trovato</string>
|
|
||||||
<string name="update_file_created">Il modulo sarà aggiornato al prossimo riavvio</string>
|
|
||||||
<string name="remove_file_created">Il modulo sarà rimosso al prossimo riavvio</string>
|
|
||||||
<string name="remove_file_deleted">Il modulo non sarà rimosso al prossimo riavvio</string>
|
|
||||||
<string name="disable_file_created">Il modulo sarà disattivato al prossimo riavvio</string>
|
|
||||||
<string name="disable_file_removed">Il modulo sarà abilitato al prossimo riavvio</string>
|
|
||||||
<string name="author">Creato da: %1$s</string>
|
|
||||||
|
|
||||||
<!--Repo Fragment-->
|
|
||||||
<string name="update_available">Aggiornamento disponibile</string>
|
|
||||||
<string name="installed">Installato</string>
|
|
||||||
<string name="not_installed">Non installato</string>
|
|
||||||
<string name="changelog">Changelog</string>
|
|
||||||
|
|
||||||
<!--Log Fragment-->
|
|
||||||
<string name="menuSaveToSd">Salva nella SD</string>
|
|
||||||
<string name="menuSend">Invia</string>
|
|
||||||
<string name="menuReload">Ricarica</string>
|
|
||||||
<string name="menuClearLog">Cancella log</string>
|
|
||||||
<string name="logs_cleared">Log creato con successo</string>
|
|
||||||
<string name="log_is_empty">Il log è vuoto</string>
|
|
||||||
<string name="logs_save_failed">Impossibile scrivere il log sulla SD</string>
|
|
||||||
|
|
||||||
<!--About Activity-->
|
|
||||||
<string name="about">Informazioni</string>
|
|
||||||
<string name="app_developers">Sviluppatori</string>
|
|
||||||
<string name="app_developers_"><![CDATA[App creata da <a href="https://github.com/topjohnwu">topjohnwu</a> in collaborazione con <a href="https://github.com/d8ahazard">Digitalhigh</a> e <a href="https://github.com/dvdandroid">Dvdandroid</a>.]]></string>
|
|
||||||
<string name="app_changelog">Changelog</string>
|
|
||||||
<string name="translators">Fabb2303</string>
|
|
||||||
<string name="app_version">Versione App</string>
|
|
||||||
<string name="app_source_code">Codice sorgente</string>
|
|
||||||
<string name="donation">Donazione</string>
|
|
||||||
<string name="app_translators">Traduttori App</string>
|
|
||||||
<string name="support_thread">Thread di supporto</string>
|
|
||||||
|
|
||||||
<!--Toasts, Dialogs-->
|
|
||||||
<string name="permissionNotGranted">Questa funzione non funziona senza il permesso di scrivere sulla memoria di archiviazione esterna</string>
|
|
||||||
<string name="no_thanks">No grazie</string>
|
|
||||||
<string name="repo_install_title">Installazione %1$s</string>
|
|
||||||
<string name="repo_install_msg">Vuoi installare %1$s ?</string>
|
|
||||||
<string name="download_install">Scarica e installa</string>
|
|
||||||
<string name="download_file_error">Errore nel download del file</string>
|
|
||||||
<string name="install_error">Errore di installazione!</string>
|
|
||||||
<string name="manual_install_1">Errore nel flash del file, il file zip è in %1$s\nFlash esegui il flash manuale</string>
|
|
||||||
<string name="invalid_zip">Lo zip non è un Modulo Magisk!!</string>
|
|
||||||
<string name="reboot_title">Installazione completata</string>
|
|
||||||
<string name="reboot_msg">Vuoi riavviare ora?</string>
|
|
||||||
<string name="reboot">Riavvia</string>
|
|
||||||
<string name="zip_install_progress_title">Installazione</string>
|
|
||||||
<string name="zip_install_progress_msg">"Installazione %1$s …"</string>
|
|
||||||
<string name="no_magisk_title">Magisk non installato!</string>
|
|
||||||
<string name="no_magisk_msg">Vuoi scaricare ed installare Magisk?</string>
|
|
||||||
|
|
||||||
<!--URL Templates-->
|
|
||||||
|
|
||||||
<!--Settings Fragment -->
|
|
||||||
<string name="settings_general_category">Generali</string>
|
|
||||||
<string name="settings_theme_title">Tema</string>
|
|
||||||
<string name="settings_theme_summary">Scegli un tema</string>
|
|
||||||
<string name="theme_default">Default</string>
|
|
||||||
<string name="theme_dark">Scuro</string>
|
|
||||||
|
|
||||||
<string name="settings_busybox_title">Abilita BusyBox</string>
|
|
||||||
<string name="settings_busybox_summary">Make Magisk\'s built-in BusyBox be visible in PATH</string>
|
|
||||||
|
|
||||||
<string name="settings_development_category">Development</string>
|
|
||||||
<string name="settings_developer_logging_title">Abilita Debug log avanzato</string>
|
|
||||||
<string name="settings_developer_logging_summary">Abilita questa funzione per avere un log più dettagliato.</string>
|
|
||||||
<string name="settings_shell_logging_title">Abilita shell di registrazione dei comandi di debug</string>
|
|
||||||
<string name="settings_shell_logging_summary">Abilita questa funzione per abilitare la registrazione tutti i comandi e l\'output della shell</string>
|
|
||||||
|
|
||||||
<!-- Strings related to Settings -->
|
|
||||||
|
|
||||||
<!-- Example General settings -->
|
|
||||||
|
|
||||||
<!-- Example settings for Data & Sync -->
|
|
||||||
|
|
||||||
<!-- Example settings for Notifications -->
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<!--Universal-->
|
|
||||||
|
|
||||||
<!--Welcome Activity-->
|
|
||||||
<string name="navigation_drawer_open">Open navigatie</string>
|
|
||||||
<string name="navigation_drawer_close">Sluit navigatie</string>
|
|
||||||
<string name="modules">Modules</string>
|
|
||||||
<string name="downloads">Downloads</string>
|
|
||||||
<string name="log">Log</string>
|
|
||||||
<string name="settings">Instellingen</string>
|
|
||||||
|
|
||||||
<!--Magisk Fragment-->
|
|
||||||
<string name="magisk_version">Geïnstalleerde Magisk: v%1$s</string>
|
|
||||||
<string name="magisk_version_error">Heb jij Magisk wel geïnstalleerd?</string>
|
|
||||||
|
|
||||||
<string name="magisk_update_available">Magisk v%1$.1f update!</string>
|
|
||||||
<string name="cannot_check_updates">Kan niet controleren op updates.</string>
|
|
||||||
<string name="up_to_date">Nieuwste versie van %1$s geïnstalleerd</string>
|
|
||||||
|
|
||||||
<!--Root Fragment-->
|
|
||||||
|
|
||||||
<!--Module Fragment-->
|
|
||||||
<string name="no_info_provided">(Geen informatie gegeven)</string>
|
|
||||||
<string name="no_modules_found">Geen modules gevonden</string>
|
|
||||||
<string name="update_file_created">Module wordt geüpdatet bij de volgende reboot.</string>
|
|
||||||
<string name="remove_file_created">Module wordt verwijderd bij de volgende reboot.</string>
|
|
||||||
<string name="remove_file_deleted">Module wordt niet verwijderd bij de volgende reboot.</string>
|
|
||||||
<string name="disable_file_created">Module wordt uitgeschakeld bij de volgende reboot.</string>
|
|
||||||
<string name="disable_file_removed">Module wordt ingeschakeld bij de volgende reboot.</string>
|
|
||||||
<string name="author">Gemaakt door: %1$s</string>
|
|
||||||
|
|
||||||
<!--Repo Fragment-->
|
|
||||||
<string name="update_available">Update Beschikbaar</string>
|
|
||||||
<string name="installed">Geïnstalleerd</string>
|
|
||||||
<string name="not_installed">Niet geïnstalleerd</string>
|
|
||||||
<string name="changelog">Changelog</string>
|
|
||||||
|
|
||||||
<!--Log Fragment-->
|
|
||||||
<string name="menuSaveToSd">Opslaan op SD-kaart</string>
|
|
||||||
<string name="menuSend">Verzenden</string>
|
|
||||||
<string name="menuReload">Herladen</string>
|
|
||||||
<string name="menuClearLog">Maak log leeg</string>
|
|
||||||
<string name="logs_cleared">Log succesvol geleegd</string>
|
|
||||||
<string name="log_is_empty">Log is leeg</string>
|
|
||||||
<string name="logs_save_failed">Kon niet naar SD-kaart schrijven:</string>
|
|
||||||
|
|
||||||
<!--About Activity-->
|
|
||||||
<string name="about">Over</string>
|
|
||||||
<string name="app_developers">Hoofddevelopers</string>
|
|
||||||
<string name="app_developers_"><![CDATA[App gemaakt door <a href="https://github.com/topjohnwu">topjohnwu</a> in samenwerking met <a href="https://github.com/d8ahazard">Digitalhigh</a> en <a href="https://github.com/dvdandroid">Dvdandroid</a>.]]></string>
|
|
||||||
<string name="app_changelog">App\'s changelog</string>
|
|
||||||
<string name="translators">NaamloosDT, Klaessen</string>
|
|
||||||
<string name="app_version">App\'s versie</string>
|
|
||||||
<string name="app_source_code">Source code</string>
|
|
||||||
<string name="donation">Donatie</string>
|
|
||||||
<string name="app_translators">App\'s vertalers</string>
|
|
||||||
<string name="support_thread">Support thread</string>
|
|
||||||
|
|
||||||
<!--Toasts, Dialogs-->
|
|
||||||
<string name="permissionNotGranted">Deze feature zal niet werken zonder permissie om op de externe opslag te schrijven.</string>
|
|
||||||
<string name="no_thanks">Nee bedankt</string>
|
|
||||||
<string name="repo_install_title">Installeer %1$s</string>
|
|
||||||
<string name="repo_install_msg">Wilt u %1$s installeren?</string>
|
|
||||||
<string name="download_install">Downloaden en installeren</string>
|
|
||||||
<string name="download_file_error">Er is een fout opgetreden bij het downloaden van het bestand</string>
|
|
||||||
<string name="install_error">Er is een fout opgetreden in de installatie</string>
|
|
||||||
<string name="manual_install_1">Er is een fout opgetreden in het flashen van het bestand. Het zip bestand is opgeslagen in %1$s\nFlash het handmatig in recovery modus.</string>
|
|
||||||
<string name="invalid_zip">Zip bestand is geen Magisk Module!</string>
|
|
||||||
<string name="reboot_title">Installatie succesvol!</string>
|
|
||||||
<string name="reboot_msg">Wilt u nu rebooten?</string>
|
|
||||||
<string name="reboot">Reboot</string>
|
|
||||||
<string name="zip_install_progress_title">Installeren</string>
|
|
||||||
<string name="zip_install_progress_msg">"Instalatievoortgang: %1$s …"</string>
|
|
||||||
<string name="no_magisk_title">Geen Magisk geïnstalleerd!</string>
|
|
||||||
<string name="no_magisk_msg">Wilt u Magisk downloaden en installeren?</string>
|
|
||||||
|
|
||||||
<!--URL Templates-->
|
|
||||||
|
|
||||||
<!--Settings Fragment -->
|
|
||||||
<string name="settings_general_category">Algemeen</string>
|
|
||||||
<string name="settings_theme_title">Thema</string>
|
|
||||||
<string name="settings_theme_summary">Selecteer een thema</string>
|
|
||||||
<string name="theme_default">Standaard</string>
|
|
||||||
<string name="theme_dark">Donker</string>
|
|
||||||
|
|
||||||
<string name="settings_busybox_title">Schakel BusyBox in</string>
|
|
||||||
<string name="settings_busybox_summary">Maakt Magisk\'s ingebouwde BusyBox zichtbaar in PATH</string>
|
|
||||||
|
|
||||||
<string name="settings_development_category">Development</string>
|
|
||||||
<string name="settings_developer_logging_title">Geavanceerde debug logging</string>
|
|
||||||
<string name="settings_developer_logging_summary">Schakel dit in voor uitgebreidere logging.</string>
|
|
||||||
<string name="settings_shell_logging_title">Shell command debug loggin</string>
|
|
||||||
<string name="settings_shell_logging_summary">Schakel dit in om alle shell commands en output te loggen</string>
|
|
||||||
<string name="settings_magiskhide_title">Magisk verbergen</string>
|
|
||||||
<string name="settings_magiskhide_summary">Reboot om de instellingen toe te passen</string>
|
|
||||||
|
|
||||||
<!-- Strings related to Settings -->
|
|
||||||
|
|
||||||
<!-- Example General settings -->
|
|
||||||
|
|
||||||
<!-- Example settings for Data & Sync -->
|
|
||||||
|
|
||||||
<!-- Example settings for Notifications -->
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<!--Welcome Activity-->
|
|
||||||
<string name="navigation_drawer_open">Abrir gaveta de notificação</string>
|
|
||||||
<string name="navigation_drawer_close">Fechar gaveta de notificação</string>
|
|
||||||
<string name="magiskhide">Magisk Hide</string>
|
|
||||||
<string name="modules">Módulos</string>
|
|
||||||
<string name="downloads">Baixar</string>
|
|
||||||
<string name="log">Registro</string>
|
|
||||||
<string name="settings">Configurações</string>
|
|
||||||
<string name="status">Status</string>
|
|
||||||
<string name="install">Instalar</string>
|
|
||||||
|
|
||||||
<!--Status Fragment-->
|
|
||||||
<string name="magisk_version">Magisk v%1$s Instalado</string>
|
|
||||||
<string name="magisk_version_error">Magisk não instalado</string>
|
|
||||||
|
|
||||||
<string name="checking_for_updates">Checando por atualizações…</string>
|
|
||||||
<string name="magisk_update_available">Magisk v%1$.1f disponível!</string>
|
|
||||||
<string name="cannot_check_updates">Não é possível verificar se há atualizações</string>
|
|
||||||
<string name="up_to_date">Última versão do %1$s instalado</string>
|
|
||||||
<string name="root_error">Rooteado mas sem permissão de root, o acesso foi permitido?</string>
|
|
||||||
<string name="not_rooted">Sem root</string>
|
|
||||||
<string name="proper_root">Rooteado</string>
|
|
||||||
<string name="safetyNet_check_text">Pressione para checar o SafetyNet</string>
|
|
||||||
<string name="checking_safetyNet_status">Checando status do SafetyNet…</string>
|
|
||||||
<string name="safetyNet_connection_failed">Não é possível conectar-se à API do Google</string>
|
|
||||||
<string name="safetyNet_connection_suspended">A conexão com API do Google foi suspensa</string>
|
|
||||||
<string name="safetyNet_error">Não é possível verificar o SafetyNet, sem Internet?</string>
|
|
||||||
<string name="safetyNet_fail">SafetyNet Falhou: CTS profile mismatch</string>
|
|
||||||
<string name="safetyNet_pass">SafetyNet Passado</string>
|
|
||||||
<string name="root_info_warning">Funcionalidade muito limitada</string>
|
|
||||||
|
|
||||||
<!--Install Fragment-->
|
|
||||||
<string name="auto_detect">"(Auto) %1$s"</string>
|
|
||||||
<string name="boot_image_title">Local da Boot Image</string>
|
|
||||||
<string name="detect_button">Detectar</string>
|
|
||||||
<string name="advanced_settings_title">Configurações avançadas</string>
|
|
||||||
<string name="keep_force_encryption">Keep force encryption</string>
|
|
||||||
<string name="keep_dm_verity">Keep dm-verity</string>
|
|
||||||
<string name="current_magisk_title">Versão instalada do Magisk: v%1$s</string>
|
|
||||||
<string name="install_magisk_title">Última versão do Magisk: v%1$.1f</string>
|
|
||||||
|
|
||||||
<!--Module Fragment-->
|
|
||||||
<string name="no_info_provided">(Nenhuma informação fornecida)</string>
|
|
||||||
<string name="no_modules_found">Nenhum módulo encontrado</string>
|
|
||||||
<string name="update_file_created">Módulo será atualizado na próxima reinicialização</string>
|
|
||||||
<string name="remove_file_created">Módulo será removido na próxima reinicialização</string>
|
|
||||||
<string name="remove_file_deleted">Módulo não será removido na próxima reinicialização</string>
|
|
||||||
<string name="disable_file_created">Módulo será desativado na próxima reinicialização</string>
|
|
||||||
<string name="disable_file_removed">Módulo será ativado na próxima reinicialização</string>
|
|
||||||
<string name="author">Criado por %1$s</string>
|
|
||||||
<string name="fab_flash_zip">Flashear Módulo Zip</string>
|
|
||||||
|
|
||||||
<!--Repo Fragment-->
|
|
||||||
<string name="update_available">Atualização disponível</string>
|
|
||||||
<string name="installed">Instalado</string>
|
|
||||||
<string name="not_installed">Não Instalado</string>
|
|
||||||
<string name="changelog">Registro de mudança</string>
|
|
||||||
|
|
||||||
<!--Log Fragment-->
|
|
||||||
<string name="menuSaveToSd">Salvar no SD</string>
|
|
||||||
<string name="menuSend">Enviar</string>
|
|
||||||
<string name="menuReload">Recarregar</string>
|
|
||||||
<string name="menuClearLog">Limpar registro agora</string>
|
|
||||||
<string name="logs_cleared">Registro limpado com sucesso</string>
|
|
||||||
<string name="log_is_empty">Registro está vazio</string>
|
|
||||||
<string name="logs_save_failed">Não foi possível gravar o registro para o cartão SD:</string>
|
|
||||||
|
|
||||||
<!--About Activity-->
|
|
||||||
<string name="about">Sobre</string>
|
|
||||||
<string name="app_developers">Principais desenvolvedores</string>
|
|
||||||
<string name="app_developers_"><![CDATA[App criado por <a href="https://github.com/topjohnwu">topjohnwu</a> em colaboração com <a href="https://github.com/d8ahazard">Digitalhigh</a> e <a href="https://github.com/dvdandroid">Dvdandroid</a>.]]></string>
|
|
||||||
<string name="app_changelog">Registro de Mudanças do App</string>
|
|
||||||
<string name="translators">Killer7Mod</string>
|
|
||||||
<string name="app_version">Versão do App</string>
|
|
||||||
<string name="app_source_code">Código fonte</string>
|
|
||||||
<string name="donation">Doação</string>
|
|
||||||
<string name="app_translators">Tradutores do App</string>
|
|
||||||
<string name="support_thread">Suporte</string>
|
|
||||||
|
|
||||||
<!--Toasts, Dialogs-->
|
|
||||||
<string name="permissionNotGranted">Este recurso não funcionará sem permissão de escrita do armazenamento externo.</string>
|
|
||||||
<string name="no_thanks">Não, Obrigado</string>
|
|
||||||
<string name="repo_install_title">Instalar %1$s</string>
|
|
||||||
<string name="repo_install_msg">Você deseja instalar%1$s ?</string>
|
|
||||||
<string name="download_install">Baixar & instalar</string>
|
|
||||||
<string name="goto_install">Ir na seção \"Instalar\"</string>
|
|
||||||
<string name="download_file_error">Erro ao baixar o arquivo</string>
|
|
||||||
<string name="install_error">Erro na instalação!</string>
|
|
||||||
<string name="manual_install_1">Erro ao flashear o arquivo, arquivo zip colocado em %1$s</string>
|
|
||||||
<string name="manual_install_2">Flashear isto na recuperação manualmente</string>
|
|
||||||
<string name="invalid_zip">O zip não é um Módulo Magisk!!</string>
|
|
||||||
<string name="reboot_title">Instalação bem-sucedida!</string>
|
|
||||||
<string name="reboot_msg">Você quer reiniciar agora?</string>
|
|
||||||
<string name="reboot">Reiniciar</string>
|
|
||||||
<string name="copying_msg">Copiando zip para diretório temporário</string>
|
|
||||||
<string name="zip_install_progress_title">Instalando</string>
|
|
||||||
<string name="zip_install_unzip_zip_msg">Descompactando arquivo zip …</string>
|
|
||||||
<string name="zip_install_process_zip_msg">Processando arquivo zip …</string>
|
|
||||||
<string name="zip_install_progress_msg">"Instalando %1$s …"</string>
|
|
||||||
<string name="no_magisk_title">Magisk Não Instalado!</string>
|
|
||||||
<string name="no_magisk_msg">Você quer baixar e instalar o Magisk?</string>
|
|
||||||
<string name="downloading_toast">Baixando %1$s</string>
|
|
||||||
<string name="magisk_update_title">Nova atualização do Magisk disponível!</string>
|
|
||||||
<string name="magisk_update_message">Magisk v%1$.1f Atualização está pronta, você quer instalar?</string>
|
|
||||||
<string name="check_release_notes">Verificar as notas da atualização</string>
|
|
||||||
|
|
||||||
<!--Settings Fragment -->
|
|
||||||
<string name="settings_general_category">Geral</string>
|
|
||||||
<string name="settings_theme_title">Tema</string>
|
|
||||||
<string name="settings_theme_summary">Escolha um tema</string>
|
|
||||||
<string name="theme_default">Padrão</string>
|
|
||||||
<string name="theme_dark">Escuro</string>
|
|
||||||
|
|
||||||
<string name="settings_magiskhide_title">Ativar Magisk Hide</string>
|
|
||||||
<string name="settings_magiskhide_summary">Ocultar Magisk de várias detecções</string>
|
|
||||||
<string name="settings_busybox_title">Ativar BusyBox</string>
|
|
||||||
<string name="settings_busybox_summary">Monta a busybox interna do Magisk\'s para xbin</string>
|
|
||||||
<string name="settings_hosts_title">Ativar systemless hosts</string>
|
|
||||||
<string name="settings_hosts_summary">Suporte do systemless para Adblock apps</string>
|
|
||||||
|
|
||||||
<string name="settings_development_category">Desenvolvimento</string>
|
|
||||||
<string name="settings_developer_logging_title">Ativar registro mais detalhado</string>
|
|
||||||
<string name="settings_developer_logging_summary">Marque essa opção para habilitar um registro mais detalhado</string>
|
|
||||||
<string name="settings_shell_logging_title">Ativar registro da shell de comando</string>
|
|
||||||
<string name="settings_shell_logging_summary">Marque esta opção para habilitar o registro de todos os comandos shell e de saída</string>
|
|
||||||
|
|
||||||
<string name="settings_reboot_toast">Reinicie para aplicar configurações</string>
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<style name="AppTheme.Transparent" parent="Theme.AppCompat.Light.NoActionBar">
|
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
|
||||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
|
||||||
<item name="android:windowIsTranslucent">true</item>
|
|
||||||
<item name="android:windowNoTitle">true</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<!--Universal-->
|
|
||||||
|
|
||||||
<!--Welcome Activity-->
|
|
||||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
|
||||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
|
||||||
<string name="magiskhide">Magisk 隐藏</string>
|
|
||||||
<string name="modules">模块</string>
|
|
||||||
<string name="downloads">下载</string>
|
|
||||||
<string name="log">日志</string>
|
|
||||||
<string name="settings">设置</string>
|
|
||||||
<string name="status">状态</string>
|
|
||||||
<string name="install">安装</string>
|
|
||||||
|
|
||||||
<!--Status Fragment-->
|
|
||||||
<string name="magisk_version">已安装 Magisk v%1$s</string>
|
|
||||||
<string name="magisk_version_error">你是否已安装 Magisk?</string>
|
|
||||||
|
|
||||||
<string name="checking_for_updates">正在检查更新…</string>
|
|
||||||
<string name="magisk_update_available">Magisk 可更新到 v%1$.1f!</string>
|
|
||||||
<string name="cannot_check_updates">无法检查更新,没有网络连接?</string>
|
|
||||||
<string name="up_to_date">已安装最新版本的 %1$s</string>
|
|
||||||
<string name="root_error">已 ROOT 但没有 ROOT 权限,未授予权限?</string>
|
|
||||||
<string name="not_rooted">未 ROOT</string>
|
|
||||||
<string name="proper_root">已正确 ROOT</string>
|
|
||||||
<string name="checking_safetyNet_status">正在检查 SafetyNet 状态…</string>
|
|
||||||
<string name="safetyNet_error">无法检查 SafetyNet,没有网络连接?</string>
|
|
||||||
<string name="safetyNet_fail">SafetyNet 失败:CTS 配置文件不匹配</string>
|
|
||||||
<string name="safetyNet_pass">SafetyNet 已通过</string>
|
|
||||||
<string name="root_info_warning">功能严重受限</string>
|
|
||||||
|
|
||||||
<!--Install Fragment-->
|
|
||||||
<string name="auto_detect">"(自动) %1$s"</string>
|
|
||||||
<string name="boot_image_title">Boot 镜像位置</string>
|
|
||||||
<string name="detect_button">检测</string>
|
|
||||||
<string name="advanced_settings_title">高级设置</string>
|
|
||||||
<string name="keep_force_encryption">保持强制加密</string>
|
|
||||||
<string name="keep_dm_verity">保持 dm-verity</string>
|
|
||||||
<string name="install_magisk_title">安装 Magisk 版本:v%1$.1f</string>
|
|
||||||
|
|
||||||
<!--Module Fragment-->
|
|
||||||
<string name="no_info_provided">(未提供信息)</string>
|
|
||||||
<string name="no_modules_found">未找到模块</string>
|
|
||||||
<string name="update_file_created">模块将在下次重启后更新</string>
|
|
||||||
<string name="remove_file_created">模块将在下次重启后移除</string>
|
|
||||||
<string name="remove_file_deleted">模块将不会在下次重启后移除</string>
|
|
||||||
<string name="disable_file_created">模块将在下次重启后禁用</string>
|
|
||||||
<string name="disable_file_removed">模块将在下次重启后启用</string>
|
|
||||||
<string name="author">作者:%1$s</string>
|
|
||||||
<string name="fab_flash_zip">刷入模块 Zip</string>
|
|
||||||
|
|
||||||
<!--Repo Fragment-->
|
|
||||||
<string name="update_available">可更新</string>
|
|
||||||
<string name="installed">已安装</string>
|
|
||||||
<string name="not_installed">未安装</string>
|
|
||||||
<string name="changelog">更新日志</string>
|
|
||||||
|
|
||||||
<!--Log Fragment-->
|
|
||||||
<string name="menuSaveToSd">保存到 SD 卡</string>
|
|
||||||
<string name="menuSend">发送</string>
|
|
||||||
<string name="menuReload">重载</string>
|
|
||||||
<string name="menuClearLog">清除日志</string>
|
|
||||||
<string name="logs_cleared">日志已成功清除</string>
|
|
||||||
<string name="log_is_empty">日志为空</string>
|
|
||||||
<string name="logs_save_failed">无法将日志写入 SD 卡:</string>
|
|
||||||
|
|
||||||
<!--About Activity-->
|
|
||||||
<string name="about">关于</string>
|
|
||||||
<string name="app_developers">主要开发者</string>
|
|
||||||
<string name="app_developers_"><![CDATA[此应用由 <a href="https://github.com/topjohnwu">topjohnwu</a> 与 <a href="https://github.com/d8ahazard">Digitalhigh</a> 和 <a href="https://github.com/dvdandroid">Dvdandroid</a> 合作开发。]]></string>
|
|
||||||
<string name="app_changelog">应用更新日志</string>
|
|
||||||
<string name="translators">gh2923</string>
|
|
||||||
<string name="app_version">应用版本</string>
|
|
||||||
<string name="app_source_code">源代码</string>
|
|
||||||
<string name="donation">捐赠</string>
|
|
||||||
<string name="app_translators">翻译者</string>
|
|
||||||
<string name="support_thread">支持主题</string>
|
|
||||||
|
|
||||||
<!--Toasts, Dialogs-->
|
|
||||||
<string name="permissionNotGranted">未授予写入外置存储权限,此功能无法正常工作。</string>
|
|
||||||
<string name="no_thanks">不,谢谢</string>
|
|
||||||
<string name="repo_install_title">安装 %1$s</string>
|
|
||||||
<string name="repo_install_msg">你想要安装 %1$s 吗?</string>
|
|
||||||
<string name="download_install">下载并安装</string>
|
|
||||||
<string name="goto_install">前往“安装”界面</string>
|
|
||||||
<string name="download_file_error">下载文件时出错</string>
|
|
||||||
<string name="install_error">安装出错!</string>
|
|
||||||
<string name="manual_install_1">Zip 文件已保存至 %1$s</string>
|
|
||||||
<string name="manual_install_2">请在 Recovery 中手动刷入</string>
|
|
||||||
<string name="invalid_zip">此 zip 文件不是 Magisk 模块!!</string>
|
|
||||||
<string name="reboot_title">安装成功!</string>
|
|
||||||
<string name="reboot_msg">你想要立即重启吗?</string>
|
|
||||||
<string name="reboot">重启</string>
|
|
||||||
<string name="copying_msg">正在复制 zip 到临时目录</string>
|
|
||||||
<string name="zip_install_progress_title">正在安装</string>
|
|
||||||
<string name="zip_install_unzip_zip_msg">正在解压 zip 文件 …</string>
|
|
||||||
<string name="zip_install_process_zip_msg">正在处理 zip 文件 …</string>
|
|
||||||
<string name="zip_install_progress_msg">"正在安装 %1$s …"</string>
|
|
||||||
<string name="no_magisk_title">未安装 Magisk!</string>
|
|
||||||
<string name="no_magisk_msg">你想要下载并安装 Magisk 吗?</string>
|
|
||||||
<string name="downloading_toast">正在下载 %1$s</string>
|
|
||||||
|
|
||||||
<!--URL Templates-->
|
|
||||||
|
|
||||||
<!--Settings Activity -->
|
|
||||||
<string name="settings_general_category">常规</string>
|
|
||||||
<string name="settings_theme_title">主题</string>
|
|
||||||
<string name="settings_theme_summary">选择一个主题</string>
|
|
||||||
<string name="theme_default">默认</string>
|
|
||||||
<string name="theme_dark">深色</string>
|
|
||||||
|
|
||||||
<string name="settings_magiskhide_title">启用 Magisk 隐藏</string>
|
|
||||||
<string name="settings_magiskhide_summary">隐藏 Magisk 使其不被多种方法检测到</string>
|
|
||||||
<string name="settings_busybox_title">启用 BusyBox</string>
|
|
||||||
<string name="settings_busybox_summary">将 Magisk 内置的 Busybox 挂载到 xbin</string>
|
|
||||||
<string name="settings_hosts_title">启用 Systemless hosts</string>
|
|
||||||
<string name="settings_hosts_summary">Systemless 支持广告屏蔽应用</string>
|
|
||||||
|
|
||||||
<string name="settings_development_category">开发</string>
|
|
||||||
<string name="settings_developer_logging_title">启用高级调试日志记录</string>
|
|
||||||
<string name="settings_developer_logging_summary">勾选此项以启用更加详细的日志记录。</string>
|
|
||||||
<string name="settings_shell_logging_title">启用 shell 命令调试日志记录</string>
|
|
||||||
<string name="settings_shell_logging_summary">勾选此项以启用对所有 shell 命令及输出的日志记录</string>
|
|
||||||
|
|
||||||
<string name="settings_reboot_toast">重启以应用设置</string>
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string-array name="themes">
|
|
||||||
<item>@string/theme_default</item>
|
|
||||||
<item>@string/theme_dark</item>
|
|
||||||
</string-array>
|
|
||||||
<string-array name="themes_values">
|
|
||||||
<item>@string/theme_default_value</item>
|
|
||||||
<item>@string/theme_dark_value</item>
|
|
||||||
</string-array>
|
|
||||||
</resources>
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<!--Universal-->
|
|
||||||
<string name="app_name" translatable="false">Magisk Manager</string>
|
|
||||||
<string name="magisk" translatable="false">Magisk</string>
|
|
||||||
|
|
||||||
<!--Welcome Activity-->
|
|
||||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
|
||||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
|
||||||
<string name="magiskhide">Magisk Hide</string>
|
|
||||||
<string name="modules">Modules</string>
|
|
||||||
<string name="downloads">Downloads</string>
|
|
||||||
<string name="log">Log</string>
|
|
||||||
<string name="settings">Settings</string>
|
|
||||||
<string name="status">Status</string>
|
|
||||||
<string name="install">Install</string>
|
|
||||||
|
|
||||||
<!--Status Fragment-->
|
|
||||||
<string name="magisk_version">Installed Magisk v%1$s</string>
|
|
||||||
<string name="magisk_version_error">Magisk not installed</string>
|
|
||||||
|
|
||||||
<string name="checking_for_updates">Checking for updates…</string>
|
|
||||||
<string name="magisk_update_available">Magisk v%1$.1f available!</string>
|
|
||||||
<string name="cannot_check_updates">Cannot check for updates, no Internet?</string>
|
|
||||||
<string name="up_to_date">Latest version of %1$s installed</string>
|
|
||||||
<string name="root_error">Rooted but no root permission, not allowed?</string>
|
|
||||||
<string name="not_rooted">Not rooted</string>
|
|
||||||
<string name="proper_root">Properly rooted</string>
|
|
||||||
<string name="safetyNet_check_text">Tap to start SafetyNet check</string>
|
|
||||||
<string name="checking_safetyNet_status">Checking SafetyNet status…</string>
|
|
||||||
<string name="safetyNet_connection_failed">Cannot connect to Google API</string>
|
|
||||||
<string name="safetyNet_connection_suspended">Connection to Google API was suspended</string>
|
|
||||||
<string name="safetyNet_error">Cannot check SafetyNet, no Internet?</string>
|
|
||||||
<string name="safetyNet_fail">SafetyNet Failed: CTS profile mismatch</string>
|
|
||||||
<string name="safetyNet_pass">SafetyNet Passed</string>
|
|
||||||
<string name="root_info_warning">Functionality greatly limited</string>
|
|
||||||
|
|
||||||
<!--Install Fragment-->
|
|
||||||
<string name="auto_detect">"(Auto) %1$s"</string>
|
|
||||||
<string name="boot_image_title">Boot Image Location</string>
|
|
||||||
<string name="detect_button">Detect</string>
|
|
||||||
<string name="advanced_settings_title">Advanced Settings</string>
|
|
||||||
<string name="keep_force_encryption">Keep force encryption</string>
|
|
||||||
<string name="keep_dm_verity">Keep dm-verity</string>
|
|
||||||
<string name="current_magisk_title">Installed Magisk Version: v%1$s</string>
|
|
||||||
<string name="install_magisk_title">Latest Magisk Version: v%1$.1f</string>
|
|
||||||
<string name="magiskify" translatable="false">Magiskify</string>
|
|
||||||
|
|
||||||
<!--Module Fragment-->
|
|
||||||
<string name="no_info_provided">(No info provided)</string>
|
|
||||||
<string name="no_modules_found">No modules found</string>
|
|
||||||
<string name="update_file_created">Module will be updated at next reboot</string>
|
|
||||||
<string name="remove_file_created">Module will be removed at next reboot</string>
|
|
||||||
<string name="remove_file_deleted">Module will not be removed at next reboot</string>
|
|
||||||
<string name="disable_file_created">Module will be disabled at next reboot</string>
|
|
||||||
<string name="disable_file_removed">Module will be enabled at next reboot</string>
|
|
||||||
<string name="author">Created by %1$s</string>
|
|
||||||
<string name="fab_flash_zip">Flash Module Zip</string>
|
|
||||||
|
|
||||||
<!--Repo Fragment-->
|
|
||||||
<string name="update_available">Update Available</string>
|
|
||||||
<string name="installed">Installed</string>
|
|
||||||
<string name="not_installed">Not Installed</string>
|
|
||||||
<string name="changelog">Changelog</string>
|
|
||||||
|
|
||||||
<!--Log Fragment-->
|
|
||||||
<string name="menuSaveToSd">Save to SD</string>
|
|
||||||
<string name="menuSend">Send</string>
|
|
||||||
<string name="menuReload">Reload</string>
|
|
||||||
<string name="menuClearLog">Clear log now</string>
|
|
||||||
<string name="logs_cleared">Log successfully cleared</string>
|
|
||||||
<string name="log_is_empty">Log is empty</string>
|
|
||||||
<string name="logs_save_failed">Could not write log to SD card:</string>
|
|
||||||
|
|
||||||
<!--About Activity-->
|
|
||||||
<string name="about">About</string>
|
|
||||||
<string name="app_developers">Main developers</string>
|
|
||||||
<string name="app_developers_"><![CDATA[App created by <a href="https://github.com/topjohnwu">topjohnwu</a> in collaboration with <a href="https://github.com/d8ahazard">Digitalhigh</a> and <a href="https://github.com/dvdandroid">Dvdandroid</a>.]]></string>
|
|
||||||
<string name="app_changelog">App\'s changelog</string>
|
|
||||||
<string name="translators" />
|
|
||||||
<string name="app_version">App\'s version</string>
|
|
||||||
<string name="app_source_code">Source code</string>
|
|
||||||
<string name="donation">Donation</string>
|
|
||||||
<string name="app_translators">App\'s translators</string>
|
|
||||||
<string name="support_thread">Support thread</string>
|
|
||||||
|
|
||||||
<!--Toasts, Dialogs-->
|
|
||||||
<string name="permissionNotGranted">This feature will not work without permission to write external storage.</string>
|
|
||||||
<string name="no_thanks">No thanks</string>
|
|
||||||
<string name="repo_install_title">Install %1$s</string>
|
|
||||||
<string name="repo_install_msg">Do you want to install %1$s ?</string>
|
|
||||||
<string name="download_install">Download & install</string>
|
|
||||||
<string name="goto_install">Go to \"Install\" section</string>
|
|
||||||
<string name="download_file_error">Error downloading file</string>
|
|
||||||
<string name="install_error">Installation error!</string>
|
|
||||||
<string name="manual_install_1">Zip file placed in %1$s</string>
|
|
||||||
<string name="manual_install_2">Flash it in recovery manually</string>
|
|
||||||
<string name="invalid_zip">The zip is not a Magisk Module!!</string>
|
|
||||||
<string name="reboot_title">Installation succeeded!</string>
|
|
||||||
<string name="reboot_msg">Do you want to reboot now?</string>
|
|
||||||
<string name="reboot">Reboot</string>
|
|
||||||
<string name="copying_msg">Copying zip to temp directory</string>
|
|
||||||
<string name="zip_install_progress_title">Installing</string>
|
|
||||||
<string name="zip_install_unzip_zip_msg">Unzipping zip file …</string>
|
|
||||||
<string name="zip_install_process_zip_msg">Processing zip file …</string>
|
|
||||||
<string name="zip_install_progress_msg">"Installing %1$s …"</string>
|
|
||||||
<string name="no_magisk_title">No Magisk Installed!</string>
|
|
||||||
<string name="no_magisk_msg">Do you want to download and install Magisk?</string>
|
|
||||||
<string name="downloading_toast">Downloading %1$s</string>
|
|
||||||
<string name="magisk_update_title">New Magisk Update Available!</string>
|
|
||||||
<string name="magisk_update_message">Magisk v%1$.1f update is live, do you want to install?</string>
|
|
||||||
<string name="settings_reboot_toast">Reboot to apply settings</string>
|
|
||||||
<string name="check_release_notes">Check release notes</string>
|
|
||||||
<string name="repo_cache_cleared">Repo cache cleared</string>
|
|
||||||
|
|
||||||
<!--URL Templates-->
|
|
||||||
<string name="url_main" translatable="false">https://api.github.com/orgs/Magisk-Modules-Repo/repos</string>
|
|
||||||
<string name="file_url" translatable="false">https://raw.githubusercontent.com/Magisk-Modules-Repo/%1$s/master/%2$s</string>
|
|
||||||
<string name="zip_url" translatable="false">https://github.com/Magisk-Modules-Repo/%1$s/archive/master.zip</string>
|
|
||||||
|
|
||||||
<!--Settings Activity -->
|
|
||||||
<string name="settings_general_category">General</string>
|
|
||||||
<string name="settings_theme_title">Theme</string>
|
|
||||||
<string name="settings_theme_summary">Select a theme</string>
|
|
||||||
<string name="theme_default">Default</string>
|
|
||||||
<string name="theme_dark">Dark</string>
|
|
||||||
<string name="settings_clear_cache_title">Clear Repo Cache</string>
|
|
||||||
<string name="settings_clear_cache_summary">Clear the cached information for online repos, forces the app to refresh online</string>
|
|
||||||
|
|
||||||
<string name="settings_magiskhide_title">Enable Magisk Hide</string>
|
|
||||||
<string name="settings_magiskhide_summary">Hide Magisk from various detections</string>
|
|
||||||
<string name="settings_busybox_title">Enable BusyBox</string>
|
|
||||||
<string name="settings_busybox_summary">Bind mount Magisk\'s built-in busybox to xbin</string>
|
|
||||||
<string name="settings_hosts_title">Enable systemless hosts</string>
|
|
||||||
<string name="settings_hosts_summary">Systemless support for Adblock apps</string>
|
|
||||||
|
|
||||||
<string name="settings_development_category">Development</string>
|
|
||||||
<string name="settings_developer_logging_title">Enable advanced debug logging</string>
|
|
||||||
<string name="settings_developer_logging_summary">Check this to enable verbose logging</string>
|
|
||||||
<string name="settings_shell_logging_title">Enable shell command debug logging</string>
|
|
||||||
<string name="settings_shell_logging_summary">Check this to enable logging all shell commands and its output</string>
|
|
||||||
|
|
||||||
<!-- Themes -->
|
|
||||||
<string name="theme_default_value" translatable="false">default</string>
|
|
||||||
<string name="theme_dark_value" translatable="false">dark</string>
|
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
<resources>
|
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
|
||||||
<item name="colorPrimary">@color/primary</item>
|
|
||||||
<item name="colorPrimaryDark">@color/primary_dark</item>
|
|
||||||
<item name="colorAccent">@color/accent</item>
|
|
||||||
<item name="windowActionModeOverlay">true</item>
|
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
|
||||||
<item name="cardStyle">@style/CardViewStyle.Light</item>
|
|
||||||
<item name="windowActionBar">false</item>
|
|
||||||
<item name="windowNoTitle">true</item>
|
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="AppTheme.dh" parent="ThemeOverlay.AppCompat.Dark">
|
|
||||||
<item name="colorPrimary">@color/dh_primary</item>
|
|
||||||
<item name="colorPrimaryDark">@color/dh_primary_dark</item>
|
|
||||||
<item name="colorAccent">@color/dh_accent</item>
|
|
||||||
<item name="windowActionModeOverlay">true</item>
|
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
|
||||||
<item name="cardBackgroundColor">@color/dh_accent</item>
|
|
||||||
<item name="windowActionBar">false</item>
|
|
||||||
<item name="windowNoTitle">true</item>
|
|
||||||
<item name="cardStyle">@style/CardViewStyle.Dark</item>
|
|
||||||
<item name="android:textColorPrimary">@color/dh_primary_text</item>
|
|
||||||
<item name="android:textColorSecondary">@color/dh_primary_text</item>
|
|
||||||
<item name="android:alertDialogTheme">@style/AlertDialog.dh</item>
|
|
||||||
<item name="android:dialogTheme">@style/AlertDialog.dh</item>
|
|
||||||
<item name="android:actionOverflowButtonStyle">@style/OverFlow</item>
|
|
||||||
<item name="colorControlNormal">@color/dh_primary_text</item>
|
|
||||||
<item name="colorControlActivated">@color/dh_accent</item>
|
|
||||||
<item name="colorControlHighlight">@color/dh_icons</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="OverFlow" parent="@android:style/Widget.ActionBar">
|
|
||||||
<item name="android:backgroundTint">@color/dh_icons</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="AppTheme.Toolbar.dh" parent="ThemeOverlay.AppCompat.Dark">
|
|
||||||
<item name="colorPrimary">@color/dh_primary</item>
|
|
||||||
<item name="android:elevation">4dp</item>
|
|
||||||
<item name="android:textColorPrimary">@color/dh_primary_text</item>
|
|
||||||
<item name="android:textColorSecondary">@color/dh_primary_text</item> <!-- force -->
|
|
||||||
<item name="actionMenuTextColor">@color/dh_icons</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="CardViewStyle.Dark" parent="CardView">
|
|
||||||
<item name="cardBackgroundColor">@android:color/background_dark</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="AlertDialog.dh" parent="ThemeOverlay.AppCompat.Dark">
|
|
||||||
<item name="android:textColor">@color/dh_primary_text</item>
|
|
||||||
<item name="android:textColorSecondary">@color/dh_primary_text</item>
|
|
||||||
<item name="colorAccent">@color/dh_accent</item>
|
|
||||||
<item name="android:textColorPrimary">@color/dh_primary_text</item>
|
|
||||||
<item name="android:background">@color/dh_divider</item>
|
|
||||||
<item name="android:textColorAlertDialogListItem">@color/dh_primary_text</item>
|
|
||||||
<item name="android:windowContentOverlay">@null</item>
|
|
||||||
<item name="android:windowIsFloating">true</item>
|
|
||||||
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
|
|
||||||
<item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
|
|
||||||
<item name="android:windowTitleStyle">@style/MyTitleTextStyle</item>
|
|
||||||
<item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
|
|
||||||
<item name="android:alertDialogStyle">@style/ListPrefAlertDialogStyle</item>
|
|
||||||
<item name="android:windowBackground">@android:color/transparent</item>
|
|
||||||
<item name="android:textAppearanceMedium">@style/MyAlertTextAppearance</item>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="MyAlertTextAppearance">
|
|
||||||
<!-- Set text size and color of title and message here -->
|
|
||||||
<item name="android:textColor">@color/dh_primary_text</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="ListPrefAlertDialogStyle">
|
|
||||||
<item name="android:fullDark" >@android:color/transparent</item>
|
|
||||||
<item name="android:topDark" >@android:color/transparent</item>
|
|
||||||
<item name="android:centerDark" >@android:color/transparent</item>
|
|
||||||
<item name="android:bottomDark" >@android:color/transparent</item>
|
|
||||||
<item name="android:fullBright" >@android:color/transparent</item>
|
|
||||||
<item name="android:topBright" >@android:color/transparent</item>
|
|
||||||
<item name="android:centerBright">@android:color/transparent</item>
|
|
||||||
<item name="android:bottomBright">@android:color/transparent</item>
|
|
||||||
<item name="android:bottomMedium">@android:color/transparent</item>
|
|
||||||
<item name="android:centerMedium">@android:color/transparent</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="ListViewStyle.dh" parent="android:Widget.Material.PopupMenu">
|
|
||||||
<item name="android:divider">@color/dh_divider</item>
|
|
||||||
<item name="android:dividerHeight">1dp</item>
|
|
||||||
<item name="android:listSelector">@android:color/background_dark</item>
|
|
||||||
<item name="android:drawSelectorOnTop">true</item>
|
|
||||||
<item name="android:cacheColorHint">@color/dh_divider</item>
|
|
||||||
<item name="android:foregroundTint">@color/dh_primary_text</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="TransparentListView" parent="@android:style/Widget.ListView">
|
|
||||||
<item name="android:cacheColorHint">@android:color/transparent</item>
|
|
||||||
<item name="android:background">#FFFF00</item>
|
|
||||||
<item name="android:listSelector">@android:color/transparent</item>
|
|
||||||
<item name="android:divider">#F78181</item>
|
|
||||||
<item name="android:dividerHeight">3dp</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="MyTitleTextStyle">
|
|
||||||
<item name="android:textColor">@color/dh_primary_text</item>
|
|
||||||
<item name="android:textAppearance">@style/TextAppearance.AppCompat.Title</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="CardViewStyle.Light" parent="CardView">
|
|
||||||
<item name="cardBackgroundColor">@android:color/background_light</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="AppTheme.Transparent" parent="Theme.AppCompat.Light.NoActionBar">
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
|
|
||||||
<item name="android:windowBackground">@drawable/ic_splash_activity</item>
|
|
||||||
<item name="android:windowTranslucentStatus">true</item>
|
|
||||||
<item name="android:windowTranslucentNavigation">true</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
<PreferenceScreen
|
|
||||||
android:layout_marginTop="?attr/actionBarSize"
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<PreferenceCategory
|
|
||||||
android:title="@string/settings_general_category">
|
|
||||||
|
|
||||||
<ListPreference
|
|
||||||
android:key="theme"
|
|
||||||
android:title="@string/settings_theme_title"
|
|
||||||
android:summary="@string/settings_theme_summary"
|
|
||||||
android:defaultValue="@string/theme_default_value"
|
|
||||||
android:entries="@array/themes"
|
|
||||||
android:entryValues="@array/themes_values"/>
|
|
||||||
|
|
||||||
<Preference
|
|
||||||
android:key="clear"
|
|
||||||
android:title="@string/settings_clear_cache_title"
|
|
||||||
android:summary="@string/settings_clear_cache_summary" />
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory
|
|
||||||
android:title="Magisk">
|
|
||||||
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="magiskhide"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:title="@string/settings_magiskhide_title"
|
|
||||||
android:summary="@string/settings_magiskhide_summary" />
|
|
||||||
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="busybox"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:title="@string/settings_busybox_title"
|
|
||||||
android:summary="@string/settings_busybox_summary" />
|
|
||||||
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="hosts"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:title="@string/settings_hosts_title"
|
|
||||||
android:summary="@string/settings_hosts_summary" />
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory
|
|
||||||
android:title="@string/settings_development_category">
|
|
||||||
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="developer_logging"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:title="@string/settings_developer_logging_title"
|
|
||||||
android:summary="@string/settings_developer_logging_summary" />
|
|
||||||
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="shell_logging"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:title="@string/settings_shell_logging_title"
|
|
||||||
android:summary="@string/settings_shell_logging_summary" />
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
|
||||||
74
build.gradle
@@ -1,25 +1,67 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
buildscript {
|
android {
|
||||||
repositories {
|
compileSdkVersion 27
|
||||||
jcenter()
|
buildToolsVersion "27.0.3"
|
||||||
mavenCentral()
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.topjohnwu.magisk"
|
||||||
|
minSdkVersion 21
|
||||||
|
targetSdkVersion 27
|
||||||
|
versionCode 90
|
||||||
|
versionName "5.5.4"
|
||||||
|
ndk {
|
||||||
|
moduleName 'zipadjust'
|
||||||
|
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||||
|
}
|
||||||
|
javaCompileOptions {
|
||||||
|
annotationProcessorOptions {
|
||||||
|
argument('butterknife.debuggable', 'false')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:2.2.3'
|
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
buildTypes {
|
||||||
// in the individual module build.gradle files
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
shrinkResources true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
dexOptions {
|
||||||
|
preDexLibraries true
|
||||||
|
javaMaxHeapSize "2g"
|
||||||
|
}
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
path 'src/main/jni/CMakeLists.txt'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lintOptions {
|
||||||
|
disable 'MissingTranslation'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
repositories {
|
||||||
repositories {
|
jcenter()
|
||||||
jcenter()
|
google()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
dependencies {
|
||||||
delete rootProject.buildDir
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
|
implementation project(':crypto')
|
||||||
|
implementation 'com.android.support:recyclerview-v7:27.0.2'
|
||||||
|
implementation 'com.android.support:cardview-v7:27.0.2'
|
||||||
|
implementation 'com.android.support:design:27.0.2'
|
||||||
|
implementation 'com.android.support:support-v4:27.0.2'
|
||||||
|
implementation 'com.jakewharton:butterknife:8.8.1'
|
||||||
|
implementation 'com.atlassian.commonmark:commonmark:0.10.0'
|
||||||
|
implementation 'org.kamranzafar:jtar:2.3'
|
||||||
|
implementation 'com.google.code.gson:gson:2.8.2'
|
||||||
|
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
# Project-wide Gradle settings.
|
|
||||||
|
|
||||||
# IDE (e.g. Android Studio) users:
|
|
||||||
# Gradle settings configured through the IDE *will override*
|
|
||||||
# any settings specified in this file.
|
|
||||||
|
|
||||||
# For more details on how to configure your build environment visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
|
||||||
|
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
|
||||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
|
||||||
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=4096m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
|
||||||
|
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
|
||||||
org.gradle.parallel=true
|
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
6
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +0,0 @@
|
|||||||
#Wed Aug 17 11:39:12 CEST 2016
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
|
||||||
distributionPath=wrapper/dists
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
|
|
||||||
160
gradlew
vendored
@@ -1,160 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
##
|
|
||||||
## Gradle start up script for UN*X
|
|
||||||
##
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS=""
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=`basename "$0"`
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
|
||||||
MAX_FD="maximum"
|
|
||||||
|
|
||||||
warn ( ) {
|
|
||||||
echo "$*"
|
|
||||||
}
|
|
||||||
|
|
||||||
die ( ) {
|
|
||||||
echo
|
|
||||||
echo "$*"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
|
||||||
cygwin=false
|
|
||||||
msys=false
|
|
||||||
darwin=false
|
|
||||||
case "`uname`" in
|
|
||||||
CYGWIN* )
|
|
||||||
cygwin=true
|
|
||||||
;;
|
|
||||||
Darwin* )
|
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
|
||||||
# Resolve links: $0 may be a link
|
|
||||||
PRG="$0"
|
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
|
||||||
ls=`ls -ld "$PRG"`
|
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
|
||||||
PRG="$link"
|
|
||||||
else
|
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >/dev/null
|
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
|
||||||
else
|
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
|
||||||
fi
|
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
JAVACMD="java"
|
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
|
||||||
if [ $? -eq 0 ] ; then
|
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
|
||||||
fi
|
|
||||||
ulimit -n $MAX_FD
|
|
||||||
if [ $? -ne 0 ] ; then
|
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Darwin, add options to specify how the application appears in the dock
|
|
||||||
if $darwin; then
|
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
|
||||||
if $cygwin ; then
|
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
|
||||||
SEP=""
|
|
||||||
for dir in $ROOTDIRSRAW ; do
|
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
|
||||||
SEP="|"
|
|
||||||
done
|
|
||||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
|
||||||
# Add a user-defined pattern to the cygpath arguments
|
|
||||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
|
||||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
|
||||||
fi
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
i=0
|
|
||||||
for arg in "$@" ; do
|
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
|
||||||
else
|
|
||||||
eval `echo args$i`="\"$arg\""
|
|
||||||
fi
|
|
||||||
i=$((i+1))
|
|
||||||
done
|
|
||||||
case $i in
|
|
||||||
(0) set -- ;;
|
|
||||||
(1) set -- "$args0" ;;
|
|
||||||
(2) set -- "$args0" "$args1" ;;
|
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
|
||||||
function splitJvmOpts() {
|
|
||||||
JVM_OPTS=("$@")
|
|
||||||
}
|
|
||||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
|
||||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
|
||||||
|
|
||||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
|
||||||
90
gradlew.bat
vendored
@@ -1,90 +0,0 @@
|
|||||||
@if "%DEBUG%" == "" @echo off
|
|
||||||
@rem ##########################################################################
|
|
||||||
@rem
|
|
||||||
@rem Gradle startup script for Windows
|
|
||||||
@rem
|
|
||||||
@rem ##########################################################################
|
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
set DEFAULT_JVM_OPTS=
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
|
||||||
set APP_BASE_NAME=%~n0
|
|
||||||
set APP_HOME=%DIRNAME%
|
|
||||||
|
|
||||||
@rem Find java.exe
|
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:init
|
|
||||||
@rem Get command-line arguments, handling Windowz variants
|
|
||||||
|
|
||||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
|
||||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
|
||||||
|
|
||||||
:win9xME_args
|
|
||||||
@rem Slurp the command line arguments.
|
|
||||||
set CMD_LINE_ARGS=
|
|
||||||
set _SKIP=2
|
|
||||||
|
|
||||||
:win9xME_args_slurp
|
|
||||||
if "x%~1" == "x" goto execute
|
|
||||||
|
|
||||||
set CMD_LINE_ARGS=%*
|
|
||||||
goto execute
|
|
||||||
|
|
||||||
:4NT_args
|
|
||||||
@rem Get arguments from the 4NT Shell from JP Software
|
|
||||||
set CMD_LINE_ARGS=%$
|
|
||||||
|
|
||||||
:execute
|
|
||||||
@rem Setup the command line
|
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
|
||||||
|
|
||||||
:end
|
|
||||||
@rem End local scope for the variables with windows NT shell
|
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
|
||||||
exit /b 1
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
27
proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in /Users/topjohnwu/Library/Android/sdk/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Keep all names, we are open source anyway :)
|
||||||
|
-keepnames class ** { *; }
|
||||||
|
|
||||||
|
# BouncyCastle
|
||||||
|
-keep class org.bouncycastle.jcajce.provider.** { *; }
|
||||||
|
-dontwarn javax.naming.**
|
||||||
|
|
||||||
|
# Gson
|
||||||
|
-keepattributes Signature
|
||||||
@@ -1 +0,0 @@
|
|||||||
include ':app'
|
|
||||||
98
src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="com.topjohnwu.magisk">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".MagiskManager"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme"
|
||||||
|
android:directBootAware="true"
|
||||||
|
tools:ignore="UnusedAttribute">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:configChanges="orientation|screenSize"
|
||||||
|
android:exported="true" />
|
||||||
|
<activity
|
||||||
|
android:name=".SplashActivity"
|
||||||
|
android:configChanges="orientation|screenSize"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/SplashTheme">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".AboutActivity"
|
||||||
|
android:theme="@style/AppTheme.Transparent" />
|
||||||
|
<activity
|
||||||
|
android:name=".SettingsActivity"
|
||||||
|
android:theme="@style/AppTheme.Transparent" />
|
||||||
|
<activity
|
||||||
|
android:name=".FlashActivity"
|
||||||
|
android:screenOrientation="nosensor"
|
||||||
|
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||||
|
android:theme="@style/AppTheme.Transparent" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".superuser.RequestActivity"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:taskAffinity="internal.superuser"
|
||||||
|
android:theme="@style/SuRequest" />
|
||||||
|
|
||||||
|
<receiver android:name=".superuser.SuReceiver" />
|
||||||
|
<receiver android:name=".receivers.BootReceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
<receiver android:name=".receivers.PackageReceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||||
|
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
|
||||||
|
|
||||||
|
<data android:scheme="package" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
<receiver android:name=".receivers.ManagerUpdate" />
|
||||||
|
<receiver android:name=".receivers.RebootReceiver" />
|
||||||
|
|
||||||
|
<service android:name=".services.OnBootIntentService" />
|
||||||
|
<service
|
||||||
|
android:name=".services.UpdateCheckService"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="android.support.v4.content.FileProvider"
|
||||||
|
android:authorities="com.topjohnwu.magisk.provider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
|
<!-- Hardcode GMS version -->
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.version"
|
||||||
|
android:value="7095000" />
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
3
src/main/assets/changelog.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
### v5.5.4
|
||||||
|
- Fix on-boot dtbo detection
|
||||||
|
- Add fingerprint authentication for Superuser requests
|
||||||
276
src/main/assets/dark.css
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
body {
|
||||||
|
font-family: Helvetica, arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
background-color: #424242;
|
||||||
|
color: white;
|
||||||
|
padding: 15px; }
|
||||||
|
|
||||||
|
body > *:first-child {
|
||||||
|
margin-top: 0 !important; }
|
||||||
|
body > *:last-child {
|
||||||
|
margin-bottom: 0 !important; }
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #4183C4; }
|
||||||
|
a.absent {
|
||||||
|
color: #cc0000; }
|
||||||
|
a.anchor {
|
||||||
|
display: block;
|
||||||
|
padding-left: 30px;
|
||||||
|
margin-left: -30px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0; }
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin: 20px 0 10px;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
cursor: text;
|
||||||
|
position: relative; }
|
||||||
|
|
||||||
|
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
|
||||||
|
background: url("../../images/modules/styleguide/para.png") no-repeat 10px center;
|
||||||
|
text-decoration: none; }
|
||||||
|
|
||||||
|
h1 tt, h1 code {
|
||||||
|
font-size: inherit; }
|
||||||
|
|
||||||
|
h2 tt, h2 code {
|
||||||
|
font-size: inherit; }
|
||||||
|
|
||||||
|
h3 tt, h3 code {
|
||||||
|
font-size: inherit; }
|
||||||
|
|
||||||
|
h4 tt, h4 code {
|
||||||
|
font-size: inherit; }
|
||||||
|
|
||||||
|
h5 tt, h5 code {
|
||||||
|
font-size: inherit; }
|
||||||
|
|
||||||
|
h6 tt, h6 code {
|
||||||
|
font-size: inherit; }
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 28px; }
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
border-bottom: 1px solid #cccccc; }
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 18px; }
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 16px; }
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 14px; }
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
color: #888888;
|
||||||
|
font-size: 14px; }
|
||||||
|
|
||||||
|
p, blockquote, ul, ol, dl, li, table, pre {
|
||||||
|
margin: 15px 0; }
|
||||||
|
|
||||||
|
hr {
|
||||||
|
background: transparent url("../../images/modules/pulls/dirty-shade.png") repeat-x 0 0;
|
||||||
|
border: 0 none;
|
||||||
|
color: #cccccc;
|
||||||
|
height: 4px;
|
||||||
|
padding: 0; }
|
||||||
|
|
||||||
|
body > h2:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0; }
|
||||||
|
body > h1:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0; }
|
||||||
|
body > h1:first-child + h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0; }
|
||||||
|
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0; }
|
||||||
|
|
||||||
|
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0; }
|
||||||
|
|
||||||
|
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
|
||||||
|
margin-top: 0; }
|
||||||
|
|
||||||
|
li p.first {
|
||||||
|
display: inline-block; }
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
padding-left: 30px; }
|
||||||
|
|
||||||
|
ul :first-child, ol :first-child {
|
||||||
|
margin-top: 0; }
|
||||||
|
|
||||||
|
ul :last-child, ol :last-child {
|
||||||
|
margin-bottom: 0; }
|
||||||
|
|
||||||
|
dl {
|
||||||
|
padding: 0; }
|
||||||
|
dl dt {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: italic;
|
||||||
|
padding: 0;
|
||||||
|
margin: 15px 0 5px; }
|
||||||
|
dl dt:first-child {
|
||||||
|
padding: 0; }
|
||||||
|
dl dt > :first-child {
|
||||||
|
margin-top: 0; }
|
||||||
|
dl dt > :last-child {
|
||||||
|
margin-bottom: 0; }
|
||||||
|
dl dd {
|
||||||
|
margin: 0 0 15px;
|
||||||
|
padding: 0 15px; }
|
||||||
|
dl dd > :first-child {
|
||||||
|
margin-top: 0; }
|
||||||
|
dl dd > :last-child {
|
||||||
|
margin-bottom: 0; }
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left: 4px solid #404040;
|
||||||
|
padding: 0 15px;
|
||||||
|
color: #888888; }
|
||||||
|
blockquote > :first-child {
|
||||||
|
margin-top: 0; }
|
||||||
|
blockquote > :last-child {
|
||||||
|
margin-bottom: 0; }
|
||||||
|
|
||||||
|
table {
|
||||||
|
padding: 0; }
|
||||||
|
table tr {
|
||||||
|
border-top: 1px solid #707070;
|
||||||
|
background-color: #303030;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0; }
|
||||||
|
table tr:nth-child(2n) {
|
||||||
|
background-color: #505050; }
|
||||||
|
table tr th {
|
||||||
|
font-weight: bold;
|
||||||
|
border: 1px solid #707070;
|
||||||
|
text-align: left;
|
||||||
|
margin: 0;
|
||||||
|
padding: 6px 13px; }
|
||||||
|
table tr td {
|
||||||
|
border: 1px solid #707070;
|
||||||
|
text-align: left;
|
||||||
|
margin: 0;
|
||||||
|
padding: 6px 13px; }
|
||||||
|
table tr th :first-child, table tr td :first-child {
|
||||||
|
margin-top: 0; }
|
||||||
|
table tr th :last-child, table tr td :last-child {
|
||||||
|
margin-bottom: 0; }
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%; }
|
||||||
|
|
||||||
|
span.frame {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden; }
|
||||||
|
span.frame > span {
|
||||||
|
border: 1px solid #dddddd;
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 13px 0 0;
|
||||||
|
padding: 7px;
|
||||||
|
width: auto; }
|
||||||
|
span.frame span img {
|
||||||
|
display: block;
|
||||||
|
float: left; }
|
||||||
|
span.frame span span {
|
||||||
|
clear: both;
|
||||||
|
color: #cccccc;
|
||||||
|
display: block;
|
||||||
|
padding: 5px 0 0; }
|
||||||
|
span.align-center {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
clear: both; }
|
||||||
|
span.align-center > span {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 13px auto 0;
|
||||||
|
text-align: center; }
|
||||||
|
span.align-center span img {
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center; }
|
||||||
|
span.align-right {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
clear: both; }
|
||||||
|
span.align-right > span {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 13px 0 0;
|
||||||
|
text-align: right; }
|
||||||
|
span.align-right span img {
|
||||||
|
margin: 0;
|
||||||
|
text-align: right; }
|
||||||
|
span.float-left {
|
||||||
|
display: block;
|
||||||
|
margin-right: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
float: left; }
|
||||||
|
span.float-left span {
|
||||||
|
margin: 13px 0 0; }
|
||||||
|
span.float-right {
|
||||||
|
display: block;
|
||||||
|
margin-left: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
float: right; }
|
||||||
|
span.float-right > span {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 13px auto 0;
|
||||||
|
text-align: right; }
|
||||||
|
|
||||||
|
code, tt {
|
||||||
|
margin: 0 2px;
|
||||||
|
padding: 0 5px;
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 1px solid #707070;
|
||||||
|
background-color: #606060;
|
||||||
|
border-radius: 3px; }
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
white-space: pre;
|
||||||
|
border: none;
|
||||||
|
background: transparent; }
|
||||||
|
|
||||||
|
.highlight pre {
|
||||||
|
background-color: #3f3f3f;
|
||||||
|
border: 1px solid #707070;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 19px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 3px; }
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background-color: #606060;
|
||||||
|
border: 1px solid #707070;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 19px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 3px; }
|
||||||
|
pre code, pre tt {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none; }
|
||||||
277
src/main/assets/light.css
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
body {
|
||||||
|
font-family: Helvetica, arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
background-color: white;
|
||||||
|
padding: 15px; }
|
||||||
|
|
||||||
|
body > *:first-child {
|
||||||
|
margin-top: 0 !important; }
|
||||||
|
body > *:last-child {
|
||||||
|
margin-bottom: 0 !important; }
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #4183C4; }
|
||||||
|
a.absent {
|
||||||
|
color: #cc0000; }
|
||||||
|
a.anchor {
|
||||||
|
display: block;
|
||||||
|
padding-left: 30px;
|
||||||
|
margin-left: -30px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0; }
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin: 20px 0 10px;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
cursor: text;
|
||||||
|
position: relative; }
|
||||||
|
|
||||||
|
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
|
||||||
|
background: url("../../images/modules/styleguide/para.png") no-repeat 10px center;
|
||||||
|
text-decoration: none; }
|
||||||
|
|
||||||
|
h1 tt, h1 code {
|
||||||
|
font-size: inherit; }
|
||||||
|
|
||||||
|
h2 tt, h2 code {
|
||||||
|
font-size: inherit; }
|
||||||
|
|
||||||
|
h3 tt, h3 code {
|
||||||
|
font-size: inherit; }
|
||||||
|
|
||||||
|
h4 tt, h4 code {
|
||||||
|
font-size: inherit; }
|
||||||
|
|
||||||
|
h5 tt, h5 code {
|
||||||
|
font-size: inherit; }
|
||||||
|
|
||||||
|
h6 tt, h6 code {
|
||||||
|
font-size: inherit; }
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
color: black; }
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
border-bottom: 1px solid #cccccc;
|
||||||
|
color: black; }
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 18px; }
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 16px; }
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 14px; }
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
color: #777777;
|
||||||
|
font-size: 14px; }
|
||||||
|
|
||||||
|
p, blockquote, ul, ol, dl, li, table, pre {
|
||||||
|
margin: 15px 0; }
|
||||||
|
|
||||||
|
hr {
|
||||||
|
background: transparent url("../../images/modules/pulls/dirty-shade.png") repeat-x 0 0;
|
||||||
|
border: 0 none;
|
||||||
|
color: #cccccc;
|
||||||
|
height: 4px;
|
||||||
|
padding: 0; }
|
||||||
|
|
||||||
|
body > h2:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0; }
|
||||||
|
body > h1:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0; }
|
||||||
|
body > h1:first-child + h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0; }
|
||||||
|
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0; }
|
||||||
|
|
||||||
|
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0; }
|
||||||
|
|
||||||
|
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
|
||||||
|
margin-top: 0; }
|
||||||
|
|
||||||
|
li p.first {
|
||||||
|
display: inline-block; }
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
padding-left: 30px; }
|
||||||
|
|
||||||
|
ul :first-child, ol :first-child {
|
||||||
|
margin-top: 0; }
|
||||||
|
|
||||||
|
ul :last-child, ol :last-child {
|
||||||
|
margin-bottom: 0; }
|
||||||
|
|
||||||
|
dl {
|
||||||
|
padding: 0; }
|
||||||
|
dl dt {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: italic;
|
||||||
|
padding: 0;
|
||||||
|
margin: 15px 0 5px; }
|
||||||
|
dl dt:first-child {
|
||||||
|
padding: 0; }
|
||||||
|
dl dt > :first-child {
|
||||||
|
margin-top: 0; }
|
||||||
|
dl dt > :last-child {
|
||||||
|
margin-bottom: 0; }
|
||||||
|
dl dd {
|
||||||
|
margin: 0 0 15px;
|
||||||
|
padding: 0 15px; }
|
||||||
|
dl dd > :first-child {
|
||||||
|
margin-top: 0; }
|
||||||
|
dl dd > :last-child {
|
||||||
|
margin-bottom: 0; }
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left: 4px solid #dddddd;
|
||||||
|
padding: 0 15px;
|
||||||
|
color: #777777; }
|
||||||
|
blockquote > :first-child {
|
||||||
|
margin-top: 0; }
|
||||||
|
blockquote > :last-child {
|
||||||
|
margin-bottom: 0; }
|
||||||
|
|
||||||
|
table {
|
||||||
|
padding: 0; }
|
||||||
|
table tr {
|
||||||
|
border-top: 1px solid #cccccc;
|
||||||
|
background-color: white;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0; }
|
||||||
|
table tr:nth-child(2n) {
|
||||||
|
background-color: #f8f8f8; }
|
||||||
|
table tr th {
|
||||||
|
font-weight: bold;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
text-align: left;
|
||||||
|
margin: 0;
|
||||||
|
padding: 6px 13px; }
|
||||||
|
table tr td {
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
text-align: left;
|
||||||
|
margin: 0;
|
||||||
|
padding: 6px 13px; }
|
||||||
|
table tr th :first-child, table tr td :first-child {
|
||||||
|
margin-top: 0; }
|
||||||
|
table tr th :last-child, table tr td :last-child {
|
||||||
|
margin-bottom: 0; }
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%; }
|
||||||
|
|
||||||
|
span.frame {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden; }
|
||||||
|
span.frame > span {
|
||||||
|
border: 1px solid #dddddd;
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 13px 0 0;
|
||||||
|
padding: 7px;
|
||||||
|
width: auto; }
|
||||||
|
span.frame span img {
|
||||||
|
display: block;
|
||||||
|
float: left; }
|
||||||
|
span.frame span span {
|
||||||
|
clear: both;
|
||||||
|
color: #333333;
|
||||||
|
display: block;
|
||||||
|
padding: 5px 0 0; }
|
||||||
|
span.align-center {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
clear: both; }
|
||||||
|
span.align-center > span {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 13px auto 0;
|
||||||
|
text-align: center; }
|
||||||
|
span.align-center span img {
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center; }
|
||||||
|
span.align-right {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
clear: both; }
|
||||||
|
span.align-right > span {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 13px 0 0;
|
||||||
|
text-align: right; }
|
||||||
|
span.align-right span img {
|
||||||
|
margin: 0;
|
||||||
|
text-align: right; }
|
||||||
|
span.float-left {
|
||||||
|
display: block;
|
||||||
|
margin-right: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
float: left; }
|
||||||
|
span.float-left span {
|
||||||
|
margin: 13px 0 0; }
|
||||||
|
span.float-right {
|
||||||
|
display: block;
|
||||||
|
margin-left: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
float: right; }
|
||||||
|
span.float-right > span {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 13px auto 0;
|
||||||
|
text-align: right; }
|
||||||
|
|
||||||
|
code, tt {
|
||||||
|
margin: 0 2px;
|
||||||
|
padding: 0 5px;
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 1px solid #eaeaea;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-radius: 3px; }
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
white-space: pre;
|
||||||
|
border: none;
|
||||||
|
background: transparent; }
|
||||||
|
|
||||||
|
.highlight pre {
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 19px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 3px; }
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 19px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 3px; }
|
||||||
|
pre code, pre tt {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none; }
|
||||||
BIN
src/main/ic_launcher-web.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
86
src/main/java/com/topjohnwu/magisk/AboutActivity.java
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v7.app.ActionBar;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||||
|
import com.topjohnwu.magisk.components.AboutCardRow;
|
||||||
|
import com.topjohnwu.magisk.components.Activity;
|
||||||
|
import com.topjohnwu.magisk.utils.Const;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
|
public class AboutActivity extends Activity {
|
||||||
|
|
||||||
|
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||||
|
@BindView(R.id.app_version_info) AboutCardRow appVersionInfo;
|
||||||
|
@BindView(R.id.app_changelog) AboutCardRow appChangelog;
|
||||||
|
@BindView(R.id.app_translators) AboutCardRow appTranslators;
|
||||||
|
@BindView(R.id.app_source_code) AboutCardRow appSourceCode;
|
||||||
|
@BindView(R.id.support_thread) AboutCardRow supportThread;
|
||||||
|
@BindView(R.id.donation) AboutCardRow donation;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDarkTheme() {
|
||||||
|
return R.style.AppTheme_Transparent_Dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_about);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
toolbar.setNavigationOnClickListener(view -> finish());
|
||||||
|
|
||||||
|
ActionBar ab = getSupportActionBar();
|
||||||
|
if (ab != null) {
|
||||||
|
ab.setTitle(R.string.about);
|
||||||
|
ab.setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
appVersionInfo.setSummary(String.format(Locale.US, "%s (%d) (%s)",
|
||||||
|
BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, getPackageName()));
|
||||||
|
|
||||||
|
appChangelog.removeSummary();
|
||||||
|
appChangelog.setOnClickListener(v -> {
|
||||||
|
try {
|
||||||
|
InputStream is = getAssets().open("changelog.md");
|
||||||
|
new MarkDownWindow(this, getString(R.string.app_changelog), is).exec();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
String translators = getString(R.string.translators);
|
||||||
|
if (TextUtils.isEmpty(translators)) {
|
||||||
|
appTranslators.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
appTranslators.setSummary(translators);
|
||||||
|
}
|
||||||
|
|
||||||
|
appSourceCode.removeSummary();
|
||||||
|
appSourceCode.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Const.Url.SOURCE_CODE_URL))));
|
||||||
|
|
||||||
|
supportThread.removeSummary();
|
||||||
|
supportThread.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Const.Url.XDA_THREAD))));
|
||||||
|
|
||||||
|
donation.removeSummary();
|
||||||
|
donation.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Const.Url.DONATION_URL))));
|
||||||
|
|
||||||
|
setFloating();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
131
src/main/java/com/topjohnwu/magisk/FlashActivity.java
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.ActionBar;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.asyncs.FlashZip;
|
||||||
|
import com.topjohnwu.magisk.asyncs.InstallMagisk;
|
||||||
|
import com.topjohnwu.magisk.components.Activity;
|
||||||
|
import com.topjohnwu.magisk.container.CallbackList;
|
||||||
|
import com.topjohnwu.magisk.utils.Const;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
|
||||||
|
public class FlashActivity extends Activity {
|
||||||
|
|
||||||
|
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||||
|
@BindView(R.id.txtLog) TextView flashLogs;
|
||||||
|
@BindView(R.id.button_panel) public LinearLayout buttonPanel;
|
||||||
|
@BindView(R.id.reboot) public Button reboot;
|
||||||
|
@BindView(R.id.scrollView) ScrollView sv;
|
||||||
|
|
||||||
|
private List<String> logs;
|
||||||
|
|
||||||
|
@OnClick(R.id.no_thanks)
|
||||||
|
void dismiss() {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.reboot)
|
||||||
|
void reboot() {
|
||||||
|
Shell.su_raw("/system/bin/reboot");
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.save_logs)
|
||||||
|
void saveLogs() {
|
||||||
|
Calendar now = Calendar.getInstance();
|
||||||
|
String filename = String.format(Locale.US,
|
||||||
|
"install_log_%04d%02d%02d_%02d%02d%02d.log",
|
||||||
|
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||||
|
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||||
|
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
|
||||||
|
|
||||||
|
File logFile = new File(Const.EXTERNAL_PATH + "/logs", filename);
|
||||||
|
logFile.getParentFile().mkdirs();
|
||||||
|
try (FileWriter writer = new FileWriter(logFile)) {
|
||||||
|
for (String s : logs) {
|
||||||
|
writer.write(s);
|
||||||
|
writer.write('\n');
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MagiskManager.toast(logFile.getPath(), Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDarkTheme() {
|
||||||
|
return R.style.AppTheme_Transparent_Dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_flash);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
ActionBar ab = getSupportActionBar();
|
||||||
|
if (ab != null) {
|
||||||
|
ab.setTitle(R.string.flashing);
|
||||||
|
}
|
||||||
|
setFloating();
|
||||||
|
setFinishOnTouchOutside(false);
|
||||||
|
if (!Shell.rootAccess())
|
||||||
|
reboot.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
logs = new ArrayList<>();
|
||||||
|
List<String> console = new CallbackList<String>() {
|
||||||
|
@Override
|
||||||
|
public synchronized void onAddElement(String e) {
|
||||||
|
logs.add(e);
|
||||||
|
flashLogs.setText(TextUtils.join("\n", this));
|
||||||
|
sv.postDelayed(() -> sv.fullScroll(ScrollView.FOCUS_DOWN), 10);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// We must receive a Uri of the target zip
|
||||||
|
Intent intent = getIntent();
|
||||||
|
Uri uri = intent.getData();
|
||||||
|
|
||||||
|
switch (intent.getStringExtra(Const.Key.FLASH_ACTION)) {
|
||||||
|
case Const.Value.FLASH_ZIP:
|
||||||
|
new FlashZip(this, uri, console, logs).exec();
|
||||||
|
break;
|
||||||
|
case Const.Value.PATCH_BOOT:
|
||||||
|
new InstallMagisk(this, console, logs, uri, (Uri) intent.getParcelableExtra(Const.Key.FLASH_SET_BOOT))
|
||||||
|
.exec();
|
||||||
|
break;
|
||||||
|
case Const.Value.FLASH_MAGISK:
|
||||||
|
new InstallMagisk(this, console, logs, uri, intent.getStringExtra(Const.Key.FLASH_SET_BOOT))
|
||||||
|
.exec();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
// Prevent user accidentally press back button
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/main/java/com/topjohnwu/magisk/LogFragment.java
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.design.widget.TabLayout;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.adapters.TabFragmentAdapter;
|
||||||
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
|
import com.topjohnwu.magisk.utils.Const;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.Unbinder;
|
||||||
|
|
||||||
|
public class LogFragment extends Fragment {
|
||||||
|
|
||||||
|
private Unbinder unbinder;
|
||||||
|
|
||||||
|
@BindView(R.id.container) ViewPager viewPager;
|
||||||
|
@BindView(R.id.tab) TabLayout tab;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
// Inflate the layout for this fragment
|
||||||
|
View v = inflater.inflate(R.layout.fragment_log, container, false);
|
||||||
|
unbinder = ButterKnife.bind(this, v);
|
||||||
|
|
||||||
|
((MainActivity) getActivity()).toolbar.setElevation(0);
|
||||||
|
|
||||||
|
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
|
||||||
|
|
||||||
|
if (!(Const.USER_ID > 0 && getApplication().multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||||
|
adapter.addTab(new SuLogFragment(), getString(R.string.superuser));
|
||||||
|
}
|
||||||
|
adapter.addTab(new MagiskLogFragment(), getString(R.string.magisk));
|
||||||
|
tab.setupWithViewPager(viewPager);
|
||||||
|
tab.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
unbinder.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
305
src/main/java/com/topjohnwu/magisk/MagiskFragment.java
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
|
import android.support.v7.widget.CardView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.asyncs.CheckSafetyNet;
|
||||||
|
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||||
|
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||||
|
import com.topjohnwu.magisk.components.ExpandableView;
|
||||||
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.ShowUI;
|
||||||
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import butterknife.BindColor;
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
import butterknife.Unbinder;
|
||||||
|
|
||||||
|
public class MagiskFragment extends Fragment
|
||||||
|
implements Topic.Subscriber, SwipeRefreshLayout.OnRefreshListener, ExpandableView {
|
||||||
|
|
||||||
|
private static final int CAUSE_SERVICE_DISCONNECTED = 0x01;
|
||||||
|
private static final int CAUSE_NETWORK_LOST = 0x02;
|
||||||
|
private static final int RESPONSE_ERR = 0x04;
|
||||||
|
private static final int CONNECTION_FAIL = 0x08;
|
||||||
|
|
||||||
|
private static final int BASIC_PASS = 0x10;
|
||||||
|
private static final int CTS_PASS = 0x20;
|
||||||
|
|
||||||
|
private Container expandableContainer = new Container();
|
||||||
|
|
||||||
|
private MagiskManager mm;
|
||||||
|
private Unbinder unbinder;
|
||||||
|
private static boolean shownDialog = false;
|
||||||
|
|
||||||
|
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||||
|
|
||||||
|
@BindView(R.id.magisk_update) RelativeLayout magiskUpdate;
|
||||||
|
@BindView(R.id.magisk_update_icon) ImageView magiskUpdateIcon;
|
||||||
|
@BindView(R.id.magisk_update_status) TextView magiskUpdateText;
|
||||||
|
@BindView(R.id.magisk_update_progress) ProgressBar magiskUpdateProgress;
|
||||||
|
@BindView(R.id.magisk_status_icon) ImageView magiskStatusIcon;
|
||||||
|
@BindView(R.id.magisk_version) TextView magiskVersionText;
|
||||||
|
|
||||||
|
@BindView(R.id.safetyNet_card) CardView safetyNetCard;
|
||||||
|
@BindView(R.id.safetyNet_refresh) ImageView safetyNetRefreshIcon;
|
||||||
|
@BindView(R.id.safetyNet_status) TextView safetyNetStatusText;
|
||||||
|
@BindView(R.id.safetyNet_check_progress) ProgressBar safetyNetProgress;
|
||||||
|
@BindView(R.id.expand_layout) LinearLayout expandLayout;
|
||||||
|
@BindView(R.id.cts_status_icon) ImageView ctsStatusIcon;
|
||||||
|
@BindView(R.id.cts_status) TextView ctsStatusText;
|
||||||
|
@BindView(R.id.basic_status_icon) ImageView basicStatusIcon;
|
||||||
|
@BindView(R.id.basic_status) TextView basicStatusText;
|
||||||
|
|
||||||
|
@BindView(R.id.install_option_card) CardView installOptionCard;
|
||||||
|
@BindView(R.id.keep_force_enc) CheckBox keepEncChkbox;
|
||||||
|
@BindView(R.id.keep_verity) CheckBox keepVerityChkbox;
|
||||||
|
@BindView(R.id.install_button) CardView installButton;
|
||||||
|
@BindView(R.id.install_text) TextView installText;
|
||||||
|
@BindView(R.id.uninstall_button) CardView uninstallButton;
|
||||||
|
|
||||||
|
@BindColor(R.color.red500) int colorBad;
|
||||||
|
@BindColor(R.color.green500) int colorOK;
|
||||||
|
@BindColor(R.color.yellow500) int colorWarn;
|
||||||
|
@BindColor(R.color.grey500) int colorNeutral;
|
||||||
|
@BindColor(R.color.blue500) int colorInfo;
|
||||||
|
|
||||||
|
@OnClick(R.id.safetyNet_title)
|
||||||
|
void safetyNet() {
|
||||||
|
Runnable task = () -> {
|
||||||
|
safetyNetProgress.setVisibility(View.VISIBLE);
|
||||||
|
safetyNetRefreshIcon.setVisibility(View.GONE);
|
||||||
|
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
|
||||||
|
new CheckSafetyNet(getActivity()).exec();
|
||||||
|
collapse();
|
||||||
|
};
|
||||||
|
if (!CheckSafetyNet.dexPath.exists()) {
|
||||||
|
// Show dialog
|
||||||
|
new AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.proprietary_title)
|
||||||
|
.setMessage(R.string.proprietary_notice)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setPositiveButton(R.string.yes, (d, i) -> task.run())
|
||||||
|
.setNegativeButton(R.string.no_thanks, null)
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.install_button)
|
||||||
|
void install() {
|
||||||
|
shownDialog = true;
|
||||||
|
|
||||||
|
// Show Manager update first
|
||||||
|
if (mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||||
|
ShowUI.managerInstallDialog(getActivity());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
((NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
|
||||||
|
ShowUI.magiskInstallDialog(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.uninstall_button)
|
||||||
|
void uninstall() {
|
||||||
|
ShowUI.uninstallDialog(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.fragment_magisk, container, false);
|
||||||
|
unbinder = ButterKnife.bind(this, v);
|
||||||
|
getActivity().setTitle(R.string.magisk);
|
||||||
|
|
||||||
|
mm = getApplication();
|
||||||
|
|
||||||
|
expandableContainer.expandLayout = expandLayout;
|
||||||
|
setupExpandable();
|
||||||
|
|
||||||
|
keepVerityChkbox.setChecked(mm.keepVerity);
|
||||||
|
keepVerityChkbox.setOnCheckedChangeListener((view, checked) -> mm.keepVerity = checked);
|
||||||
|
keepEncChkbox.setChecked(mm.keepEnc);
|
||||||
|
keepEncChkbox.setOnCheckedChangeListener((view, checked) -> mm.keepEnc = checked);
|
||||||
|
|
||||||
|
mSwipeRefreshLayout.setOnRefreshListener(this);
|
||||||
|
updateUI();
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRefresh() {
|
||||||
|
mm.loadMagiskInfo();
|
||||||
|
updateUI();
|
||||||
|
|
||||||
|
magiskUpdateText.setText(R.string.checking_for_updates);
|
||||||
|
magiskUpdateProgress.setVisibility(View.VISIBLE);
|
||||||
|
magiskUpdateIcon.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
safetyNetStatusText.setText(R.string.safetyNet_check_text);
|
||||||
|
|
||||||
|
mm.safetyNetDone.hasPublished = false;
|
||||||
|
mm.updateCheckDone.hasPublished = false;
|
||||||
|
mm.remoteMagiskVersionString = null;
|
||||||
|
mm.remoteMagiskVersionCode = -1;
|
||||||
|
collapse();
|
||||||
|
|
||||||
|
shownDialog = false;
|
||||||
|
|
||||||
|
// Trigger state check
|
||||||
|
if (Utils.checkNetworkStatus()) {
|
||||||
|
new CheckUpdates().exec();
|
||||||
|
} else {
|
||||||
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTopicPublished(Topic topic, Object result) {
|
||||||
|
if (topic == mm.updateCheckDone) {
|
||||||
|
updateCheckUI();
|
||||||
|
} else if (topic == mm.safetyNetDone) {
|
||||||
|
updateSafetyNetUI((int) result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Topic[] getSubscription() {
|
||||||
|
return new Topic[] { mm.updateCheckDone, mm.safetyNetDone };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
unbinder.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Container getContainer() {
|
||||||
|
return expandableContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateUI() {
|
||||||
|
((MainActivity) getActivity()).checkHideSection();
|
||||||
|
|
||||||
|
boolean hasNetwork = Utils.checkNetworkStatus();
|
||||||
|
boolean hasRoot = Shell.rootAccess();
|
||||||
|
boolean isUpToDate = mm.magiskVersionCode > 1300;
|
||||||
|
|
||||||
|
magiskUpdate.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||||
|
safetyNetCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||||
|
installOptionCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||||
|
uninstallButton.setVisibility(isUpToDate && hasRoot ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
int image, color;
|
||||||
|
|
||||||
|
if (mm.magiskVersionCode < 0) {
|
||||||
|
color = colorBad;
|
||||||
|
image = R.drawable.ic_cancel;
|
||||||
|
magiskVersionText.setText(R.string.magisk_version_error);
|
||||||
|
} else {
|
||||||
|
color = colorOK;
|
||||||
|
image = R.drawable.ic_check_circle;
|
||||||
|
magiskVersionText.setText(getString(R.string.current_magisk_title, "v" + mm.magiskVersionString));
|
||||||
|
}
|
||||||
|
|
||||||
|
magiskStatusIcon.setImageResource(image);
|
||||||
|
magiskStatusIcon.setColorFilter(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCheckUI() {
|
||||||
|
int image, color;
|
||||||
|
|
||||||
|
if (mm.remoteMagiskVersionCode < 0) {
|
||||||
|
color = colorNeutral;
|
||||||
|
image = R.drawable.ic_help;
|
||||||
|
magiskUpdateText.setText(R.string.invalid_update_channel);
|
||||||
|
installButton.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
color = colorOK;
|
||||||
|
image = R.drawable.ic_check_circle;
|
||||||
|
magiskUpdateText.setText(getString(R.string.install_magisk_title, "v" + mm.remoteMagiskVersionString));
|
||||||
|
installButton.setVisibility(View.VISIBLE);
|
||||||
|
if (mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||||
|
installText.setText(getString(R.string.update, getString(R.string.app_name)));
|
||||||
|
} else if (mm.magiskVersionCode > 0 && mm.remoteMagiskVersionCode > mm.magiskVersionCode) {
|
||||||
|
installText.setText(getString(R.string.update, getString(R.string.magisk)));
|
||||||
|
} else {
|
||||||
|
installText.setText(R.string.install);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shownDialog && (mm.remoteMagiskVersionCode > mm.magiskVersionCode
|
||||||
|
|| mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE)) {
|
||||||
|
install();
|
||||||
|
}
|
||||||
|
|
||||||
|
magiskUpdateIcon.setImageResource(image);
|
||||||
|
magiskUpdateIcon.setColorFilter(color);
|
||||||
|
magiskUpdateIcon.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
magiskUpdateProgress.setVisibility(View.GONE);
|
||||||
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSafetyNetUI(int response) {
|
||||||
|
safetyNetProgress.setVisibility(View.GONE);
|
||||||
|
safetyNetRefreshIcon.setVisibility(View.VISIBLE);
|
||||||
|
if ((response & 0x0F) == 0) {
|
||||||
|
safetyNetStatusText.setText(R.string.safetyNet_check_success);
|
||||||
|
|
||||||
|
boolean b;
|
||||||
|
b = (response & CTS_PASS) != 0;
|
||||||
|
ctsStatusText.setText("ctsProfile: " + b);
|
||||||
|
ctsStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel);
|
||||||
|
ctsStatusIcon.setColorFilter(b ? colorOK : colorBad);
|
||||||
|
|
||||||
|
b = (response & BASIC_PASS) != 0;
|
||||||
|
basicStatusText.setText("basicIntegrity: " + b);
|
||||||
|
basicStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel);
|
||||||
|
basicStatusIcon.setColorFilter(b ? colorOK : colorBad);
|
||||||
|
|
||||||
|
expand();
|
||||||
|
} else {
|
||||||
|
@StringRes int resid;
|
||||||
|
switch (response) {
|
||||||
|
case CAUSE_SERVICE_DISCONNECTED:
|
||||||
|
resid = R.string.safetyNet_network_loss;
|
||||||
|
break;
|
||||||
|
case CAUSE_NETWORK_LOST:
|
||||||
|
resid = R.string.safetyNet_service_disconnected;
|
||||||
|
break;
|
||||||
|
case RESPONSE_ERR:
|
||||||
|
resid = R.string.safetyNet_res_invalid;
|
||||||
|
break;
|
||||||
|
case CONNECTION_FAIL:
|
||||||
|
default:
|
||||||
|
resid = R.string.safetyNet_api_error;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
safetyNetStatusText.setText(resid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,13 +1,9 @@
|
|||||||
package com.topjohnwu.magisk;
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
import android.app.Fragment;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.view.MenuItemCompat;
|
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@@ -16,48 +12,41 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.SearchView;
|
import android.widget.SearchView;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
|
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
|
||||||
import com.topjohnwu.magisk.utils.Async;
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
import com.topjohnwu.magisk.utils.CallbackHandler;
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.Unbinder;
|
||||||
|
|
||||||
public class MagiskHideFragment extends Fragment implements CallbackHandler.EventListener {
|
public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||||
|
|
||||||
|
private Unbinder unbinder;
|
||||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||||
|
|
||||||
// Don't show in list...
|
|
||||||
public static final List<String> BLACKLIST = Arrays.asList(
|
|
||||||
"android",
|
|
||||||
"com.topjohnwu.magisk",
|
|
||||||
"com.google.android.gms",
|
|
||||||
"com.google.android.apps.walletnfcrel",
|
|
||||||
"com.nianticlabs.pokemongo"
|
|
||||||
);
|
|
||||||
public static CallbackHandler.Event packageLoadDone = new CallbackHandler.Event();
|
|
||||||
|
|
||||||
private ApplicationAdapter appAdapter;
|
private ApplicationAdapter appAdapter;
|
||||||
|
|
||||||
private SearchView.OnQueryTextListener searchListener;
|
private SearchView.OnQueryTextListener searchListener;
|
||||||
private String lastFilter;
|
private String lastFilter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.magisk_hide_fragment, container, false);
|
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
|
||||||
ButterKnife.bind(this, view);
|
unbinder = ButterKnife.bind(this, view);
|
||||||
|
lastFilter = "";
|
||||||
PackageManager packageManager = getActivity().getPackageManager();
|
|
||||||
|
|
||||||
mSwipeRefreshLayout.setRefreshing(true);
|
mSwipeRefreshLayout.setRefreshing(true);
|
||||||
mSwipeRefreshLayout.setOnRefreshListener(() -> new Async.LoadApps(packageManager).exec());
|
mSwipeRefreshLayout.setOnRefreshListener(() -> appAdapter.refresh());
|
||||||
|
|
||||||
appAdapter = new ApplicationAdapter(packageManager);
|
appAdapter = new ApplicationAdapter(getActivity());
|
||||||
recyclerView.setAdapter(appAdapter);
|
recyclerView.setAdapter(appAdapter);
|
||||||
|
|
||||||
searchListener = new SearchView.OnQueryTextListener() {
|
searchListener = new SearchView.OnQueryTextListener() {
|
||||||
@@ -76,41 +65,32 @@ public class MagiskHideFragment extends Fragment implements CallbackHandler.Even
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getActivity().setTitle(R.string.magiskhide);
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.menu_magiskhide, menu);
|
inflater.inflate(R.menu.menu_magiskhide, menu);
|
||||||
SearchView search = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.app_search));
|
SearchView search = (SearchView) menu.findItem(R.id.app_search).getActionView();
|
||||||
search.setOnQueryTextListener(searchListener);
|
search.setOnQueryTextListener(searchListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onDestroyView() {
|
||||||
super.onResume();
|
super.onDestroyView();
|
||||||
setHasOptionsMenu(true);
|
unbinder.unbind();
|
||||||
getActivity().setTitle(R.string.magiskhide);
|
|
||||||
CallbackHandler.register(packageLoadDone, this);
|
|
||||||
if (packageLoadDone.isTriggered) {
|
|
||||||
onTrigger(packageLoadDone);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onTopicPublished(Topic topic, Object result) {
|
||||||
super.onPause();
|
|
||||||
CallbackHandler.unRegister(packageLoadDone, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrigger(CallbackHandler.Event event) {
|
|
||||||
Logger.dev("MagiskHideFragment: UI refresh");
|
|
||||||
Async.LoadApps.Result result = (Async.LoadApps.Result) event.getResult();
|
|
||||||
appAdapter.setLists(result.listApps, result.hideList);
|
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
if (!TextUtils.isEmpty(lastFilter)) {
|
appAdapter.filter(lastFilter);
|
||||||
appAdapter.filter(lastFilter);
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public Topic[] getSubscription() {
|
||||||
|
return new Topic[] { getApplication().magiskHideDone };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
227
src/main/java/com/topjohnwu/magisk/MagiskLogFragment.java
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.HorizontalScrollView;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||||
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
|
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||||
|
import com.topjohnwu.magisk.utils.Const;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.Unbinder;
|
||||||
|
|
||||||
|
public class MagiskLogFragment extends Fragment {
|
||||||
|
|
||||||
|
private Unbinder unbinder;
|
||||||
|
|
||||||
|
@BindView(R.id.txtLog) TextView txtLog;
|
||||||
|
@BindView(R.id.svLog) ScrollView svLog;
|
||||||
|
@BindView(R.id.hsvLog) HorizontalScrollView hsvLog;
|
||||||
|
@BindView(R.id.progressBar) ProgressBar progressBar;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_magisk_log, container, false);
|
||||||
|
unbinder = ButterKnife.bind(this, view);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
|
txtLog.setTextIsSelectable(true);
|
||||||
|
|
||||||
|
new LogManager().read();
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
getActivity().setTitle(R.string.log);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
new LogManager().read();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
unbinder.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.menu_log, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.menu_refresh:
|
||||||
|
new LogManager().read();
|
||||||
|
return true;
|
||||||
|
case R.id.menu_save:
|
||||||
|
Utils.runWithPermission(getActivity(),
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
|
() -> new LogManager().save());
|
||||||
|
return true;
|
||||||
|
case R.id.menu_clear:
|
||||||
|
new LogManager().clear();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LogManager extends ParallelTask<Object, Void, Object> {
|
||||||
|
|
||||||
|
private int mode;
|
||||||
|
private File targetFile;
|
||||||
|
|
||||||
|
LogManager() {
|
||||||
|
super(MagiskLogFragment.this.getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object doInBackground(Object... params) {
|
||||||
|
mode = (int) params[0];
|
||||||
|
switch (mode) {
|
||||||
|
case 0:
|
||||||
|
StringBuildingList logList = new StringBuildingList();
|
||||||
|
Shell.su(logList, "cat " + Const.MAGISK_LOG + " | tail -n 5000");
|
||||||
|
return logList.getCharSequence();
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
Shell.su_raw("echo -n > " + Const.MAGISK_LOG);
|
||||||
|
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||||
|
return "";
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
Calendar now = Calendar.getInstance();
|
||||||
|
String filename = String.format(Locale.US,
|
||||||
|
"magisk_log_%04d%02d%02d_%02d%02d%02d.log",
|
||||||
|
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||||
|
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||||
|
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
|
||||||
|
|
||||||
|
targetFile = new File(Const.EXTERNAL_PATH + "/logs", filename);
|
||||||
|
|
||||||
|
if ((!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs())
|
||||||
|
|| (targetFile.exists() && !targetFile.delete())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileWriter out = new FileWriter(targetFile)) {
|
||||||
|
FileWritingList fileWritingList = new FileWritingList(out);
|
||||||
|
Shell.su(fileWritingList, "cat " + Const.MAGISK_LOG);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Object o) {
|
||||||
|
if (o == null) return;
|
||||||
|
switch (mode) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
CharSequence llog = (CharSequence) o;
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
if (TextUtils.isEmpty(llog))
|
||||||
|
txtLog.setText(R.string.log_is_empty);
|
||||||
|
else
|
||||||
|
txtLog.setText(llog);
|
||||||
|
svLog.postDelayed(() -> svLog.fullScroll(ScrollView.FOCUS_DOWN), 100);
|
||||||
|
hsvLog.postDelayed(() -> hsvLog.fullScroll(ScrollView.FOCUS_LEFT), 100);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
boolean bool = (boolean) o;
|
||||||
|
if (bool) {
|
||||||
|
MagiskManager.toast(targetFile.getPath(), Toast.LENGTH_LONG);
|
||||||
|
} else {
|
||||||
|
MagiskManager.toast(R.string.logs_save_failed, Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void read() {
|
||||||
|
exec(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
exec(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void save() {
|
||||||
|
exec(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class StringBuildingList extends Shell.AbstractList<String> {
|
||||||
|
|
||||||
|
StringBuilder builder;
|
||||||
|
|
||||||
|
StringBuildingList() {
|
||||||
|
builder = new StringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(String s) {
|
||||||
|
builder.append(s).append("\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence getCharSequence() {
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FileWritingList extends Shell.AbstractList<String> {
|
||||||
|
|
||||||
|
private FileWriter writer;
|
||||||
|
|
||||||
|
FileWritingList(FileWriter out) {
|
||||||
|
writer = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(String s) {
|
||||||
|
try {
|
||||||
|
writer.write(s + "\n");
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
243
src/main/java/com/topjohnwu/magisk/MagiskManager.java
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.container.Module;
|
||||||
|
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||||
|
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||||
|
import com.topjohnwu.magisk.utils.Const;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MagiskManager extends Application {
|
||||||
|
|
||||||
|
// Global weak reference to self
|
||||||
|
private static WeakReference<MagiskManager> weakSelf;
|
||||||
|
|
||||||
|
// Topics
|
||||||
|
public final Topic magiskHideDone = new Topic();
|
||||||
|
public final Topic reloadActivity = new Topic();
|
||||||
|
public final Topic moduleLoadDone = new Topic();
|
||||||
|
public final Topic repoLoadDone = new Topic();
|
||||||
|
public final Topic updateCheckDone = new Topic();
|
||||||
|
public final Topic safetyNetDone = new Topic();
|
||||||
|
public final Topic localeDone = new Topic();
|
||||||
|
|
||||||
|
// Info
|
||||||
|
public boolean hasInit = false;
|
||||||
|
public String magiskVersionString;
|
||||||
|
public int magiskVersionCode = -1;
|
||||||
|
public String remoteMagiskVersionString;
|
||||||
|
public int remoteMagiskVersionCode = -1;
|
||||||
|
public String magiskLink;
|
||||||
|
public String releaseNoteLink;
|
||||||
|
public String remoteManagerVersionString;
|
||||||
|
public int remoteManagerVersionCode = -1;
|
||||||
|
public String managerLink;
|
||||||
|
public String bootBlock = null;
|
||||||
|
public boolean keepVerity = false;
|
||||||
|
public boolean keepEnc = false;
|
||||||
|
|
||||||
|
// Data
|
||||||
|
public Map<String, Module> moduleMap;
|
||||||
|
public List<Locale> locales;
|
||||||
|
|
||||||
|
// Configurations
|
||||||
|
public static Locale locale;
|
||||||
|
public static Locale defaultLocale;
|
||||||
|
|
||||||
|
public boolean magiskHide;
|
||||||
|
public boolean isDarkTheme;
|
||||||
|
public int suRequestTimeout;
|
||||||
|
public int suLogTimeout = 14;
|
||||||
|
public int suAccessState;
|
||||||
|
public int multiuserMode;
|
||||||
|
public int suResponseType;
|
||||||
|
public int suNotificationType;
|
||||||
|
public int suNamespaceMode;
|
||||||
|
public String localeConfig;
|
||||||
|
public int updateChannel;
|
||||||
|
public String bootFormat;
|
||||||
|
public int repoOrder;
|
||||||
|
|
||||||
|
// Global resources
|
||||||
|
public SharedPreferences prefs;
|
||||||
|
public SuDatabaseHelper suDB;
|
||||||
|
public RepoDatabaseHelper repoDB;
|
||||||
|
public Shell shell;
|
||||||
|
public Runnable permissionGrantCallback = null;
|
||||||
|
|
||||||
|
private static Handler mHandler = new Handler();
|
||||||
|
|
||||||
|
public MagiskManager() {
|
||||||
|
weakSelf = new WeakReference<>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
|
||||||
|
// Handle duplicate package
|
||||||
|
if (!getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||||
|
try {
|
||||||
|
getPackageManager().getApplicationInfo(Const.ORIG_PKG_NAME, 0);
|
||||||
|
Intent intent = getPackageManager().getLaunchIntentForPackage(Const.ORIG_PKG_NAME);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(intent);
|
||||||
|
return;
|
||||||
|
} catch (PackageManager.NameNotFoundException ignored) { /* Expected */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
suDB = SuDatabaseHelper.getSuDB(false);
|
||||||
|
repoDB = new RepoDatabaseHelper(this);
|
||||||
|
defaultLocale = Locale.getDefault();
|
||||||
|
setLocale();
|
||||||
|
loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagiskManager get() {
|
||||||
|
return weakSelf.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocale() {
|
||||||
|
localeConfig = prefs.getString(Const.Key.LOCALE, "");
|
||||||
|
if (localeConfig.isEmpty()) {
|
||||||
|
locale = defaultLocale;
|
||||||
|
} else {
|
||||||
|
locale = Locale.forLanguageTag(localeConfig);
|
||||||
|
}
|
||||||
|
Resources res = getBaseContext().getResources();
|
||||||
|
Configuration config = new Configuration(res.getConfiguration());
|
||||||
|
config.setLocale(locale);
|
||||||
|
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadConfig() {
|
||||||
|
// su
|
||||||
|
suRequestTimeout = Utils.getPrefsInt(prefs, Const.Key.SU_REQUEST_TIMEOUT, Const.Value.timeoutList[2]);
|
||||||
|
suResponseType = Utils.getPrefsInt(prefs, Const.Key.SU_AUTO_RESPONSE, Const.Value.SU_PROMPT);
|
||||||
|
suNotificationType = Utils.getPrefsInt(prefs, Const.Key.SU_NOTIFICATION, Const.Value.NOTIFICATION_TOAST);
|
||||||
|
suAccessState = suDB.getSettings(Const.Key.ROOT_ACCESS, Const.Value.ROOT_ACCESS_APPS_AND_ADB);
|
||||||
|
multiuserMode = suDB.getSettings(Const.Key.SU_MULTIUSER_MODE, Const.Value.MULTIUSER_MODE_OWNER_ONLY);
|
||||||
|
suNamespaceMode = suDB.getSettings(Const.Key.SU_MNT_NS, Const.Value.NAMESPACE_MODE_REQUESTER);
|
||||||
|
|
||||||
|
// config
|
||||||
|
isDarkTheme = prefs.getBoolean(Const.Key.DARK_THEME, false);
|
||||||
|
updateChannel = Utils.getPrefsInt(prefs, Const.Key.UPDATE_CHANNEL, Const.Value.STABLE_CHANNEL);
|
||||||
|
bootFormat = prefs.getString(Const.Key.BOOT_FORMAT, ".img");
|
||||||
|
repoOrder = prefs.getInt(Const.Key.REPO_ORDER, Const.Value.ORDER_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeConfig() {
|
||||||
|
prefs.edit()
|
||||||
|
.putBoolean(Const.Key.DARK_THEME, isDarkTheme)
|
||||||
|
.putBoolean(Const.Key.MAGISKHIDE, magiskHide)
|
||||||
|
.putBoolean(Const.Key.HOSTS, Utils.itemExist(Const.MAGISK_HOST_FILE()))
|
||||||
|
.putBoolean(Const.Key.COREONLY, Utils.itemExist(Const.MAGISK_DISABLE_FILE))
|
||||||
|
.putString(Const.Key.SU_REQUEST_TIMEOUT, String.valueOf(suRequestTimeout))
|
||||||
|
.putString(Const.Key.SU_AUTO_RESPONSE, String.valueOf(suResponseType))
|
||||||
|
.putString(Const.Key.SU_NOTIFICATION, String.valueOf(suNotificationType))
|
||||||
|
.putString(Const.Key.ROOT_ACCESS, String.valueOf(suAccessState))
|
||||||
|
.putString(Const.Key.SU_MULTIUSER_MODE, String.valueOf(multiuserMode))
|
||||||
|
.putString(Const.Key.SU_MNT_NS, String.valueOf(suNamespaceMode))
|
||||||
|
.putString(Const.Key.UPDATE_CHANNEL, String.valueOf(updateChannel))
|
||||||
|
.putString(Const.Key.LOCALE, localeConfig)
|
||||||
|
.putString(Const.Key.BOOT_FORMAT, bootFormat)
|
||||||
|
.putInt(Const.Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
|
||||||
|
.putInt(Const.Key.REPO_ORDER, repoOrder)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void toast(CharSequence msg, int duration) {
|
||||||
|
mHandler.post(() -> Toast.makeText(weakSelf.get(), msg, duration).show());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void toast(int resId, int duration) {
|
||||||
|
mHandler.post(() -> Toast.makeText(weakSelf.get(), resId, duration).show());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadMagiskInfo() {
|
||||||
|
List<String> ret;
|
||||||
|
ret = Shell.sh("magisk -v");
|
||||||
|
if (!Utils.isValidShellResponse(ret)) {
|
||||||
|
ret = Shell.sh("getprop magisk.version");
|
||||||
|
if (Utils.isValidShellResponse(ret)) {
|
||||||
|
try {
|
||||||
|
magiskVersionString = ret.get(0);
|
||||||
|
magiskVersionCode = (int) Double.parseDouble(ret.get(0)) * 10;
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
magiskVersionString = ret.get(0).split(":")[0];
|
||||||
|
ret = Shell.sh("magisk -V");
|
||||||
|
try {
|
||||||
|
magiskVersionCode = Integer.parseInt(ret.get(0));
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
}
|
||||||
|
if (magiskVersionCode > 1435) {
|
||||||
|
ret = Shell.su("resetprop -p " + Const.MAGISKHIDE_PROP);
|
||||||
|
} else {
|
||||||
|
ret = Shell.sh("getprop " + Const.MAGISKHIDE_PROP);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
magiskHide = !Utils.isValidShellResponse(ret) || Integer.parseInt(ret.get(0)) != 0;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
magiskHide = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = Shell.su("echo \"$BOOTIMAGE\"");
|
||||||
|
if (Utils.isValidShellResponse(ret))
|
||||||
|
bootBlock = ret.get(0);
|
||||||
|
|
||||||
|
if (suDB != null && !SuDatabaseHelper.verified) {
|
||||||
|
suDB.close();
|
||||||
|
suDB = SuDatabaseHelper.getSuDB(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getDefaultInstallFlags() {
|
||||||
|
List<String> ret;
|
||||||
|
ret = Shell.su("echo \"$DTBOIMAGE\"");
|
||||||
|
if (Utils.isValidShellResponse(ret))
|
||||||
|
keepVerity = true;
|
||||||
|
|
||||||
|
ret = Shell.su(
|
||||||
|
"getvar KEEPVERITY",
|
||||||
|
"echo $KEEPVERITY");
|
||||||
|
try {
|
||||||
|
if (Utils.isValidShellResponse(ret))
|
||||||
|
keepVerity = Boolean.parseBoolean(ret.get(0));
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
|
||||||
|
ret = Shell.sh("getprop ro.crypto.state");
|
||||||
|
if (Utils.isValidShellResponse(ret) && ret.get(0).equals("encrypted"))
|
||||||
|
keepEnc = true;
|
||||||
|
|
||||||
|
ret = Shell.su(
|
||||||
|
"getvar KEEPFORCEENCRYPT",
|
||||||
|
"echo $KEEPFORCEENCRYPT");
|
||||||
|
try {
|
||||||
|
if (Utils.isValidShellResponse(ret))
|
||||||
|
keepEnc = Boolean.parseBoolean(ret.get(0));
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPermissionGrantCallback(Runnable callback) {
|
||||||
|
permissionGrantCallback = callback;
|
||||||
|
}
|
||||||
|
}
|
||||||
236
src/main/java/com/topjohnwu/magisk/MainActivity.java
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.design.widget.NavigationView;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentTransaction;
|
||||||
|
import android.support.v4.widget.DrawerLayout;
|
||||||
|
import android.support.v7.app.ActionBarDrawerToggle;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||||
|
import com.topjohnwu.magisk.components.Activity;
|
||||||
|
import com.topjohnwu.magisk.utils.Const;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
|
public class MainActivity extends Activity
|
||||||
|
implements NavigationView.OnNavigationItemSelectedListener, Topic.Subscriber {
|
||||||
|
|
||||||
|
private final Handler mDrawerHandler = new Handler();
|
||||||
|
private SharedPreferences prefs;
|
||||||
|
private int mDrawerItem;
|
||||||
|
|
||||||
|
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||||
|
@BindView(R.id.drawer_layout) DrawerLayout drawer;
|
||||||
|
@BindView(R.id.nav_view) public NavigationView navigationView;
|
||||||
|
|
||||||
|
private float toolbarElevation;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDarkTheme() {
|
||||||
|
return R.style.AppTheme_Dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
|
|
||||||
|
MagiskManager mm = getMagiskManager();
|
||||||
|
prefs = mm.prefs;
|
||||||
|
|
||||||
|
if (!mm.hasInit) {
|
||||||
|
Intent intent = new Intent(this, SplashActivity.class);
|
||||||
|
String section = getIntent().getStringExtra(Const.Key.OPEN_SECTION);
|
||||||
|
if (section != null) {
|
||||||
|
intent.putExtra(Const.Key.OPEN_SECTION, section);
|
||||||
|
}
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
String perm = getIntent().getStringExtra(Const.Key.INTENT_PERM);
|
||||||
|
if (perm != null) {
|
||||||
|
ActivityCompat.requestPermissions(this, new String[] { perm }, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.magisk, R.string.magisk) {
|
||||||
|
@Override
|
||||||
|
public void onDrawerOpened(View drawerView) {
|
||||||
|
super.onDrawerOpened(drawerView);
|
||||||
|
super.onDrawerSlide(drawerView, 0); // this disables the arrow @ completed tate
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrawerSlide(View drawerView, float slideOffset) {
|
||||||
|
super.onDrawerSlide(drawerView, 0); // this disables the animation
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
toolbarElevation = toolbar.getElevation();
|
||||||
|
|
||||||
|
drawer.addDrawerListener(toggle);
|
||||||
|
toggle.syncState();
|
||||||
|
|
||||||
|
if (savedInstanceState == null)
|
||||||
|
navigate(getIntent().getStringExtra(Const.Key.OPEN_SECTION));
|
||||||
|
|
||||||
|
navigationView.setNavigationItemSelectedListener(this);
|
||||||
|
|
||||||
|
if (mm.prefs.getInt(Const.Key.APP_VER, -1) < BuildConfig.VERSION_CODE) {
|
||||||
|
prefs.edit().putInt(Const.Key.APP_VER, BuildConfig.VERSION_CODE).apply();
|
||||||
|
try {
|
||||||
|
InputStream is = getAssets().open("changelog.md");
|
||||||
|
new MarkDownWindow(this, getString(R.string.app_changelog), is).exec();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
checkHideSection();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (drawer.isDrawerOpen(navigationView)) {
|
||||||
|
drawer.closeDrawer(navigationView);
|
||||||
|
} else if (mDrawerItem != R.id.magisk) {
|
||||||
|
navigate(R.id.magisk);
|
||||||
|
} else {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onNavigationItemSelected(@NonNull final MenuItem menuItem) {
|
||||||
|
mDrawerHandler.removeCallbacksAndMessages(null);
|
||||||
|
mDrawerHandler.postDelayed(() -> navigate(menuItem.getItemId()), 250);
|
||||||
|
drawer.closeDrawer(navigationView);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTopicPublished(Topic topic, Object result) {
|
||||||
|
recreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Topic[] getSubscription() {
|
||||||
|
return new Topic[] { getMagiskManager().reloadActivity };
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkHideSection() {
|
||||||
|
MagiskManager mm = getMagiskManager();
|
||||||
|
Menu menu = navigationView.getMenu();
|
||||||
|
menu.findItem(R.id.magiskhide).setVisible(
|
||||||
|
Shell.rootAccess() && mm.magiskVersionCode >= 1300
|
||||||
|
&& prefs.getBoolean(Const.Key.MAGISKHIDE, false));
|
||||||
|
menu.findItem(R.id.modules).setVisible(!mm.prefs.getBoolean(Const.Key.COREONLY, false) &&
|
||||||
|
Shell.rootAccess() && mm.magiskVersionCode >= 0);
|
||||||
|
menu.findItem(R.id.downloads).setVisible(!mm.prefs.getBoolean(Const.Key.COREONLY, false)
|
||||||
|
&& Utils.checkNetworkStatus() && Shell.rootAccess() && mm.magiskVersionCode >= 0);
|
||||||
|
menu.findItem(R.id.log).setVisible(Shell.rootAccess());
|
||||||
|
menu.findItem(R.id.superuser).setVisible(Shell.rootAccess() &&
|
||||||
|
!(Const.USER_ID > 0 && mm.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void navigate(String item) {
|
||||||
|
int itemId = R.id.magisk;
|
||||||
|
if (item != null) {
|
||||||
|
switch (item) {
|
||||||
|
case "magisk":
|
||||||
|
itemId = R.id.magisk;
|
||||||
|
break;
|
||||||
|
case "superuser":
|
||||||
|
itemId = R.id.superuser;
|
||||||
|
break;
|
||||||
|
case "modules":
|
||||||
|
itemId = R.id.modules;
|
||||||
|
break;
|
||||||
|
case "downloads":
|
||||||
|
itemId = R.id.downloads;
|
||||||
|
break;
|
||||||
|
case "magiskhide":
|
||||||
|
itemId = R.id.magiskhide;
|
||||||
|
break;
|
||||||
|
case "log":
|
||||||
|
itemId = R.id.log;
|
||||||
|
break;
|
||||||
|
case "settings":
|
||||||
|
itemId = R.id.settings;
|
||||||
|
break;
|
||||||
|
case "about":
|
||||||
|
itemId = R.id.app_about;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
navigate(itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void navigate(int itemId) {
|
||||||
|
int bak = mDrawerItem;
|
||||||
|
mDrawerItem = itemId;
|
||||||
|
navigationView.setCheckedItem(itemId);
|
||||||
|
switch (itemId) {
|
||||||
|
case R.id.magisk:
|
||||||
|
displayFragment(new MagiskFragment(), "magisk", true);
|
||||||
|
break;
|
||||||
|
case R.id.superuser:
|
||||||
|
displayFragment(new SuperuserFragment(), "superuser", true);
|
||||||
|
break;
|
||||||
|
case R.id.modules:
|
||||||
|
displayFragment(new ModulesFragment(), "modules", true);
|
||||||
|
break;
|
||||||
|
case R.id.downloads:
|
||||||
|
displayFragment(new ReposFragment(), "downloads", true);
|
||||||
|
break;
|
||||||
|
case R.id.magiskhide:
|
||||||
|
displayFragment(new MagiskHideFragment(), Const.Key.MAGISKHIDE, true);
|
||||||
|
break;
|
||||||
|
case R.id.log:
|
||||||
|
displayFragment(new LogFragment(), "log", false);
|
||||||
|
break;
|
||||||
|
case R.id.settings:
|
||||||
|
startActivity(new Intent(this, SettingsActivity.class));
|
||||||
|
mDrawerItem = bak;
|
||||||
|
break;
|
||||||
|
case R.id.app_about:
|
||||||
|
startActivity(new Intent(this, AboutActivity.class));
|
||||||
|
mDrawerItem = bak;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayFragment(@NonNull Fragment navFragment, String tag, boolean setElevation) {
|
||||||
|
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||||
|
supportInvalidateOptionsMenu();
|
||||||
|
transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||||
|
transaction.replace(R.id.content_frame, navFragment, tag).commitNow();
|
||||||
|
if (setElevation) toolbar.setElevation(toolbarElevation);
|
||||||
|
else toolbar.setElevation(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
145
src/main/java/com/topjohnwu/magisk/ModulesFragment.java
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.adapters.ModulesAdapter;
|
||||||
|
import com.topjohnwu.magisk.asyncs.LoadModules;
|
||||||
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
|
import com.topjohnwu.magisk.container.Module;
|
||||||
|
import com.topjohnwu.magisk.utils.Const;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
import butterknife.Unbinder;
|
||||||
|
|
||||||
|
public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||||
|
|
||||||
|
private Unbinder unbinder;
|
||||||
|
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||||
|
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||||
|
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||||
|
@OnClick(R.id.fab)
|
||||||
|
public void selectFile() {
|
||||||
|
Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
intent.setType("application/zip");
|
||||||
|
startActivityForResult(intent, Const.ID.FETCH_ZIP);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Module> listModules = new ArrayList<>();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_modules, container, false);
|
||||||
|
unbinder = ButterKnife.bind(this, view);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
|
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||||
|
recyclerView.setVisibility(View.GONE);
|
||||||
|
new LoadModules().exec();
|
||||||
|
});
|
||||||
|
|
||||||
|
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
mSwipeRefreshLayout.setEnabled(recyclerView.getChildAt(0).getTop() >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||||
|
super.onScrollStateChanged(recyclerView, newState);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
getActivity().setTitle(R.string.modules);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTopicPublished(Topic topic, Object result) {
|
||||||
|
updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Topic[] getSubscription() {
|
||||||
|
return new Topic[] { getApplication().moduleLoadDone };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
|
||||||
|
// Get the URI of the selected file
|
||||||
|
Intent intent = new Intent(getActivity(), FlashActivity.class);
|
||||||
|
intent.setData(data.getData()).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
unbinder.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.menu_reboot, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.reboot:
|
||||||
|
Shell.su_raw("/system/bin/reboot");
|
||||||
|
return true;
|
||||||
|
case R.id.reboot_recovery:
|
||||||
|
Shell.su_raw("/system/bin/reboot recovery");
|
||||||
|
return true;
|
||||||
|
case R.id.reboot_bootloader:
|
||||||
|
Shell.su_raw("/system/bin/reboot bootloader");
|
||||||
|
return true;
|
||||||
|
case R.id.reboot_download:
|
||||||
|
Shell.su_raw("/system/bin/reboot download");
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateUI() {
|
||||||
|
listModules.clear();
|
||||||
|
listModules.addAll(getApplication().moduleMap.values());
|
||||||
|
if (listModules.size() == 0) {
|
||||||
|
emptyRv.setVisibility(View.VISIBLE);
|
||||||
|
recyclerView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
emptyRv.setVisibility(View.GONE);
|
||||||
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
|
recyclerView.setAdapter(new ModulesAdapter(listModules));
|
||||||
|
}
|
||||||
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
125
src/main/java/com/topjohnwu/magisk/ReposFragment.java
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.SearchView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
||||||
|
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||||
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
|
import com.topjohnwu.magisk.utils.Const;
|
||||||
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.Unbinder;
|
||||||
|
|
||||||
|
public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||||
|
|
||||||
|
private Unbinder unbinder;
|
||||||
|
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||||
|
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||||
|
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||||
|
|
||||||
|
public static ReposAdapter adapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_repos, container, false);
|
||||||
|
unbinder = ButterKnife.bind(this, view);
|
||||||
|
|
||||||
|
mSwipeRefreshLayout.setRefreshing(true);
|
||||||
|
|
||||||
|
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||||
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
|
emptyRv.setVisibility(View.GONE);
|
||||||
|
new UpdateRepos(true).exec();
|
||||||
|
});
|
||||||
|
|
||||||
|
getActivity().setTitle(R.string.downloads);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
adapter = new ReposAdapter(getApplication().repoDB, getApplication().moduleMap);
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
super.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
adapter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTopicPublished(Topic topic, Object result) {
|
||||||
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
|
recyclerView.setVisibility(adapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
|
||||||
|
emptyRv.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Topic[] getSubscription() {
|
||||||
|
return new Topic[] { getApplication().repoLoadDone };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.menu_repo, menu);
|
||||||
|
SearchView search = (SearchView) menu.findItem(R.id.repo_search).getActionView();
|
||||||
|
search.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onQueryTextSubmit(String query) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onQueryTextChange(String newText) {
|
||||||
|
adapter.filter(newText);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
MagiskManager mm = getApplication();
|
||||||
|
if (item.getItemId() == R.id.repo_sort) {
|
||||||
|
new AlertDialog.Builder(getActivity())
|
||||||
|
.setTitle(R.string.sorting_order)
|
||||||
|
.setSingleChoiceItems(R.array.sorting_orders, mm.repoOrder, (d, which) -> {
|
||||||
|
mm.repoOrder = which;
|
||||||
|
mm.prefs.edit().putInt(Const.Key.REPO_ORDER, mm.repoOrder).apply();
|
||||||
|
adapter.notifyDBChanged();
|
||||||
|
d.dismiss();
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
unbinder.unbind();
|
||||||
|
}
|
||||||
|
}
|
||||||
295
src/main/java/com/topjohnwu/magisk/SettingsActivity.java
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.preference.ListPreference;
|
||||||
|
import android.preference.Preference;
|
||||||
|
import android.preference.PreferenceCategory;
|
||||||
|
import android.preference.PreferenceFragment;
|
||||||
|
import android.preference.PreferenceScreen;
|
||||||
|
import android.preference.SwitchPreference;
|
||||||
|
import android.support.v7.app.ActionBar;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||||
|
import com.topjohnwu.magisk.asyncs.HideManager;
|
||||||
|
import com.topjohnwu.magisk.components.Activity;
|
||||||
|
import com.topjohnwu.magisk.utils.Const;
|
||||||
|
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
|
public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||||
|
|
||||||
|
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDarkTheme() {
|
||||||
|
return R.style.AppTheme_Transparent_Dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_settings);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
toolbar.setNavigationOnClickListener(view -> finish());
|
||||||
|
|
||||||
|
ActionBar ab = getSupportActionBar();
|
||||||
|
if (ab != null) {
|
||||||
|
ab.setTitle(R.string.settings);
|
||||||
|
ab.setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFloating();
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
getFragmentManager().beginTransaction().add(R.id.container, new SettingsFragment()).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTopicPublished(Topic topic, Object result) {
|
||||||
|
recreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Topic[] getSubscription() {
|
||||||
|
return new Topic[] { getMagiskManager().reloadActivity };
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SettingsFragment extends PreferenceFragment
|
||||||
|
implements SharedPreferences.OnSharedPreferenceChangeListener, Topic.Subscriber {
|
||||||
|
|
||||||
|
private SharedPreferences prefs;
|
||||||
|
private PreferenceScreen prefScreen;
|
||||||
|
|
||||||
|
private ListPreference updateChannel, suAccess, autoRes, suNotification,
|
||||||
|
requestTimeout, multiuserMode, namespaceMode;
|
||||||
|
private MagiskManager mm;
|
||||||
|
private PreferenceCategory generalCatagory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
addPreferencesFromResource(R.xml.app_settings);
|
||||||
|
mm = Utils.getMagiskManager(getActivity());
|
||||||
|
prefs = mm.prefs;
|
||||||
|
prefScreen = getPreferenceScreen();
|
||||||
|
|
||||||
|
generalCatagory = (PreferenceCategory) findPreference("general");
|
||||||
|
PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
|
||||||
|
PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
|
||||||
|
Preference hideManager = findPreference("hide");
|
||||||
|
findPreference("clear").setOnPreferenceClickListener((pref) -> {
|
||||||
|
prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
||||||
|
mm.repoDB.clearRepo();
|
||||||
|
MagiskManager.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
updateChannel = (ListPreference) findPreference(Const.Key.UPDATE_CHANNEL);
|
||||||
|
suAccess = (ListPreference) findPreference(Const.Key.ROOT_ACCESS);
|
||||||
|
autoRes = (ListPreference) findPreference(Const.Key.SU_AUTO_RESPONSE);
|
||||||
|
requestTimeout = (ListPreference) findPreference(Const.Key.SU_REQUEST_TIMEOUT);
|
||||||
|
suNotification = (ListPreference) findPreference(Const.Key.SU_NOTIFICATION);
|
||||||
|
multiuserMode = (ListPreference) findPreference(Const.Key.SU_MULTIUSER_MODE);
|
||||||
|
namespaceMode = (ListPreference) findPreference(Const.Key.SU_MNT_NS);
|
||||||
|
SwitchPreference reauth = (SwitchPreference) findPreference(Const.Key.SU_REAUTH);
|
||||||
|
SwitchPreference fingerprint = (SwitchPreference) findPreference(Const.Key.SU_FINGERPRINT);
|
||||||
|
|
||||||
|
updateChannel.setOnPreferenceChangeListener((pref, o) -> {
|
||||||
|
mm.updateChannel = Integer.parseInt((String) o);
|
||||||
|
if (mm.updateChannel == Const.Value.CUSTOM_CHANNEL) {
|
||||||
|
View v = LayoutInflater.from(getActivity()).inflate(R.layout.custom_channel_dialog, null);
|
||||||
|
EditText url = v.findViewById(R.id.custom_url);
|
||||||
|
url.setText(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||||
|
new AlertDialog.Builder(getActivity())
|
||||||
|
.setTitle(R.string.settings_update_custom)
|
||||||
|
.setView(v)
|
||||||
|
.setPositiveButton(R.string.ok, (d, i) ->
|
||||||
|
prefs.edit().putString(Const.Key.CUSTOM_CHANNEL,
|
||||||
|
url.getText().toString()).apply())
|
||||||
|
.setNegativeButton(R.string.close, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
setSummary();
|
||||||
|
|
||||||
|
// Disable dangerous settings in secondary user
|
||||||
|
if (Const.USER_ID > 0) {
|
||||||
|
suCategory.removePreference(multiuserMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable re-authentication option on Android O, it will not work
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
reauth.setEnabled(false);
|
||||||
|
reauth.setSummary(R.string.android_o_not_support);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove fingerprint option if not possible
|
||||||
|
if (!FingerprintHelper.canUseFingerprint()) {
|
||||||
|
suCategory.removePreference(fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm.getPackageName().equals(Const.ORIG_PKG_NAME) && mm.magiskVersionCode >= 1440) {
|
||||||
|
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||||
|
Utils.runWithPermission(getActivity(),
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
|
() -> new HideManager(getActivity()).exec());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
generalCatagory.removePreference(hideManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Shell.rootAccess() || (Const.USER_ID > 0 &&
|
||||||
|
mm.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||||
|
prefScreen.removePreference(suCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Shell.rootAccess()) {
|
||||||
|
prefScreen.removePreference(magiskCategory);
|
||||||
|
generalCatagory.removePreference(hideManager);
|
||||||
|
} else if (mm.magiskVersionCode < 1300) {
|
||||||
|
prefScreen.removePreference(magiskCategory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLocalePreference(ListPreference lp) {
|
||||||
|
boolean isNew = lp == null;
|
||||||
|
if (isNew) {
|
||||||
|
lp = new ListPreference(getActivity());
|
||||||
|
}
|
||||||
|
CharSequence[] entries = new CharSequence[mm.locales.size() + 1];
|
||||||
|
CharSequence[] entryValues = new CharSequence[mm.locales.size() + 1];
|
||||||
|
entries[0] = getString(R.string.system_default);
|
||||||
|
entryValues[0] = "";
|
||||||
|
int i = 1;
|
||||||
|
for (Locale locale : mm.locales) {
|
||||||
|
entries[i] = locale.getDisplayName(locale);
|
||||||
|
entryValues[i++] = locale.toLanguageTag();
|
||||||
|
}
|
||||||
|
lp.setEntries(entries);
|
||||||
|
lp.setEntryValues(entryValues);
|
||||||
|
lp.setTitle(R.string.language);
|
||||||
|
lp.setKey(Const.Key.LOCALE);
|
||||||
|
lp.setSummary(MagiskManager.locale.getDisplayName(MagiskManager.locale));
|
||||||
|
if (isNew) {
|
||||||
|
generalCatagory.addPreference(lp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||||
|
subscribeTopics();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||||
|
unsubscribeTopics();
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case Const.Key.DARK_THEME:
|
||||||
|
mm.isDarkTheme = prefs.getBoolean(key, false);
|
||||||
|
mm.reloadActivity.publish(false);
|
||||||
|
break;
|
||||||
|
case Const.Key.COREONLY:
|
||||||
|
if (prefs.getBoolean(key, false)) {
|
||||||
|
Utils.createFile(Const.MAGISK_DISABLE_FILE);
|
||||||
|
} else {
|
||||||
|
Utils.removeItem(Const.MAGISK_DISABLE_FILE);
|
||||||
|
}
|
||||||
|
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
|
||||||
|
break;
|
||||||
|
case Const.Key.MAGISKHIDE:
|
||||||
|
if (prefs.getBoolean(key, false)) {
|
||||||
|
Shell.su_raw("magiskhide --enable");
|
||||||
|
} else {
|
||||||
|
Shell.su_raw("magiskhide --disable");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Const.Key.HOSTS:
|
||||||
|
if (prefs.getBoolean(key, false)) {
|
||||||
|
Shell.su_raw(
|
||||||
|
"cp -af /system/etc/hosts " + Const.MAGISK_HOST_FILE(),
|
||||||
|
"mount -o bind " + Const.MAGISK_HOST_FILE() + " /system/etc/hosts");
|
||||||
|
} else {
|
||||||
|
Shell.su_raw(
|
||||||
|
"umount -l /system/etc/hosts",
|
||||||
|
"rm -f " + Const.MAGISK_HOST_FILE());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Const.Key.ROOT_ACCESS:
|
||||||
|
case Const.Key.SU_MULTIUSER_MODE:
|
||||||
|
case Const.Key.SU_MNT_NS:
|
||||||
|
mm.suDB.setSettings(key, Utils.getPrefsInt(prefs, key));
|
||||||
|
break;
|
||||||
|
case Const.Key.LOCALE:
|
||||||
|
mm.setLocale();
|
||||||
|
mm.reloadActivity.publish(false);
|
||||||
|
break;
|
||||||
|
case Const.Key.UPDATE_CHANNEL:
|
||||||
|
new CheckUpdates().exec();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mm.loadConfig();
|
||||||
|
setSummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSummary() {
|
||||||
|
updateChannel.setSummary(getResources()
|
||||||
|
.getStringArray(R.array.update_channel)[mm.updateChannel]);
|
||||||
|
suAccess.setSummary(getResources()
|
||||||
|
.getStringArray(R.array.su_access)[mm.suAccessState]);
|
||||||
|
autoRes.setSummary(getResources()
|
||||||
|
.getStringArray(R.array.auto_response)[mm.suResponseType]);
|
||||||
|
suNotification.setSummary(getResources()
|
||||||
|
.getStringArray(R.array.su_notification)[mm.suNotificationType]);
|
||||||
|
requestTimeout.setSummary(
|
||||||
|
getString(R.string.request_timeout_summary, prefs.getString(Const.Key.SU_REQUEST_TIMEOUT, "10")));
|
||||||
|
multiuserMode.setSummary(getResources()
|
||||||
|
.getStringArray(R.array.multiuser_summary)[mm.multiuserMode]);
|
||||||
|
namespaceMode.setSummary(getResources()
|
||||||
|
.getStringArray(R.array.namespace_summary)[mm.suNamespaceMode]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTopicPublished(Topic topic, Object result) {
|
||||||
|
setLocalePreference((ListPreference) findPreference(Const.Key.LOCALE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Topic[] getSubscription() {
|
||||||
|
return new Topic[] { mm.localeDone };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
105
src/main/java/com/topjohnwu/magisk/SplashActivity.java
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.job.JobInfo;
|
||||||
|
import android.app.job.JobScheduler;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||||
|
import com.topjohnwu.magisk.asyncs.LoadModules;
|
||||||
|
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||||
|
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||||
|
import com.topjohnwu.magisk.components.Activity;
|
||||||
|
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||||
|
import com.topjohnwu.magisk.utils.Const;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
public class SplashActivity extends Activity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDarkTheme() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
MagiskManager mm = getMagiskManager();
|
||||||
|
|
||||||
|
mm.loadMagiskInfo();
|
||||||
|
mm.getDefaultInstallFlags();
|
||||||
|
Utils.loadPrefs();
|
||||||
|
|
||||||
|
// Dynamic detect all locales
|
||||||
|
new LoadLocale().exec();
|
||||||
|
|
||||||
|
// Create notification channel on Android O
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationChannel channel = new NotificationChannel(Const.ID.NOTIFICATION_CHANNEL,
|
||||||
|
getString(R.string.magisk_updates), NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
getSystemService(NotificationManager.class).createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadModules loadModuleTask = new LoadModules();
|
||||||
|
|
||||||
|
if (Utils.checkNetworkStatus()) {
|
||||||
|
|
||||||
|
// Fire update check
|
||||||
|
new CheckUpdates().exec();
|
||||||
|
|
||||||
|
// Add repo update check
|
||||||
|
loadModuleTask.setCallBack(() -> new UpdateRepos(false).exec());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Magisk working as expected
|
||||||
|
if (Shell.rootAccess() && mm.magiskVersionCode > 0) {
|
||||||
|
|
||||||
|
// Add update checking service
|
||||||
|
if (Const.UPDATE_SERVICE_VER > mm.prefs.getInt(Const.Key.UPDATE_SERVICE_VER, -1)) {
|
||||||
|
ComponentName service = new ComponentName(this, UpdateCheckService.class);
|
||||||
|
JobInfo info = new JobInfo.Builder(Const.ID.UPDATE_SERVICE_ID, service)
|
||||||
|
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||||
|
.setPersisted(true)
|
||||||
|
.setPeriodic(8 * 60 * 60 * 1000)
|
||||||
|
.build();
|
||||||
|
((JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire asynctasks
|
||||||
|
loadModuleTask.exec();
|
||||||
|
|
||||||
|
// Check dtbo status
|
||||||
|
Utils.patchDTBO();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write back default values
|
||||||
|
mm.writeConfig();
|
||||||
|
|
||||||
|
mm.hasInit = true;
|
||||||
|
|
||||||
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
|
intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION));
|
||||||
|
intent.putExtra(Const.Key.INTENT_PERM, getIntent().getStringExtra(Const.Key.INTENT_PERM));
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class LoadLocale extends ParallelTask<Void, Void, Void> {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
MagiskManager.get().locales = Utils.getAvailableLocale();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void aVoid) {
|
||||||
|
MagiskManager.get().localeDone.publish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/main/java/com/topjohnwu/magisk/SuLogFragment.java
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.adapters.SuLogAdapter;
|
||||||
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.Unbinder;
|
||||||
|
|
||||||
|
public class SuLogFragment extends Fragment {
|
||||||
|
|
||||||
|
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||||
|
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||||
|
|
||||||
|
private Unbinder unbinder;
|
||||||
|
private MagiskManager mm;
|
||||||
|
private SuLogAdapter adapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.menu_log, menu);
|
||||||
|
menu.findItem(R.id.menu_save).setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
// Inflate the layout for this fragment
|
||||||
|
View v = inflater.inflate(R.layout.fragment_su_log, container, false);
|
||||||
|
unbinder = ButterKnife.bind(this, v);
|
||||||
|
mm = getApplication();
|
||||||
|
adapter = new SuLogAdapter(mm.suDB);
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
|
updateList();
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateList() {
|
||||||
|
adapter.notifyDBChanged();
|
||||||
|
|
||||||
|
if (adapter.getSectionCount() == 0) {
|
||||||
|
emptyRv.setVisibility(View.VISIBLE);
|
||||||
|
recyclerView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
emptyRv.setVisibility(View.GONE);
|
||||||
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.menu_refresh:
|
||||||
|
updateList();
|
||||||
|
return true;
|
||||||
|
case R.id.menu_clear:
|
||||||
|
mm.suDB.clearLogs();
|
||||||
|
updateList();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
unbinder.unbind();
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/main/java/com/topjohnwu/magisk/SuperuserFragment.java
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.adapters.PolicyAdapter;
|
||||||
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
|
import com.topjohnwu.magisk.container.Policy;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.Unbinder;
|
||||||
|
|
||||||
|
public class SuperuserFragment extends Fragment {
|
||||||
|
|
||||||
|
private Unbinder unbinder;
|
||||||
|
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||||
|
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_superuser, container, false);
|
||||||
|
unbinder = ButterKnife.bind(this, view);
|
||||||
|
|
||||||
|
PackageManager pm = getActivity().getPackageManager();
|
||||||
|
MagiskManager mm = getApplication();
|
||||||
|
|
||||||
|
List<Policy> policyList = mm.suDB.getPolicyList(pm);
|
||||||
|
|
||||||
|
if (policyList.size() == 0) {
|
||||||
|
emptyRv.setVisibility(View.VISIBLE);
|
||||||
|
recyclerView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
recyclerView.setAdapter(new PolicyAdapter(policyList, mm.suDB, pm));
|
||||||
|
emptyRv.setVisibility(View.GONE);
|
||||||
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
getActivity().setTitle(getString(R.string.superuser));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
unbinder.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.Filter;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.R;
|
||||||
|
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||||
|
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||||
|
import com.topjohnwu.magisk.utils.Const;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
|
public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> {
|
||||||
|
|
||||||
|
private List<ApplicationInfo> mOriginalList, mList;
|
||||||
|
private List<String> mHideList;
|
||||||
|
private PackageManager pm;
|
||||||
|
private ApplicationFilter filter;
|
||||||
|
private Topic magiskHideDone;
|
||||||
|
|
||||||
|
public ApplicationAdapter(Context context) {
|
||||||
|
mOriginalList = mList = Collections.emptyList();
|
||||||
|
mHideList = Collections.emptyList();
|
||||||
|
filter = new ApplicationFilter();
|
||||||
|
pm = context.getPackageManager();
|
||||||
|
magiskHideDone = Utils.getMagiskManager(context).magiskHideDone;
|
||||||
|
new LoadApps().exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean lowercaseContains(CharSequence string, CharSequence nonNullLowercaseSearch) {
|
||||||
|
return !TextUtils.isEmpty(string) && string.toString().toLowerCase().contains(nonNullLowercaseSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_app, parent, false);
|
||||||
|
return new ViewHolder(mView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(final ViewHolder holder, int position) {
|
||||||
|
ApplicationInfo info = mList.get(position);
|
||||||
|
|
||||||
|
holder.appIcon.setImageDrawable(info.loadIcon(pm));
|
||||||
|
holder.appName.setText(info.loadLabel(pm));
|
||||||
|
holder.appPackage.setText(info.packageName);
|
||||||
|
|
||||||
|
// Remove all listeners
|
||||||
|
holder.itemView.setOnClickListener(null);
|
||||||
|
holder.checkBox.setOnCheckedChangeListener(null);
|
||||||
|
|
||||||
|
if (Const.SN_DEFAULTLIST.contains(info.packageName)) {
|
||||||
|
holder.checkBox.setChecked(true);
|
||||||
|
holder.checkBox.setEnabled(false);
|
||||||
|
holder.itemView.setOnClickListener(v ->
|
||||||
|
SnackbarMaker.make(holder.itemView,
|
||||||
|
R.string.safetyNet_hide_notice, Snackbar.LENGTH_LONG).show()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
holder.checkBox.setEnabled(true);
|
||||||
|
holder.checkBox.setChecked(mHideList.contains(info.packageName));
|
||||||
|
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||||
|
if (isChecked) {
|
||||||
|
Shell.su_raw("magiskhide --add " + info.packageName);
|
||||||
|
mHideList.add(info.packageName);
|
||||||
|
} else {
|
||||||
|
Shell.su_raw("magiskhide --rm " + info.packageName);
|
||||||
|
mHideList.remove(info.packageName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void filter(String constraint) {
|
||||||
|
filter.filter(constraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
new LoadApps().exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
@BindView(R.id.app_icon) ImageView appIcon;
|
||||||
|
@BindView(R.id.app_name) TextView appName;
|
||||||
|
@BindView(R.id.package_name) TextView appPackage;
|
||||||
|
@BindView(R.id.checkbox) CheckBox checkBox;
|
||||||
|
|
||||||
|
ViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
ButterKnife.bind(this, itemView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ApplicationFilter extends Filter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected FilterResults performFiltering(CharSequence constraint) {
|
||||||
|
if (constraint == null || constraint.length() == 0) {
|
||||||
|
mList = mOriginalList;
|
||||||
|
} else {
|
||||||
|
mList = new ArrayList<>();
|
||||||
|
String filter = constraint.toString().toLowerCase();
|
||||||
|
for (ApplicationInfo info : mOriginalList) {
|
||||||
|
if (lowercaseContains(info.loadLabel(pm), filter)
|
||||||
|
|| lowercaseContains(info.packageName, filter)) {
|
||||||
|
mList.add(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LoadApps extends ParallelTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
mOriginalList = pm.getInstalledApplications(0);
|
||||||
|
for (Iterator<ApplicationInfo> i = mOriginalList.iterator(); i.hasNext(); ) {
|
||||||
|
ApplicationInfo info = i.next();
|
||||||
|
if (Const.HIDE_BLACKLIST.contains(info.packageName) || !info.enabled) {
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Collections.sort(mOriginalList, (a, b) -> a.loadLabel(pm).toString().toLowerCase()
|
||||||
|
.compareTo(b.loadLabel(pm).toString().toLowerCase()));
|
||||||
|
mHideList = Shell.su("magiskhide --ls");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void v) {
|
||||||
|
magiskHideDone.publish(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,8 +12,8 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.module.Module;
|
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||||
import com.topjohnwu.magisk.utils.Async;
|
import com.topjohnwu.magisk.container.Module;
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -32,7 +32,6 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
|
|||||||
@Override
|
@Override
|
||||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_module, parent, false);
|
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_module, parent, false);
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
return new ViewHolder(view);
|
return new ViewHolder(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,52 +40,43 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
|
|||||||
Context context = holder.itemView.getContext();
|
Context context = holder.itemView.getContext();
|
||||||
final Module module = mList.get(position);
|
final Module module = mList.get(position);
|
||||||
|
|
||||||
holder.title.setText(module.getName());
|
String version = module.getVersion();
|
||||||
holder.versionName.setText(module.getVersion());
|
|
||||||
String author = module.getAuthor();
|
String author = module.getAuthor();
|
||||||
holder.author.setText(TextUtils.isEmpty(author) ? null : context.getString(R.string.author, author));
|
String description = module.getDescription();
|
||||||
holder.description.setText(module.getDescription());
|
String noInfo = context.getString(R.string.no_info_provided);
|
||||||
|
|
||||||
|
holder.title.setText(module.getName());
|
||||||
|
holder.versionName.setText( TextUtils.isEmpty(version) ? noInfo : version);
|
||||||
|
holder.author.setText( TextUtils.isEmpty(author) ? noInfo : context.getString(R.string.author, author));
|
||||||
|
holder.description.setText( TextUtils.isEmpty(description) ? noInfo : description);
|
||||||
|
|
||||||
holder.checkBox.setOnCheckedChangeListener(null);
|
holder.checkBox.setOnCheckedChangeListener(null);
|
||||||
holder.checkBox.setChecked(module.isEnabled());
|
holder.checkBox.setChecked(module.isEnabled());
|
||||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> new Async.RootTask<Void, Void, Void>() {
|
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||||
@Override
|
int snack;
|
||||||
protected Void doInBackground(Void... voids) {
|
if (isChecked) {
|
||||||
if (isChecked) {
|
module.removeDisableFile();
|
||||||
module.removeDisableFile();
|
snack = R.string.disable_file_removed;
|
||||||
} else {
|
} else {
|
||||||
module.createDisableFile();
|
module.createDisableFile();
|
||||||
}
|
snack = R.string.disable_file_created;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
|
||||||
|
});
|
||||||
|
|
||||||
@Override
|
holder.delete.setOnClickListener(v -> {
|
||||||
protected void onPostExecute(Void v) {
|
boolean removed = module.willBeRemoved();
|
||||||
int title = isChecked ? R.string.disable_file_removed : R.string.disable_file_created;
|
int snack;
|
||||||
Snackbar.make(holder.title, title, Snackbar.LENGTH_SHORT).show();
|
if (removed) {
|
||||||
|
module.deleteRemoveFile();
|
||||||
|
snack = R.string.remove_file_deleted;
|
||||||
|
} else {
|
||||||
|
module.createRemoveFile();
|
||||||
|
snack = R.string.remove_file_created;
|
||||||
}
|
}
|
||||||
}.exec());
|
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
|
||||||
|
updateDeleteButton(holder, module);
|
||||||
holder.delete.setOnClickListener(v -> new Async.RootTask<Void, Void, Void>() {
|
});
|
||||||
private final boolean removed = module.willBeRemoved();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... voids) {
|
|
||||||
if (removed) {
|
|
||||||
module.deleteRemoveFile();
|
|
||||||
} else {
|
|
||||||
module.createRemoveFile();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
int title = removed ? R.string.remove_file_deleted : R.string.remove_file_created;
|
|
||||||
Snackbar.make(holder.title, title, Snackbar.LENGTH_SHORT).show();
|
|
||||||
updateDeleteButton(holder, module);
|
|
||||||
}
|
|
||||||
}.exec());
|
|
||||||
|
|
||||||
if (module.isUpdated()) {
|
if (module.isUpdated()) {
|
||||||
holder.notice.setVisibility(View.VISIBLE);
|
holder.notice.setVisibility(View.VISIBLE);
|
||||||
150
src/main/java/com/topjohnwu/magisk/adapters/PolicyAdapter.java
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.Switch;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.R;
|
||||||
|
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||||
|
import com.topjohnwu.magisk.components.ExpandableView;
|
||||||
|
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||||
|
import com.topjohnwu.magisk.container.Policy;
|
||||||
|
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
|
public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder> {
|
||||||
|
|
||||||
|
private List<Policy> policyList;
|
||||||
|
private SuDatabaseHelper dbHelper;
|
||||||
|
private PackageManager pm;
|
||||||
|
private Set<Policy> expandList = new HashSet<>();
|
||||||
|
|
||||||
|
public PolicyAdapter(List<Policy> list, SuDatabaseHelper db, PackageManager pm) {
|
||||||
|
policyList = list;
|
||||||
|
dbHelper = db;
|
||||||
|
this.pm = pm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_policy, parent, false);
|
||||||
|
return new ViewHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||||
|
Policy policy = policyList.get(position);
|
||||||
|
|
||||||
|
holder.setExpanded(expandList.contains(policy));
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener(view -> {
|
||||||
|
if (holder.isExpanded()) {
|
||||||
|
holder.collapse();
|
||||||
|
expandList.remove(policy);
|
||||||
|
} else {
|
||||||
|
holder.expand();
|
||||||
|
expandList.add(policy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
holder.appName.setText(policy.appName);
|
||||||
|
holder.packageName.setText(policy.packageName);
|
||||||
|
holder.appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
||||||
|
holder.masterSwitch.setOnCheckedChangeListener((v, isChecked) -> {
|
||||||
|
if ((isChecked && policy.policy == Policy.DENY) ||
|
||||||
|
(!isChecked && policy.policy == Policy.ALLOW)) {
|
||||||
|
policy.policy = isChecked ? Policy.ALLOW : Policy.DENY;
|
||||||
|
String message = v.getContext().getString(
|
||||||
|
isChecked ? R.string.su_snack_grant : R.string.su_snack_deny, policy.appName);
|
||||||
|
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
||||||
|
dbHelper.updatePolicy(policy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
holder.notificationSwitch.setOnCheckedChangeListener((v, isChecked) -> {
|
||||||
|
if ((isChecked && !policy.notification) ||
|
||||||
|
(!isChecked && policy.notification)) {
|
||||||
|
policy.notification = isChecked;
|
||||||
|
String message = v.getContext().getString(
|
||||||
|
isChecked ? R.string.su_snack_notif_on : R.string.su_snack_notif_off, policy.appName);
|
||||||
|
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
||||||
|
dbHelper.updatePolicy(policy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
holder.loggingSwitch.setOnCheckedChangeListener((v, isChecked) -> {
|
||||||
|
if ((isChecked && !policy.logging) ||
|
||||||
|
(!isChecked && policy.logging)) {
|
||||||
|
policy.logging = isChecked;
|
||||||
|
String message = v.getContext().getString(
|
||||||
|
isChecked ? R.string.su_snack_log_on : R.string.su_snack_log_off, policy.appName);
|
||||||
|
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
||||||
|
dbHelper.updatePolicy(policy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
holder.delete.setOnClickListener(v -> new AlertDialogBuilder((Activity) v.getContext())
|
||||||
|
.setTitle(R.string.su_revoke_title)
|
||||||
|
.setMessage(v.getContext().getString(R.string.su_revoke_msg, policy.appName))
|
||||||
|
.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||||
|
policyList.remove(position);
|
||||||
|
notifyItemRemoved(position);
|
||||||
|
notifyItemRangeChanged(position, policyList.size());
|
||||||
|
SnackbarMaker.make(holder.itemView, v.getContext().getString(R.string.su_snack_revoke, policy.appName),
|
||||||
|
Snackbar.LENGTH_SHORT).show();
|
||||||
|
dbHelper.deletePolicy(policy);
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.no_thanks, null)
|
||||||
|
.setCancelable(true)
|
||||||
|
.show());
|
||||||
|
holder.masterSwitch.setChecked(policy.policy == Policy.ALLOW);
|
||||||
|
holder.notificationSwitch.setChecked(policy.notification);
|
||||||
|
holder.loggingSwitch.setChecked(policy.logging);
|
||||||
|
|
||||||
|
// Hide for now
|
||||||
|
holder.moreInfo.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return policyList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ViewHolder extends RecyclerView.ViewHolder implements ExpandableView {
|
||||||
|
|
||||||
|
@BindView(R.id.app_name) TextView appName;
|
||||||
|
@BindView(R.id.package_name) TextView packageName;
|
||||||
|
@BindView(R.id.app_icon) ImageView appIcon;
|
||||||
|
@BindView(R.id.master_switch) Switch masterSwitch;
|
||||||
|
@BindView(R.id.notification_switch) Switch notificationSwitch;
|
||||||
|
@BindView(R.id.logging_switch) Switch loggingSwitch;
|
||||||
|
@BindView(R.id.expand_layout) ViewGroup expandLayout;
|
||||||
|
|
||||||
|
@BindView(R.id.delete) ImageView delete;
|
||||||
|
@BindView(R.id.more_info) ImageView moreInfo;
|
||||||
|
|
||||||
|
private Container container = new Container();
|
||||||
|
|
||||||
|
public ViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
ButterKnife.bind(this, itemView);
|
||||||
|
container.expandLayout = expandLayout;
|
||||||
|
setupExpandable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Container getContainer() {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
192
src/main/java/com/topjohnwu/magisk/adapters/ReposAdapter.java
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.R;
|
||||||
|
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||||
|
import com.topjohnwu.magisk.asyncs.ProcessRepoZip;
|
||||||
|
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||||
|
import com.topjohnwu.magisk.container.Module;
|
||||||
|
import com.topjohnwu.magisk.container.Repo;
|
||||||
|
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
|
public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, ReposAdapter.RepoHolder> {
|
||||||
|
|
||||||
|
private static final int UPDATES = 0;
|
||||||
|
private static final int INSTALLED = 1;
|
||||||
|
private static final int OTHERS = 2;
|
||||||
|
|
||||||
|
private Cursor repoCursor = null;
|
||||||
|
private Map<String, Module> moduleMap;
|
||||||
|
private RepoDatabaseHelper repoDB;
|
||||||
|
private List<Pair<Integer, List<Repo>>> repoPairs;
|
||||||
|
|
||||||
|
public ReposAdapter(RepoDatabaseHelper db, Map<String, Module> map) {
|
||||||
|
repoDB = db;
|
||||||
|
moduleMap = map;
|
||||||
|
repoPairs = new ArrayList<>();
|
||||||
|
notifyDBChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSectionCount() {
|
||||||
|
return repoPairs.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount(int section) {
|
||||||
|
return repoPairs.get(section).second.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SectionHolder onCreateSectionViewHolder(ViewGroup parent) {
|
||||||
|
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.section, parent, false);
|
||||||
|
return new SectionHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepoHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_repo, parent, false);
|
||||||
|
return new RepoHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindSectionViewHolder(SectionHolder holder, int section) {
|
||||||
|
switch (repoPairs.get(section).first) {
|
||||||
|
case UPDATES:
|
||||||
|
holder.sectionText.setText(R.string.update_available);
|
||||||
|
break;
|
||||||
|
case INSTALLED:
|
||||||
|
holder.sectionText.setText(R.string.installed);
|
||||||
|
break;
|
||||||
|
case OTHERS:
|
||||||
|
holder.sectionText.setText(R.string.not_installed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindItemViewHolder(RepoHolder holder, int section, int position) {
|
||||||
|
Repo repo = repoPairs.get(section).second.get(position);
|
||||||
|
Context context = holder.itemView.getContext();
|
||||||
|
|
||||||
|
holder.title.setText(repo.getName());
|
||||||
|
holder.versionName.setText(repo.getVersion());
|
||||||
|
String author = repo.getAuthor();
|
||||||
|
holder.author.setText(TextUtils.isEmpty(author) ? null : context.getString(R.string.author, author));
|
||||||
|
holder.description.setText(repo.getDescription());
|
||||||
|
holder.updateTime.setText(context.getString(R.string.updated_on, repo.getLastUpdateString()));
|
||||||
|
|
||||||
|
holder.infoLayout.setOnClickListener(v ->
|
||||||
|
new MarkDownWindow((Activity) context, null, repo.getDetailUrl()).exec());
|
||||||
|
|
||||||
|
holder.downloadImage.setOnClickListener(v -> {
|
||||||
|
String filename = repo.getName() + "-" + repo.getVersion() + ".zip";
|
||||||
|
new AlertDialogBuilder((Activity) context)
|
||||||
|
.setTitle(context.getString(R.string.repo_install_title, repo.getName()))
|
||||||
|
.setMessage(context.getString(R.string.repo_install_msg, filename))
|
||||||
|
.setCancelable(true)
|
||||||
|
.setPositiveButton(R.string.install, (d, i) ->
|
||||||
|
new ProcessRepoZip((Activity) context, repo.getZipUrl(),
|
||||||
|
Utils.getLegalFilename(filename), true).exec()
|
||||||
|
)
|
||||||
|
.setNeutralButton(R.string.download, (d, i) ->
|
||||||
|
new ProcessRepoZip((Activity) context, repo.getZipUrl(),
|
||||||
|
Utils.getLegalFilename(filename), false).exec())
|
||||||
|
.setNegativeButton(R.string.no_thanks, null)
|
||||||
|
.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyDBChanged() {
|
||||||
|
if (repoCursor != null)
|
||||||
|
repoCursor.close();
|
||||||
|
repoCursor = repoDB.getRepoCursor();
|
||||||
|
filter("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void filter(String s) {
|
||||||
|
List<Repo> updates = new ArrayList<>();
|
||||||
|
List<Repo> installed = new ArrayList<>();
|
||||||
|
List<Repo> others = new ArrayList<>();
|
||||||
|
|
||||||
|
repoPairs.clear();
|
||||||
|
while (repoCursor.moveToNext()) {
|
||||||
|
Repo repo = new Repo(repoCursor);
|
||||||
|
if (repo.getName().toLowerCase().contains(s.toLowerCase())
|
||||||
|
|| repo.getAuthor().toLowerCase().contains(s.toLowerCase())
|
||||||
|
|| repo.getDescription().toLowerCase().contains(s.toLowerCase())
|
||||||
|
) {
|
||||||
|
// Passed the repoFilter
|
||||||
|
Module module = moduleMap.get(repo.getId());
|
||||||
|
if (module != null) {
|
||||||
|
if (repo.getVersionCode() > module.getVersionCode()) {
|
||||||
|
// Updates
|
||||||
|
updates.add(repo);
|
||||||
|
} else {
|
||||||
|
installed.add(repo);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
others.add(repo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repoCursor.moveToFirst();
|
||||||
|
|
||||||
|
if (!updates.isEmpty())
|
||||||
|
repoPairs.add(new Pair<>(UPDATES, updates));
|
||||||
|
if (!installed.isEmpty())
|
||||||
|
repoPairs.add(new Pair<>(INSTALLED, installed));
|
||||||
|
if (!others.isEmpty())
|
||||||
|
repoPairs.add(new Pair<>(OTHERS, others));
|
||||||
|
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SectionHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
@BindView(R.id.section_text) TextView sectionText;
|
||||||
|
|
||||||
|
SectionHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
ButterKnife.bind(this, itemView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class RepoHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
@BindView(R.id.title) TextView title;
|
||||||
|
@BindView(R.id.version_name) TextView versionName;
|
||||||
|
@BindView(R.id.description) TextView description;
|
||||||
|
@BindView(R.id.author) TextView author;
|
||||||
|
@BindView(R.id.info_layout) LinearLayout infoLayout;
|
||||||
|
@BindView(R.id.download) ImageView downloadImage;
|
||||||
|
@BindView(R.id.update_time) TextView updateTime;
|
||||||
|
|
||||||
|
RepoHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
ButterKnife.bind(this, itemView);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
public abstract class SectionedAdapter<S extends RecyclerView.ViewHolder, C extends RecyclerView.ViewHolder>
|
||||||
|
extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
|
|
||||||
|
private static final int SECTION_TYPE = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
final public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
if (viewType == SECTION_TYPE)
|
||||||
|
return onCreateSectionViewHolder(parent);
|
||||||
|
return onCreateItemViewHolder(parent, viewType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||||
|
PositionInfo info = getPositionInfo(position);
|
||||||
|
if (info.position == -1)
|
||||||
|
onBindSectionViewHolder((S) holder, info.section);
|
||||||
|
else
|
||||||
|
onBindItemViewHolder((C) holder, info.section, info.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
final public int getItemCount() {
|
||||||
|
int size, sec;
|
||||||
|
size = sec = getSectionCount();
|
||||||
|
for (int i = 0; i < sec; ++i){
|
||||||
|
size += getItemCount(i);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
final public int getItemViewType(int position) {
|
||||||
|
PositionInfo info = getPositionInfo(position);
|
||||||
|
if (info.position == -1)
|
||||||
|
return SECTION_TYPE;
|
||||||
|
else
|
||||||
|
return getItemViewType(info.section, info.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getItemViewType(int section, int position) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getSectionPosition(int section) {
|
||||||
|
return getItemPosition(section, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getItemPosition(int section, int position) {
|
||||||
|
int realPosition = 0;
|
||||||
|
// Previous sections
|
||||||
|
for (int i = 0; i < section; ++i) {
|
||||||
|
realPosition += getItemCount(i) + 1;
|
||||||
|
}
|
||||||
|
// Current section
|
||||||
|
realPosition += position + 1;
|
||||||
|
return realPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PositionInfo getPositionInfo(int position) {
|
||||||
|
int section = 0;
|
||||||
|
while (true) {
|
||||||
|
if (position == 0)
|
||||||
|
return new PositionInfo(section, -1);
|
||||||
|
position -= 1;
|
||||||
|
if (position < getItemCount(section))
|
||||||
|
return new PositionInfo(section, position);
|
||||||
|
position -= getItemCount(section++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PositionInfo {
|
||||||
|
int section;
|
||||||
|
int position;
|
||||||
|
PositionInfo(int section, int position) {
|
||||||
|
this.section = section;
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract int getSectionCount();
|
||||||
|
public abstract int getItemCount(int section);
|
||||||
|
public abstract S onCreateSectionViewHolder(ViewGroup parent);
|
||||||
|
public abstract C onCreateItemViewHolder(ViewGroup parent, int viewType);
|
||||||
|
public abstract void onBindSectionViewHolder(S holder, int section);
|
||||||
|
public abstract void onBindItemViewHolder(C holder, int section, int position);
|
||||||
|
}
|
||||||
155
src/main/java/com/topjohnwu/magisk/adapters/SuLogAdapter.java
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.animation.Animation;
|
||||||
|
import android.view.animation.RotateAnimation;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.R;
|
||||||
|
import com.topjohnwu.magisk.components.ExpandableView;
|
||||||
|
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||||
|
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
|
public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, SuLogAdapter.LogViewHolder> {
|
||||||
|
|
||||||
|
private List<List<Integer>> logEntryList;
|
||||||
|
private Set<Integer> itemExpanded, sectionExpanded;
|
||||||
|
private SuDatabaseHelper suDB;
|
||||||
|
private Cursor suLogCursor = null;
|
||||||
|
|
||||||
|
public SuLogAdapter(SuDatabaseHelper db) {
|
||||||
|
suDB = db;
|
||||||
|
logEntryList = Collections.emptyList();
|
||||||
|
sectionExpanded = new HashSet<>();
|
||||||
|
itemExpanded = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSectionCount() {
|
||||||
|
return logEntryList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount(int section) {
|
||||||
|
return sectionExpanded.contains(section) ? logEntryList.get(section).size() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SectionHolder onCreateSectionViewHolder(ViewGroup parent) {
|
||||||
|
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog_group, parent, false);
|
||||||
|
return new SectionHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LogViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog, parent, false);
|
||||||
|
return new LogViewHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindSectionViewHolder(SectionHolder holder, int section) {
|
||||||
|
suLogCursor.moveToPosition(logEntryList.get(section).get(0));
|
||||||
|
SuLogEntry entry = new SuLogEntry(suLogCursor);
|
||||||
|
holder.arrow.setRotation(sectionExpanded.contains(section) ? 180 : 0);
|
||||||
|
holder.itemView.setOnClickListener(v -> {
|
||||||
|
RotateAnimation rotate;
|
||||||
|
if (sectionExpanded.contains(section)) {
|
||||||
|
holder.arrow.setRotation(0);
|
||||||
|
rotate = new RotateAnimation(180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
||||||
|
sectionExpanded.remove(section);
|
||||||
|
notifyItemRangeRemoved(getItemPosition(section, 0), logEntryList.get(section).size());
|
||||||
|
} else {
|
||||||
|
rotate = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
||||||
|
sectionExpanded.add(section);
|
||||||
|
notifyItemRangeInserted(getItemPosition(section, 0), logEntryList.get(section).size());
|
||||||
|
}
|
||||||
|
rotate.setDuration(300);
|
||||||
|
rotate.setFillAfter(true);
|
||||||
|
holder.arrow.setAnimation(rotate);
|
||||||
|
});
|
||||||
|
holder.date.setText(entry.getDateString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindItemViewHolder(LogViewHolder holder, int section, int position) {
|
||||||
|
int sqlPosition = logEntryList.get(section).get(position);
|
||||||
|
suLogCursor.moveToPosition(sqlPosition);
|
||||||
|
SuLogEntry entry = new SuLogEntry(suLogCursor);
|
||||||
|
holder.setExpanded(itemExpanded.contains(sqlPosition));
|
||||||
|
holder.itemView.setOnClickListener(view -> {
|
||||||
|
if (holder.isExpanded()) {
|
||||||
|
holder.collapse();
|
||||||
|
itemExpanded.remove(sqlPosition);
|
||||||
|
} else {
|
||||||
|
holder.expand();
|
||||||
|
itemExpanded.add(sqlPosition);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
holder.appName.setText(entry.appName);
|
||||||
|
holder.action.setText(entry.action ? R.string.grant : R.string.deny);
|
||||||
|
holder.command.setText(entry.command);
|
||||||
|
holder.fromPid.setText(String.valueOf(entry.fromPid));
|
||||||
|
holder.toUid.setText(String.valueOf(entry.toUid));
|
||||||
|
holder.time.setText(entry.getTimeString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyDBChanged() {
|
||||||
|
if (suLogCursor != null)
|
||||||
|
suLogCursor.close();
|
||||||
|
suLogCursor = suDB.getLogCursor();
|
||||||
|
logEntryList = suDB.getLogStructure();
|
||||||
|
itemExpanded.clear();
|
||||||
|
sectionExpanded.clear();
|
||||||
|
sectionExpanded.add(0);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SectionHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
@BindView(R.id.date) TextView date;
|
||||||
|
@BindView(R.id.arrow) ImageView arrow;
|
||||||
|
|
||||||
|
SectionHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
ButterKnife.bind(this, itemView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class LogViewHolder extends RecyclerView.ViewHolder implements ExpandableView {
|
||||||
|
|
||||||
|
@BindView(R.id.app_name) TextView appName;
|
||||||
|
@BindView(R.id.action) TextView action;
|
||||||
|
@BindView(R.id.time) TextView time;
|
||||||
|
@BindView(R.id.fromPid) TextView fromPid;
|
||||||
|
@BindView(R.id.toUid) TextView toUid;
|
||||||
|
@BindView(R.id.command) TextView command;
|
||||||
|
@BindView(R.id.expand_layout) ViewGroup expandLayout;
|
||||||
|
|
||||||
|
private Container container = new Container();
|
||||||
|
|
||||||
|
LogViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
ButterKnife.bind(this, itemView);
|
||||||
|
container.expandLayout = expandLayout;
|
||||||
|
setupExpandable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Container getContainer() {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentPagerAdapter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class TabFragmentAdapter extends FragmentPagerAdapter {
|
||||||
|
|
||||||
|
private List<Fragment> fragmentList;
|
||||||
|
private List<String> titleList;
|
||||||
|
|
||||||
|
public TabFragmentAdapter(FragmentManager fm) {
|
||||||
|
super(fm);
|
||||||
|
fragmentList = new ArrayList<>();
|
||||||
|
titleList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
return fragmentList.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return fragmentList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getPageTitle(int position) {
|
||||||
|
return titleList.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTab(Fragment fragment, String title) {
|
||||||
|
fragmentList.add(fragment);
|
||||||
|
titleList.add(title);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
import com.topjohnwu.magisk.utils.Const;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
import com.topjohnwu.magisk.utils.WebService;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
|
||||||
|
import dalvik.system.DexClassLoader;
|
||||||
|
|
||||||
|
public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||||
|
|
||||||
|
public static final File dexPath =
|
||||||
|
new File(MagiskManager.get().getFilesDir().getParent() + "/snet", "snet.apk");
|
||||||
|
private DexClassLoader loader;
|
||||||
|
private Class<?> helperClazz, callbackClazz;
|
||||||
|
|
||||||
|
public CheckSafetyNet(Activity activity) {
|
||||||
|
super(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dlSnet() throws IOException {
|
||||||
|
Shell.sh("rm -rf " + dexPath.getParent());
|
||||||
|
HttpURLConnection conn = WebService.request(Const.Url.SNET_URL, null);
|
||||||
|
dexPath.getParentFile().mkdir();
|
||||||
|
try (
|
||||||
|
OutputStream out = new BufferedOutputStream(new FileOutputStream(dexPath));
|
||||||
|
InputStream in = new BufferedInputStream(conn.getInputStream())) {
|
||||||
|
Utils.inToOut(in, out);
|
||||||
|
}
|
||||||
|
conn.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadClasses() throws ClassNotFoundException {
|
||||||
|
loader = new DexClassLoader(dexPath.toString(), dexPath.getParent(),
|
||||||
|
null, ClassLoader.getSystemClassLoader());
|
||||||
|
helperClazz = loader.loadClass(Const.SNET_PKG + ".SafetyNetHelper");
|
||||||
|
callbackClazz = loader.loadClass(Const.SNET_PKG + ".SafetyNetCallback");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Exception doInBackground(Void... voids) {
|
||||||
|
int snet_ver = -1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!dexPath.exists())
|
||||||
|
dlSnet();
|
||||||
|
loadClasses();
|
||||||
|
|
||||||
|
try {
|
||||||
|
snet_ver = (int) helperClazz.getMethod("getVersion").invoke(null);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snet_ver != Const.SNET_VER) {
|
||||||
|
dlSnet();
|
||||||
|
loadClasses();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Exception err) {
|
||||||
|
MagiskManager mm = MagiskManager.get();
|
||||||
|
try {
|
||||||
|
if (err != null) throw err;
|
||||||
|
Object helper = helperClazz.getConstructors()[0].newInstance(
|
||||||
|
getActivity(), dexPath.getPath(), Proxy.newProxyInstance(
|
||||||
|
loader, new Class[] { callbackClazz }, (proxy, method, args) -> {
|
||||||
|
mm.safetyNetDone.publish(false, args[0]);
|
||||||
|
return null;
|
||||||
|
}));
|
||||||
|
helperClazz.getMethod("attest").invoke(helper);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
mm.safetyNetDone.publish(false, -1);
|
||||||
|
}
|
||||||
|
super.onPostExecute(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/main/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.BuildConfig;
|
||||||
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
import com.topjohnwu.magisk.utils.Const;
|
||||||
|
import com.topjohnwu.magisk.utils.ShowUI;
|
||||||
|
import com.topjohnwu.magisk.utils.WebService;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
public class CheckUpdates extends ParallelTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
private boolean showNotification;
|
||||||
|
|
||||||
|
public CheckUpdates() {
|
||||||
|
this(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CheckUpdates(boolean b) {
|
||||||
|
showNotification = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
MagiskManager mm = MagiskManager.get();
|
||||||
|
String jsonStr = "";
|
||||||
|
switch (mm.updateChannel) {
|
||||||
|
case Const.Value.STABLE_CHANNEL:
|
||||||
|
jsonStr = WebService.getString(Const.Url.STABLE_URL);
|
||||||
|
break;
|
||||||
|
case Const.Value.BETA_CHANNEL:
|
||||||
|
jsonStr = WebService.getString(Const.Url.BETA_URL);
|
||||||
|
break;
|
||||||
|
case Const.Value.CUSTOM_CHANNEL:
|
||||||
|
jsonStr = WebService.getString(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject(jsonStr);
|
||||||
|
JSONObject magisk = json.getJSONObject("magisk");
|
||||||
|
mm.remoteMagiskVersionString = magisk.getString("version");
|
||||||
|
mm.remoteMagiskVersionCode = magisk.getInt("versionCode");
|
||||||
|
mm.magiskLink = magisk.getString("link");
|
||||||
|
mm.releaseNoteLink = magisk.getString("note");
|
||||||
|
JSONObject manager = json.getJSONObject("app");
|
||||||
|
mm.remoteManagerVersionString = manager.getString("version");
|
||||||
|
mm.remoteManagerVersionCode = manager.getInt("versionCode");
|
||||||
|
mm.managerLink = manager.getString("link");
|
||||||
|
} catch (JSONException ignored) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void v) {
|
||||||
|
MagiskManager mm = MagiskManager.get();
|
||||||
|
if (showNotification && mm.prefs.getBoolean(Const.Key.UPDATE_NOTIFICATION, true)) {
|
||||||
|
if (BuildConfig.VERSION_CODE < mm.remoteManagerVersionCode) {
|
||||||
|
ShowUI.managerUpdateNotification();
|
||||||
|
} else if (mm.magiskVersionCode < mm.remoteMagiskVersionCode) {
|
||||||
|
ShowUI.magiskUpdateNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mm.updateCheckDone.publish();
|
||||||
|
super.onPostExecute(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
108
src/main/java/com/topjohnwu/magisk/asyncs/FlashZip.java
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.FlashActivity;
|
||||||
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
import com.topjohnwu.magisk.utils.Const;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||||
|
|
||||||
|
private Uri mUri;
|
||||||
|
private File mCachedFile;
|
||||||
|
private List<String> console, logs;
|
||||||
|
|
||||||
|
public FlashZip(Activity context, Uri uri, List<String> console, List<String> logs) {
|
||||||
|
super(context);
|
||||||
|
mUri = uri;
|
||||||
|
this.console = console;
|
||||||
|
this.logs = logs;
|
||||||
|
mCachedFile = new File(context.getCacheDir(), "install.zip");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean unzipAndCheck() throws Exception {
|
||||||
|
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android", true);
|
||||||
|
List<String> ret = Utils.readFile(new File(mCachedFile.getParentFile(), "updater-script"));
|
||||||
|
return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Integer doInBackground(Void... voids) {
|
||||||
|
MagiskManager mm = MagiskManager.get();
|
||||||
|
try {
|
||||||
|
console.add("- Copying zip to temp directory");
|
||||||
|
|
||||||
|
mCachedFile.delete();
|
||||||
|
try (
|
||||||
|
InputStream in = mm.getContentResolver().openInputStream(mUri);
|
||||||
|
OutputStream out = new BufferedOutputStream(new FileOutputStream(mCachedFile))
|
||||||
|
) {
|
||||||
|
if (in == null) throw new FileNotFoundException();
|
||||||
|
InputStream buf= new BufferedInputStream(in);
|
||||||
|
Utils.inToOut(buf, out);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
console.add("! Invalid Uri");
|
||||||
|
throw e;
|
||||||
|
} catch (IOException e) {
|
||||||
|
console.add("! Cannot copy to cache");
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
if (!unzipAndCheck()) return 0;
|
||||||
|
console.add("- Installing " + Utils.getNameFromUri(mm, mUri));
|
||||||
|
Shell.getShell().run(console, logs,
|
||||||
|
"cd " + mCachedFile.getParent(),
|
||||||
|
"BOOTMODE=true sh update-binary dummy 1 " + mCachedFile + " || echo 'Failed!'"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (TextUtils.equals(console.get(console.size() - 1), "Failed!"))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
console.add("- All done!");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -1 = error, manual install; 0 = invalid zip; 1 = success
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Integer result) {
|
||||||
|
FlashActivity activity = (FlashActivity) getActivity();
|
||||||
|
Shell.su_raw(
|
||||||
|
"rm -rf " + mCachedFile.getParent(),
|
||||||
|
"rm -rf " + Const.TMP_FOLDER_PATH
|
||||||
|
);
|
||||||
|
switch (result) {
|
||||||
|
case -1:
|
||||||
|
console.add("! Installation failed");
|
||||||
|
Utils.showUriSnack(getActivity(), mUri);
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
console.add("! This zip is not a Magisk Module!");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
// Success
|
||||||
|
new LoadModules().exec();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
activity.reboot.setVisibility(result > 0 ? View.VISIBLE : View.GONE);
|
||||||
|
activity.buttonPanel.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||