Compare commits
782 Commits
manager-v3
...
manager-v5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b885ccbd63 | ||
|
|
da6f1d0f12 | ||
|
|
4c0d435b6b | ||
|
|
0e717a2de4 | ||
|
|
cada862214 | ||
|
|
682c6d4e7b | ||
|
|
d0a253c97e | ||
|
|
c0e2b3027b | ||
|
|
e7dc14b07d | ||
|
|
0da9146e90 | ||
|
|
ad05a33e02 | ||
|
|
8224e038a3 | ||
|
|
03c04c2141 | ||
|
|
2e091b04e5 | ||
|
|
60296493fe | ||
|
|
20c20f8f9b | ||
|
|
f1d642a4e5 | ||
|
|
e0e5ea17a4 | ||
|
|
91a0ba72dc | ||
|
|
c54c5a974a | ||
|
|
532b8c54ab | ||
|
|
5ac87891b5 | ||
|
|
2d905ce3fb | ||
|
|
831112abd2 | ||
|
|
153d0f5505 | ||
|
|
c78896a335 | ||
|
|
316ec98e0f | ||
|
|
cf58545a45 | ||
|
|
e86015badc | ||
|
|
c8f65fc9a1 | ||
|
|
7684602ea8 | ||
|
|
4601989d4a | ||
|
|
23f697d62b | ||
|
|
4ff39f8817 | ||
|
|
1df41003ec | ||
|
|
1f39ee41ad | ||
|
|
42d8b1ecb9 | ||
|
|
a4da7b33e6 | ||
|
|
e4ee9e9095 | ||
|
|
77430a282f | ||
|
|
e6c1dd532d | ||
|
|
d1f301e059 | ||
|
|
1e812c40ce | ||
|
|
a949641342 | ||
|
|
c231e88a5d | ||
|
|
79c71509f6 | ||
|
|
5dab580cfc | ||
|
|
499a157946 | ||
|
|
c5a7ab2415 | ||
|
|
3dd5a6f378 | ||
|
|
7be26a0677 | ||
|
|
c183fdd3ca | ||
|
|
baa439457e | ||
|
|
4dbcd54b72 | ||
|
|
11062f2d4f | ||
|
|
a0466085fe | ||
|
|
f2f7d77847 | ||
|
|
b2105f2d88 | ||
|
|
4126f3bdcb | ||
|
|
74ccfe6088 | ||
|
|
48085b5573 | ||
|
|
7b9ddc9b3b | ||
|
|
15726a759c | ||
|
|
2c7474ea87 | ||
|
|
c726aee643 | ||
|
|
c3e94e1480 | ||
|
|
5f1343e5b4 | ||
|
|
ffb1303d61 | ||
|
|
a0b0d938f0 | ||
|
|
158f5ba7d9 | ||
|
|
b8cf40161c | ||
|
|
fb96e6a56f | ||
|
|
6668ba2511 | ||
|
|
4668ef3020 | ||
|
|
630f2b7d19 | ||
|
|
dde0a4a7c8 | ||
|
|
b06f69573d | ||
|
|
8fd03f7434 | ||
|
|
90e4ac2d23 | ||
|
|
956bceae75 | ||
|
|
c663be86de | ||
|
|
aca78baecf | ||
|
|
fbcf6b7954 | ||
|
|
84123222aa | ||
|
|
e9dbcf693d | ||
|
|
1cd0a9d48f | ||
|
|
1b48e44914 | ||
|
|
0a398f03fd | ||
|
|
15ed3e52f2 | ||
|
|
8990919dab | ||
|
|
e5638e4b15 | ||
|
|
404c6fac9a | ||
|
|
267395bfa2 | ||
|
|
920fc5ae99 | ||
|
|
92ed0ae51b | ||
|
|
6764a98409 | ||
|
|
fd7b5f393a | ||
|
|
2ca528f93f | ||
|
|
ce2e6b7d35 | ||
|
|
684c5d225a | ||
|
|
b75018b03b | ||
|
|
41499d4b3c | ||
|
|
383c97c303 | ||
|
|
74b54ef371 | ||
|
|
bbf7b4db79 | ||
|
|
c61f0acab5 | ||
|
|
398af123b2 | ||
|
|
315fa9d7d3 | ||
|
|
fb5e8ef40c | ||
|
|
e79d764148 | ||
|
|
ebbee0dc43 | ||
|
|
ed0c16e201 | ||
|
|
209fdf349a | ||
|
|
f49f2afacd | ||
|
|
8c6330a3c4 | ||
|
|
337b777125 | ||
|
|
1b756e8d96 | ||
|
|
52d478df1a | ||
|
|
0c782edf21 | ||
|
|
e3948d295e | ||
|
|
5f2c742a5c | ||
|
|
b30c77aab9 | ||
|
|
a5916b9c49 | ||
|
|
453180e30b | ||
|
|
8bd432d391 | ||
|
|
c9d3e20aef | ||
|
|
d5408d1f09 | ||
|
|
f334532aba | ||
|
|
be77c09f3d | ||
|
|
7de6a92753 | ||
|
|
36f76f5a14 | ||
|
|
b84523d557 | ||
|
|
2c78c415e9 | ||
|
|
79ccb30dd2 | ||
|
|
3c566becf6 | ||
|
|
151ca593af | ||
|
|
4132eacba0 | ||
|
|
06e6151816 | ||
|
|
70277d4edd | ||
|
|
d21d2f1a9c | ||
|
|
74a7be996f | ||
|
|
3f38579529 | ||
|
|
4d5a9f6e15 | ||
|
|
41f47acd76 | ||
|
|
821dcaa7c7 | ||
|
|
7135d26419 | ||
|
|
f7fd354dce | ||
|
|
0c69a65bc4 | ||
|
|
2f2ca5eab4 | ||
|
|
df9c40c035 | ||
|
|
25b67017e4 | ||
|
|
bc9c3346f3 | ||
|
|
1db7e19fe8 | ||
|
|
102c03ce2b | ||
|
|
ec19eb4455 | ||
|
|
6d9924d50e | ||
|
|
16c4d74274 | ||
|
|
e4af5fd36a | ||
|
|
702775493a | ||
|
|
b2ae826066 | ||
|
|
cc3e9990fa | ||
|
|
271cbddd5e | ||
|
|
c1423ca9ad | ||
|
|
74379150a1 | ||
|
|
c840a30c30 | ||
|
|
ae5277a898 | ||
|
|
bffa837825 | ||
|
|
b9e7d0faea | ||
|
|
860b08d9ed | ||
|
|
691dc1d49e | ||
|
|
9d6886d367 | ||
|
|
9589b68f5a | ||
|
|
28d88af1af | ||
|
|
8b5acd1849 | ||
|
|
33dc63a7fd | ||
|
|
d0a86385b7 | ||
|
|
50a49e2c8c | ||
|
|
c60adb113e | ||
|
|
aee015e8f6 | ||
|
|
bf6af29205 | ||
|
|
329905d472 | ||
|
|
00d450d262 | ||
|
|
2365d1bd20 | ||
|
|
5b385c18e5 | ||
|
|
98c0434ec0 | ||
|
|
f318d0a3bc | ||
|
|
27f5b410c0 | ||
|
|
3f55be9676 | ||
|
|
b05d2d3a2d | ||
|
|
19af5f9e0b | ||
|
|
f37f330670 | ||
|
|
40082d4571 | ||
|
|
00d655f346 | ||
|
|
821726e7c0 | ||
|
|
759e905c3c | ||
|
|
8bf7e42913 | ||
|
|
0dcd073554 | ||
|
|
2fe35d578d | ||
|
|
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
|
||||
.idea/
|
||||
/build
|
||||
app/app-release.apk
|
||||
app/release
|
||||
*.hprof
|
||||
app/.externalNativeBuild/
|
||||
.externalNativeBuild/
|
||||
src/full/res/raw/util_functions.sh
|
||||
public.certificate.x509.pem
|
||||
private.key.pk8
|
||||
*.apk
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# Magisk Manager
|
||||
The project should be built with Android Studio version 2.2.0+
|
||||
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
|
||||
This repo is no longer an independent component. It is a submodule of the [Magisk Project](https://github.com/topjohnwu/Magisk).
|
||||
|
||||
# Translations
|
||||
The default (English) string resources are scattered in these files: `src/full/res/values/strings.xml`, `src/main/res/values/strings.xml`, `src/stub/res/values/strings.xml`.
|
||||
Place the translated XMLs in the corresponding folder to the locale.
|
||||
Translations are highly appreciated via pull requests here on Github.
|
||||
|
||||
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,116 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.pm.PackageManager;
|
||||
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.text.TextUtils;
|
||||
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 com.topjohnwu.magisk.adapters.ApplicationAdapter;
|
||||
import com.topjohnwu.magisk.utils.Async;
|
||||
import com.topjohnwu.magisk.utils.CallbackHandler;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class MagiskHideFragment extends Fragment implements CallbackHandler.EventListener {
|
||||
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@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 SearchView.OnQueryTextListener searchListener;
|
||||
private String lastFilter;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.magisk_hide_fragment, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
|
||||
PackageManager packageManager = getActivity().getPackageManager();
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> new Async.LoadApps(packageManager).exec());
|
||||
|
||||
appAdapter = new ApplicationAdapter(packageManager);
|
||||
recyclerView.setAdapter(appAdapter);
|
||||
|
||||
searchListener = new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
lastFilter = query;
|
||||
appAdapter.filter(query);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
lastFilter = newText;
|
||||
appAdapter.filter(newText);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_magiskhide, menu);
|
||||
SearchView search = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.app_search));
|
||||
search.setOnQueryTextListener(searchListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setHasOptionsMenu(true);
|
||||
getActivity().setTitle(R.string.magiskhide);
|
||||
CallbackHandler.register(packageLoadDone, this);
|
||||
if (packageLoadDone.isTriggered) {
|
||||
onTrigger(packageLoadDone);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
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);
|
||||
if (!TextUtils.isEmpty(lastFilter)) {
|
||||
appAdapter.filter(lastFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,62 +0,0 @@
|
||||
package com.topjohnwu.magisk.module;
|
||||
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
public class Module extends BaseModule {
|
||||
|
||||
private String mRemoveFile, mDisableFile, mUpdateFile;
|
||||
private boolean mEnable, mRemove, mUpdated;
|
||||
|
||||
public Module(String path) throws CacheModException {
|
||||
|
||||
parseProps(Utils.readFile(path + "/module.prop"));
|
||||
|
||||
mRemoveFile = path + "/remove";
|
||||
mDisableFile = path + "/disable";
|
||||
mUpdateFile = path + "/update";
|
||||
|
||||
if (mId == null) {
|
||||
int sep = path.lastIndexOf('/');
|
||||
mId = path.substring(sep + 1);
|
||||
}
|
||||
|
||||
if (mName == null)
|
||||
mName = mId;
|
||||
|
||||
Logger.dev("Creating Module, id: " + mId);
|
||||
|
||||
mEnable = !Utils.itemExist(mDisableFile);
|
||||
mRemove = Utils.itemExist(mRemoveFile);
|
||||
mUpdated = Utils.itemExist(mUpdateFile);
|
||||
}
|
||||
|
||||
public void createDisableFile() {
|
||||
mEnable = !Utils.createFile(mDisableFile);
|
||||
}
|
||||
|
||||
public void removeDisableFile() {
|
||||
mEnable = Utils.removeItem(mDisableFile);
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return mEnable;
|
||||
}
|
||||
|
||||
public void createRemoveFile() {
|
||||
mRemove = Utils.createFile(mRemoveFile);
|
||||
}
|
||||
|
||||
public void deleteRemoveFile() {
|
||||
mRemove = !Utils.removeItem(mRemoveFile);
|
||||
}
|
||||
|
||||
public boolean willBeRemoved() {
|
||||
return mRemove;
|
||||
}
|
||||
|
||||
public boolean isUpdated() {
|
||||
return mUpdated;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,62 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Modified by topjohnwu, based on Chainfire's libsuperuser
|
||||
*/
|
||||
|
||||
public class StreamGobbler extends Thread {
|
||||
|
||||
private BufferedReader reader = null;
|
||||
private List<String> writer = null;
|
||||
private boolean isRoot = false;
|
||||
|
||||
/**
|
||||
* <p>StreamGobbler constructor</p>
|
||||
*
|
||||
* <p>We use this class because shell STDOUT and STDERR should be read as quickly as
|
||||
* possible to prevent a deadlock from occurring, or Process.waitFor() never
|
||||
* returning (as the buffer is full, pausing the native process)</p>
|
||||
*
|
||||
* @param inputStream InputStream to read from
|
||||
* @param outputList {@literal List<String>} to write to, or null
|
||||
*/
|
||||
public StreamGobbler(InputStream inputStream, List<String> outputList) {
|
||||
reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
writer = outputList;
|
||||
}
|
||||
|
||||
public StreamGobbler(InputStream inputStream, List<String> outputList, boolean root) {
|
||||
reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
writer = outputList;
|
||||
isRoot = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// keep reading the InputStream until it ends (or an error occurs)
|
||||
try {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
writer.add(line);
|
||||
if (!line.equals("-root-done-") && !line.isEmpty()) {
|
||||
Logger.shell(isRoot, "OUT: " + line);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// reader probably closed, expected exit condition
|
||||
}
|
||||
|
||||
// make sure our stream is closed and resources will be freed
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
// read already closed
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 32 KiB |
@@ -1,4 +0,0 @@
|
||||
<vector android:height="24dp" android:viewportHeight="400.0"
|
||||
android:viewportWidth="400.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillAlpha="1.00" android:fillColor="#000000" android:pathData="M200.6,24.2C231.8,24.2 263,33 289.6,49.5C304.7,58.8 318.2,70.7 329.8,84.1C351.5,109.6 365.2,141.7 368.8,175C373.6,217.4 361.5,261.5 335.5,295.5C334.2,297.1 332.8,298.7 331.9,300.7C341.7,310.1 351,320 360.9,329.3C322.7,339.3 284.5,349.6 246.3,359.9C256.4,323.5 266,287 275.7,250.5C262.4,250.5 249.1,250.5 235.8,250.5C228.5,269.8 221.2,289.1 214.1,308.4C205.9,308.6 197.8,308.5 189.6,308.5C188.4,306.7 187.1,304.9 185.9,303C192.4,285.5 199.1,268 205.6,250.5C189.5,250.6 173.4,250.3 157.4,250.6C150.4,270 142.9,289.2 135.8,308.5C129.8,308.5 123.9,308.4 117.9,308.6C130.4,317.8 144,325.6 158.9,330.3C171.9,334.6 185.6,336.6 199.4,336.7C199.4,349.4 199.3,362.1 199.4,374.8C165.8,374.9 132.2,364.5 104.4,345.6C91.7,337 80.3,326.5 70.2,314.9C48.5,289.4 34.8,257.3 31.2,224C26.3,180.4 39.2,134.9 66.8,100.7C67.2,99.9 67.7,99.2 68.1,98.4C58.3,88.9 49,79 39.1,69.7C77.3,59.7 115.6,49.4 153.7,39.1C143.6,75.7 133.8,112.5 124.1,149.3C137.9,149.3 151.7,149.2 165.5,149.3C172.9,130 180.2,110.6 187.3,91.2C195.5,90.8 203.7,91 211.8,91C213,92.8 214.3,94.6 215.5,96.3C209.1,114 202.3,131.6 195.8,149.2C211.8,149.3 227.8,149.2 243.9,149.3C251.2,129.9 258.3,110.3 265.8,90.9C271.5,90.8 277.3,91.6 282.9,90.4C280.2,89.5 278.1,87.7 275.9,86.1C254,70.7 227.4,62.2 200.6,62.3C200.6,49.6 200.7,36.9 200.6,24.2M292.5,100C286.4,116.4 280.2,132.8 274,149.3C280.9,149.2 287.8,149.3 294.7,149.2C296,150.8 297.3,152.4 298.6,153.9C297.1,162.1 295.7,170.3 294.3,178.5C283.9,178.5 273.4,178.5 262.9,178.5C257.5,192.7 252.1,206.9 246.9,221.2C258.7,221.3 270.5,221.1 282.3,221.3C283.5,222.9 284.8,224.4 286.1,225.9C284.9,233.7 283.4,241.4 282.1,249.1C281.6,251 283.6,252.1 284.6,253.3C291.5,259.8 297.7,266.9 304.8,273.1C312.9,262.1 319.6,250.1 324.2,237.3C334.3,208.7 334.2,176.7 323.7,148.3C317,130.1 306.4,113.4 292.5,100M95.2,125.9C86.2,138 79,151.4 74.5,165.8C65.3,194.4 66.4,226.3 77.6,254.2C84.6,271.5 95.1,287.4 108.7,300.1C114.9,283.6 121.3,267.1 127.2,250.5C121.5,250.5 115.8,250.5 110,250.5C108.6,250.4 106.7,251 105.8,249.5C104.5,247.9 102.3,246.3 102.9,244.1C104.2,236.5 105.5,228.9 106.8,221.2C117.4,221.2 128.1,221.4 138.7,221.1C143.9,206.9 149.3,192.7 154.6,178.5C142.9,178.4 131.1,178.6 119.3,178.4C117.6,177.3 116.4,175.3 115,173.8C116.3,165.8 117.9,157.9 119,150C118.3,148.2 116.6,147.1 115.3,145.7C108.6,139.2 102.3,132.1 95.2,125.9M168.8,221.2C184.8,221.2 200.8,221.3 216.8,221.2C222.2,207 227.5,192.8 232.8,178.5C216.8,178.5 200.7,178.5 184.6,178.5C179.4,192.8 174,207 168.8,221.2Z"/>
|
||||
</vector>
|
||||
@@ -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,27 +0,0 @@
|
||||
<vector android:height="24dp" android:viewportHeight="2230.0"
|
||||
android:viewportWidth="2230.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M416,565"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M1014,935c-238.9,170.6 -487.1,149.8 -601,-221 -20.2,-65.8 -63.9,478 186,584 74.2,31.5 34.8,-78.8 167,-179C840.5,1062.5 1060.7,901.7 1014,935Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M982,1370c-64.6,63.2 -78.9,85.3 -162,158 -119.8,104.9 -241,-42 -223,-12 107.9,179.8 389.1,481.3 376,406 -26,-148.8 -46,-225 -28,-364C957.8,1458.7 1003.8,1348.7 982,1370Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M1252,1359c64.6,63.2 78.9,85.3 162,158 119.8,104.9 241,-42 223,-12 -107.9,179.8 -389.1,481.3 -376,406 26,-148.8 46,-225 28,-364C1276.2,1447.7 1230.2,1337.7 1252,1359Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M1073,1422"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M782,1665"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M899,1281"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M946,884c-36.8,-163.5 -422.2,-66 -524,-325 -22.7,-57.7 30.6,415.1 319,427C880.7,991.8 953.2,916.1 946,884Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M983,799C813.2,659.7 722.9,478 576,279c-27.7,-37.5 124,-128 184,-134S870.3,359.4 876,489C881.2,607.4 1005.8,817.7 983,799Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M983,953l62,70"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M445,825"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M964,812S807.2,727.8 673,691C547.6,656.6 453.5,572.2 443,483c-7.8,-66.4 97,-192 97,-192s73.5,204 170,306C815,708 775.1,693 964,812Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M1081,1055c26,10.2 14.6,-538.8 26.6,-685.8S1030,132 997,105s-96.1,-6.4 -172,19c85.9,58.9 129,129 141,225C948,570.9 1067,679 1081,1055Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M980.3,1120.8c-131.5,37.9 -182.5,220.5 -315.2,243.1 -27.4,4.7 -58.3,12.9 -94.3,11.2 -67.3,-3.2 -152.4,-41.3 -266.1,-202.6 -33.6,-47.7 83.4,307.1 377,311.5 215,3.2 240.1,-210.5 383.4,-217 51.7,-2.3 55,-101 55,-101S1058.2,1098.4 980.3,1120.8Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M1258.7,1121.8c131.5,37.9 182.5,220.5 315.2,243.1 27.4,4.7 58.3,12.9 94.3,11.2 67.3,-3.2 152.4,-41.3 266.1,-202.6 33.6,-47.7 -83.4,307.1 -377,311.5 -215,3.2 -240.1,-210.5 -383.4,-217 -51.7,-2.3 -55,-101 -55,-101S1180.8,1099.4 1258.7,1121.8Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M512,1317"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M1224,935c238.9,170.6 487.1,149.8 601,-221 20.2,-65.8 63.9,478 -186,584 -74.2,31.5 -34.8,-78.8 -167,-179C1397.5,1062.5 1177.3,901.7 1224,935Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M1223.1,1362c6.4,-8.8 -23.6,56.9 -3,245 25.1,228.8 -118.7,583 -118.7,583s-105.4,-353.5 -84.8,-576c16.5,-177.5 3,-258 3,-258S1112.6,1514.7 1223.1,1362Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M1292,884c36.8,-163.5 422.2,-66 524,-325 22.7,-57.7 -30.6,415.1 -319,427C1357.3,991.8 1284.8,916.1 1292,884Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M1255,799c169.8,-139.3 260.1,-321 407,-520 27.7,-37.5 -124,-128 -184,-134s-110.3,214.4 -116,344C1356.8,607.4 1232.2,817.7 1255,799Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M1255,953l-62,70"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M1274,812s156.8,-84.3 291,-121c125.4,-34.4 219.5,-118.8 230,-208 7.8,-66.4 -97,-192 -97,-192s-73.6,204 -170,306C1423,708 1462.9,693 1274,812Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M1157,1055c-26,10.2 -14.6,-538.8 -26.6,-685.8S1208,132 1241,105s96.1,-6.4 172,19c-85.9,58.9 -129,129 -141,225C1290,570.9 1171,679 1157,1055Z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M749,1800"/>
|
||||
</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,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:drawable="@color/primary"/>
|
||||
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@drawable/logo"/>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
||||
@@ -1,16 +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"
|
||||
android:background="?android:windowBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/toolbar"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -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>
|
||||
76
build.gradle
@@ -1,25 +1,65 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.topjohnwu.magisk"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion rootProject.ext.compileSdkVersion
|
||||
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
|
||||
// in the individual module build.gradle files
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "mode"
|
||||
|
||||
productFlavors {
|
||||
full {
|
||||
versionCode 125
|
||||
versionName "5.8.0"
|
||||
}
|
||||
stub {
|
||||
versionCode 1
|
||||
versionName "stub"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
dexOptions {
|
||||
preDexLibraries true
|
||||
javaMaxHeapSize "2g"
|
||||
}
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
fullImplementation project(':utils')
|
||||
implementation "com.android.support:support-core-utils:${rootProject.ext.supportLibVersion}"
|
||||
fullImplementation "com.android.support:preference-v7:${rootProject.ext.supportLibVersion}"
|
||||
fullImplementation "com.android.support:recyclerview-v7:${rootProject.ext.supportLibVersion}"
|
||||
fullImplementation "com.android.support:cardview-v7:${rootProject.ext.supportLibVersion}"
|
||||
fullImplementation "com.android.support:design:${rootProject.ext.supportLibVersion}"
|
||||
fullImplementation 'com.github.topjohnwu:libsu:1.2.0'
|
||||
fullImplementation 'com.atlassian.commonmark:commonmark:0.11.0'
|
||||
fullImplementation 'org.kamranzafar:jtar:2.3'
|
||||
fullImplementation 'com.jakewharton:butterknife:8.8.1'
|
||||
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
|
||||
29
proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# 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.asymmetric.rsa.**SHA1** { *; }
|
||||
-keep class org.bouncycastle.jcajce.provider.asymmetric.RSA** { *; }
|
||||
-keep class org.bouncycastle.jcajce.provider.digest.SHA1** { *; }
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
# Gson
|
||||
-keepattributes Signature
|
||||
@@ -1 +0,0 @@
|
||||
include ':app'
|
||||
82
src/full/AndroidManifest.xml
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.topjohnwu.magisk">
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
|
||||
<application
|
||||
android:name=".MagiskManager"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<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.StatusBar" />
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:theme="@style/AppTheme.StatusBar" />
|
||||
<activity
|
||||
android:name=".FlashActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:screenOrientation="nosensor"
|
||||
android:theme="@style/AppTheme.StatusBar" />
|
||||
<activity
|
||||
android:name=".NoUIActivity"
|
||||
android:theme="@style/AppTheme.Translucent" />
|
||||
<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" />
|
||||
<receiver android:name=".receivers.ShortcutReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name=".services.OnBootIntentService" />
|
||||
<service
|
||||
android:name=".services.UpdateCheckService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
|
||||
<!-- Hardcode GMS version -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="7095000" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
80
src/full/java/com/topjohnwu/magisk/AboutActivity.java
Normal file
@@ -0,0 +1,80 @@
|
||||
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.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_StatusBar_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 -> {
|
||||
new MarkDownWindow(this, getString(R.string.app_changelog),
|
||||
getResources().openRawResource(R.raw.changelog)).exec();
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
155
src/full/java/com/topjohnwu/magisk/FlashActivity.java
Normal file
@@ -0,0 +1,155 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
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.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.superuser.CallbackList;
|
||||
import com.topjohnwu.superuser.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.Async.su("/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_StatusBar_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<>();
|
||||
CallbackList<String> console = new CallbackList<String>(new ArrayList<>()) {
|
||||
@Override
|
||||
public void onAddElement(String s) {
|
||||
logs.add(s);
|
||||
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.UNINSTALL:
|
||||
new UninstallMagisk(this, uri, console, logs).exec();
|
||||
break;
|
||||
case Const.Value.FLASH_MAGISK:
|
||||
new InstallMagisk(this, console, logs, uri, InstallMagisk.DIRECT_MODE).exec();
|
||||
break;
|
||||
case Const.Value.FLASH_SECOND_SLOT:
|
||||
new InstallMagisk(this, console, logs, uri, InstallMagisk.SECOND_SLOT_MODE).exec();
|
||||
break;
|
||||
case Const.Value.PATCH_BOOT:
|
||||
new InstallMagisk(this, console, logs, uri,
|
||||
intent.getParcelableExtra(Const.Key.FLASH_SET_BOOT)).exec();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Prevent user accidentally press back button
|
||||
}
|
||||
|
||||
private static class UninstallMagisk extends FlashZip {
|
||||
|
||||
private UninstallMagisk(Activity context, Uri uri, List<String> console, List<String> logs) {
|
||||
super(context, uri, console, logs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
if (result == 1) {
|
||||
new Handler().postDelayed(() ->
|
||||
RootUtils.uninstallPkg(getActivity().getPackageName()), 3000);
|
||||
} else {
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/full/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();
|
||||
}
|
||||
|
||||
}
|
||||
313
src/full/java/com/topjohnwu/magisk/MagiskFragment.java
Normal file
@@ -0,0 +1,313 @@
|
||||
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.text.TextUtils;
|
||||
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.Const;
|
||||
import com.topjohnwu.magisk.utils.ISafetyNetHelper;
|
||||
import com.topjohnwu.magisk.utils.ShowUI;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
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 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 (!TextUtils.equals(mm.getPackageName(), Const.ORIG_PKG_NAME)) {
|
||||
new AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.cannot_check_sn_title)
|
||||
.setMessage(R.string.cannot_check_sn_notice)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
} else 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.reset();
|
||||
mm.updateCheckDone.reset();
|
||||
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) {
|
||||
if (topic == mm.updateCheckDone) {
|
||||
updateCheckUI();
|
||||
} else if (topic == mm.safetyNetDone) {
|
||||
updateSafetyNetUI((int) topic.getResults()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@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 > Const.MAGISK_VER.UNIFIED;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
magiskUpdateIcon.setImageResource(image);
|
||||
magiskUpdateIcon.setColorFilter(color);
|
||||
magiskUpdateIcon.setVisibility(View.VISIBLE);
|
||||
|
||||
magiskUpdateProgress.setVisibility(View.GONE);
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
|
||||
if (!shownDialog) {
|
||||
if (mm.remoteMagiskVersionCode > mm.magiskVersionCode
|
||||
|| mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
install();
|
||||
} else if (mm.remoteMagiskVersionCode >= Const.MAGISK_VER.FIX_ENV &&
|
||||
!ShellUtils.fastCmdResult("env_check")) {
|
||||
ShowUI.envFixDialog(getActivity());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 & ISafetyNetHelper.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 & ISafetyNetHelper.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 ISafetyNetHelper.CAUSE_SERVICE_DISCONNECTED:
|
||||
resid = R.string.safetyNet_network_loss;
|
||||
break;
|
||||
case ISafetyNetHelper.CAUSE_NETWORK_LOST:
|
||||
resid = R.string.safetyNet_service_disconnected;
|
||||
break;
|
||||
case ISafetyNetHelper.RESPONSE_ERR:
|
||||
resid = R.string.safetyNet_res_invalid;
|
||||
break;
|
||||
case ISafetyNetHelper.CONNECTION_FAIL:
|
||||
default:
|
||||
resid = R.string.safetyNet_api_error;
|
||||
break;
|
||||
}
|
||||
safetyNetStatusText.setText(resid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
92
src/full/java/com/topjohnwu/magisk/MagiskHideFragment.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
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.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SearchView;
|
||||
|
||||
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
private Unbinder unbinder;
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
|
||||
private ApplicationAdapter appAdapter;
|
||||
|
||||
private SearchView.OnQueryTextListener searchListener;
|
||||
|
||||
@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_magisk_hide, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> appAdapter.refresh());
|
||||
|
||||
appAdapter = new ApplicationAdapter();
|
||||
recyclerView.setAdapter(appAdapter);
|
||||
|
||||
searchListener = new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
appAdapter.filter(query);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
appAdapter.filter(newText);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
getActivity().setTitle(R.string.magiskhide);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_magiskhide, menu);
|
||||
SearchView search = (SearchView) menu.findItem(R.id.app_search).getActionView();
|
||||
search.setOnQueryTextListener(searchListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unbinder.unbind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
appAdapter.filter(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { getApplication().magiskHideDone };
|
||||
}
|
||||
}
|
||||
145
src/full/java/com/topjohnwu/magisk/MagiskLogFragment.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.Manifest;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
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 com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
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);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
getActivity().setTitle(R.string.log);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
readLogs();
|
||||
}
|
||||
|
||||
@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:
|
||||
readLogs();
|
||||
return true;
|
||||
case R.id.menu_save:
|
||||
runWithPermission(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, this::saveLogs);
|
||||
return true;
|
||||
case R.id.menu_clear:
|
||||
clearLogs();
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void readLogs() {
|
||||
Shell.Async.su(new Shell.Async.Callback() {
|
||||
@Override
|
||||
public void onTaskResult(@Nullable List<String> out, @Nullable List<String> err) {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (ShellUtils.isValidOutput(out)) {
|
||||
txtLog.setText(TextUtils.join("\n", out));
|
||||
} else {
|
||||
txtLog.setText(R.string.log_is_empty);
|
||||
}
|
||||
svLog.postDelayed(() -> svLog.fullScroll(ScrollView.FOCUS_DOWN), 100);
|
||||
hsvLog.postDelayed(() -> hsvLog.fullScroll(ScrollView.FOCUS_LEFT), 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskError(@NonNull Throwable throwable) {
|
||||
txtLog.setText(R.string.log_is_empty);
|
||||
}
|
||||
}, "cat " + Const.MAGISK_LOG + " | tail -n 5000");
|
||||
}
|
||||
|
||||
public void saveLogs() {
|
||||
Calendar now = Calendar.getInstance();
|
||||
String filename = Utils.fmt("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));
|
||||
|
||||
File targetFile = new File(Const.EXTERNAL_PATH + "/logs", filename);
|
||||
targetFile.getParentFile().mkdirs();
|
||||
try {
|
||||
targetFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
Shell.Async.su(new Shell.Async.Callback() {
|
||||
@Override
|
||||
public void onTaskResult(@Nullable List<String> out, @Nullable List<String> err) {
|
||||
SnackbarMaker.make(txtLog, targetFile.getPath(), Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskError(@NonNull Throwable throwable) {}
|
||||
}, "cat " + Const.MAGISK_LOG + " > " + targetFile);
|
||||
}
|
||||
|
||||
public void clearLogs() {
|
||||
Shell.Async.su("echo -n > " + Const.MAGISK_LOG);
|
||||
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
292
src/full/java/com/topjohnwu/magisk/MagiskManager.java
Normal file
@@ -0,0 +1,292 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.content.ComponentName;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.topjohnwu.magisk.components.Application;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.database.MagiskDatabaseHelper;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.ShellInitializer;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class MagiskManager extends Application implements Shell.Container {
|
||||
|
||||
// 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 remoteManagerVersionString;
|
||||
public int remoteManagerVersionCode = -1;
|
||||
|
||||
public String magiskLink;
|
||||
public String magiskNoteLink;
|
||||
public String managerLink;
|
||||
public String managerNoteLink;
|
||||
public String uninstallerLink;
|
||||
|
||||
public boolean keepVerity = false;
|
||||
public boolean keepEnc = false;
|
||||
|
||||
// Data
|
||||
public Map<String, Module> moduleMap;
|
||||
public List<Locale> locales;
|
||||
|
||||
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 MagiskDatabaseHelper mDB;
|
||||
public RepoDatabaseHelper repoDB;
|
||||
|
||||
private volatile Shell mShell;
|
||||
|
||||
public MagiskManager() {
|
||||
weakSelf = new WeakReference<>(this);
|
||||
Shell.setContainer(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Shell getShell() {
|
||||
return mShell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShell(@Nullable Shell shell) {
|
||||
mShell = shell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Shell.setFlags(Shell.FLAG_MOUNT_MASTER);
|
||||
Shell.verboseLogging(BuildConfig.DEBUG);
|
||||
Shell.setInitializer(ShellInitializer.class);
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
mDB = MagiskDatabaseHelper.getInstance(this);
|
||||
|
||||
String pkg = mDB.getStrings(Const.Key.SU_MANAGER, null);
|
||||
if (pkg != null && getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
mDB.setStrings(Const.Key.SU_MANAGER, null);
|
||||
RootUtils.uninstallPkg(pkg);
|
||||
}
|
||||
if (TextUtils.equals(pkg, getPackageName())) {
|
||||
try {
|
||||
// We are the manager, remove com.topjohnwu.magisk as it could be malware
|
||||
getPackageManager().getApplicationInfo(Const.ORIG_PKG_NAME, 0);
|
||||
RootUtils.uninstallPkg(Const.ORIG_PKG_NAME);
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
}
|
||||
|
||||
setLocale();
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
public static MagiskManager get() {
|
||||
return (MagiskManager) 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 = mDB.getSettings(Const.Key.ROOT_ACCESS, Const.Value.ROOT_ACCESS_APPS_AND_ADB);
|
||||
multiuserMode = mDB.getSettings(Const.Key.SU_MULTIUSER_MODE, Const.Value.MULTIUSER_MODE_OWNER_ONLY);
|
||||
suNamespaceMode = mDB.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, Const.MAGISK_HOST_FILE.exists())
|
||||
.putBoolean(Const.Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
|
||||
.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 void loadMagiskInfo() {
|
||||
try {
|
||||
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
|
||||
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
|
||||
String s = ShellUtils.fastCmd((magiskVersionCode >= Const.MAGISK_VER.RESETPROP_PERSIST ?
|
||||
"resetprop -p " : "getprop ") + Const.MAGISKHIDE_PROP);
|
||||
magiskHide = s == null || Integer.parseInt(s) != 0;
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
public void getDefaultInstallFlags() {
|
||||
keepVerity = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPVERITY"));
|
||||
keepEnc = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPFORCEENCRYPT"));
|
||||
}
|
||||
|
||||
public void setupUpdateCheck() {
|
||||
JobScheduler scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
|
||||
|
||||
if (prefs.getBoolean(Const.Key.CHECK_UPDATES, true)) {
|
||||
if (scheduler.getAllPendingJobs().isEmpty() ||
|
||||
Const.UPDATE_SERVICE_VER > 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();
|
||||
scheduler.schedule(info);
|
||||
}
|
||||
} else {
|
||||
scheduler.cancel(Const.UPDATE_SERVICE_VER);
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpPrefs() {
|
||||
// Flush prefs to disk
|
||||
prefs.edit().commit();
|
||||
File xml = new File(getFilesDir().getParent() + "/shared_prefs",
|
||||
getPackageName() + "_preferences.xml");
|
||||
Shell.Sync.su(Utils.fmt("for usr in /data/user/*; do cat %s > ${usr}/%s; done", xml, Const.MANAGER_CONFIGS));
|
||||
}
|
||||
|
||||
public void loadPrefs() {
|
||||
SuFile config = new SuFile(Utils.fmt("/data/user/%d/%s", Const.USER_ID, Const.MANAGER_CONFIGS));
|
||||
if (config.exists()) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
try {
|
||||
SuFileInputStream is = new SuFileInputStream(config);
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
|
||||
parser.setInput(is, "UTF-8");
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.START_TAG, null, "map");
|
||||
while (parser.next() != XmlPullParser.END_TAG) {
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG)
|
||||
continue;
|
||||
String key = parser.getAttributeValue(null, "name");
|
||||
String value = parser.getAttributeValue(null, "value");
|
||||
switch (parser.getName()) {
|
||||
case "string":
|
||||
parser.require(XmlPullParser.START_TAG, null, "string");
|
||||
editor.putString(key, parser.nextText());
|
||||
parser.require(XmlPullParser.END_TAG, null, "string");
|
||||
break;
|
||||
case "boolean":
|
||||
parser.require(XmlPullParser.START_TAG, null, "boolean");
|
||||
editor.putBoolean(key, Boolean.parseBoolean(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "boolean");
|
||||
break;
|
||||
case "int":
|
||||
parser.require(XmlPullParser.START_TAG, null, "int");
|
||||
editor.putInt(key, Integer.parseInt(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "int");
|
||||
break;
|
||||
case "long":
|
||||
parser.require(XmlPullParser.START_TAG, null, "long");
|
||||
editor.putLong(key, Long.parseLong(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "long");
|
||||
break;
|
||||
case "float":
|
||||
parser.require(XmlPullParser.START_TAG, null, "int");
|
||||
editor.putFloat(key, Float.parseFloat(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "int");
|
||||
break;
|
||||
default:
|
||||
parser.next();
|
||||
}
|
||||
}
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
editor.remove(Const.Key.ETAG_KEY);
|
||||
editor.apply();
|
||||
loadConfig();
|
||||
config.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
219
src/full/java/com/topjohnwu/magisk/MainActivity.java
Normal file
@@ -0,0 +1,219 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
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.components.Activity;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class MainActivity extends Activity
|
||||
implements NavigationView.OnNavigationItemSelectedListener, Topic.Subscriber {
|
||||
|
||||
private final Handler mDrawerHandler = new Handler();
|
||||
private int mDrawerItem;
|
||||
private boolean fromShortcut = true;
|
||||
|
||||
@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();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
checkHideSection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (drawer.isDrawerOpen(navigationView)) {
|
||||
drawer.closeDrawer(navigationView);
|
||||
} else if (mDrawerItem != R.id.magisk && !fromShortcut) {
|
||||
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) {
|
||||
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 >= Const.MAGISK_VER.UNIFIED
|
||||
&& mm.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 "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:
|
||||
fromShortcut = false;
|
||||
displayFragment(new MagiskFragment(), true);
|
||||
break;
|
||||
case R.id.superuser:
|
||||
displayFragment(new SuperuserFragment(), true);
|
||||
break;
|
||||
case R.id.modules:
|
||||
displayFragment(new ModulesFragment(), true);
|
||||
break;
|
||||
case R.id.downloads:
|
||||
displayFragment(new ReposFragment(), true);
|
||||
break;
|
||||
case R.id.magiskhide:
|
||||
displayFragment(new MagiskHideFragment(), true);
|
||||
break;
|
||||
case R.id.log:
|
||||
displayFragment(new LogFragment(), 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, boolean setElevation) {
|
||||
supportInvalidateOptionsMenu();
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
.replace(R.id.content_frame, navFragment)
|
||||
.commitNow();
|
||||
toolbar.setElevation(setElevation ? toolbarElevation : 0);
|
||||
}
|
||||
}
|
||||
144
src/full/java/com/topjohnwu/magisk/ModulesFragment.java
Normal file
@@ -0,0 +1,144 @@
|
||||
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.Topic;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
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() {
|
||||
runWithPermission(new String[] { 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) {
|
||||
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.Async.su("/system/bin/reboot");
|
||||
return true;
|
||||
case R.id.reboot_recovery:
|
||||
Shell.Async.su("/system/bin/reboot recovery");
|
||||
return true;
|
||||
case R.id.reboot_bootloader:
|
||||
Shell.Async.su("/system/bin/reboot bootloader");
|
||||
return true;
|
||||
case R.id.reboot_download:
|
||||
Shell.Async.su("/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);
|
||||
}
|
||||
}
|
||||
26
src/full/java/com/topjohnwu/magisk/NoUIActivity.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
|
||||
public class NoUIActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
String[] perms = getIntent().getStringArrayExtra(Const.Key.INTENT_PERM);
|
||||
if (perms != null) {
|
||||
ActivityCompat.requestPermissions(this, perms, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
126
src/full/java/com/topjohnwu/magisk/ReposFragment.java
Normal file
@@ -0,0 +1,126 @@
|
||||
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;
|
||||
private MagiskManager mm;
|
||||
@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);
|
||||
mm = getApplication();
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(mm.repoLoadDone.isPending());
|
||||
|
||||
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(mm.repoDB, mm.moduleMap);
|
||||
recyclerView.setAdapter(adapter);
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
adapter = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
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[] { mm.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) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
321
src/full/java/com/topjohnwu/magisk/SettingsActivity.java
Normal file
@@ -0,0 +1,321 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v14.preference.SwitchPreference;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceCategory;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.support.v7.preference.PreferenceScreen;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
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.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
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_StatusBar_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) {
|
||||
getSupportFragmentManager().beginTransaction().add(R.id.container, new SettingsFragment()).commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
recreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { getMagiskManager().reloadActivity };
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragmentCompat
|
||||
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 onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.app_settings, rootKey);
|
||||
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");
|
||||
Preference restoreManager = findPreference("restore");
|
||||
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);
|
||||
}
|
||||
|
||||
// Disable fingerprint option if not possible
|
||||
if (!FingerprintHelper.canUseFingerprint()) {
|
||||
fingerprint.setEnabled(false);
|
||||
fingerprint.setSummary(R.string.disable_fingerprint);
|
||||
}
|
||||
|
||||
if (mm.magiskVersionCode >= Const.MAGISK_VER.MANAGER_HIDE) {
|
||||
if (mm.getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||
new HideManager(getActivity()).exec();
|
||||
return true;
|
||||
});
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
} else {
|
||||
if (Utils.checkNetworkStatus()) {
|
||||
restoreManager.setOnPreferenceClickListener((pref) -> {
|
||||
Utils.dlAndReceive(
|
||||
getActivity(), new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
mm.dumpPrefs();
|
||||
if (ShellUtils.fastCmdResult("pm install " + uri.getPath()))
|
||||
RootUtils.uninstallPkg(context.getPackageName());
|
||||
}
|
||||
},
|
||||
mm.managerLink,
|
||||
Utils.fmt("MagiskManager-v%s.apk", mm.remoteManagerVersionString)
|
||||
);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
}
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
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 < Const.MAGISK_VER.UNIFIED) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
}
|
||||
}
|
||||
|
||||
private void setLocalePreference(ListPreference lp) {
|
||||
CharSequence[] entries = new CharSequence[mm.locales.size() + 1];
|
||||
CharSequence[] entryValues = new CharSequence[mm.locales.size() + 1];
|
||||
entries[0] = Utils.getLocaleString(MagiskManager.defaultLocale, 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.setSummary(MagiskManager.locale.getDisplayName(MagiskManager.locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
subscribeTopics();
|
||||
return super.onCreateView(inflater, container, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
unsubscribeTopics();
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@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);
|
||||
return;
|
||||
case Const.Key.COREONLY:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
try {
|
||||
Const.MAGISK_DISABLE_FILE.createNewFile();
|
||||
} catch (IOException ignored) {}
|
||||
} else {
|
||||
Const.MAGISK_DISABLE_FILE.delete();
|
||||
}
|
||||
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case Const.Key.MAGISKHIDE:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.Async.su("magiskhide --enable");
|
||||
} else {
|
||||
Shell.Async.su("magiskhide --disable");
|
||||
}
|
||||
break;
|
||||
case Const.Key.HOSTS:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.Async.su(
|
||||
"cp -af /system/etc/hosts " + Const.MAGISK_HOST_FILE,
|
||||
"mount -o bind " + Const.MAGISK_HOST_FILE + " /system/etc/hosts");
|
||||
} else {
|
||||
Shell.Async.su(
|
||||
"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.mDB.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;
|
||||
case Const.Key.CHECK_UPDATES:
|
||||
mm.setupUpdateCheck();
|
||||
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) {
|
||||
setLocalePreference((ListPreference) findPreference(Const.Key.LOCALE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { mm.localeDone };
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
88
src/full/java/com/topjohnwu/magisk/SplashActivity.java
Normal file
@@ -0,0 +1,88 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
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.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.receivers.ShortcutReceiver;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class SplashActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
RootUtils.init();
|
||||
MagiskManager mm = getMagiskManager();
|
||||
|
||||
mm.repoDB = new RepoDatabaseHelper(this);
|
||||
mm.loadMagiskInfo();
|
||||
mm.getDefaultInstallFlags();
|
||||
mm.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);
|
||||
}
|
||||
|
||||
// Setup shortcuts
|
||||
sendBroadcast(new Intent(this, ShortcutReceiver.class));
|
||||
|
||||
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) {
|
||||
// Update check service
|
||||
mm.setupUpdateCheck();
|
||||
// Fire asynctasks
|
||||
loadModuleTask.exec();
|
||||
}
|
||||
|
||||
// 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/full/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.mDB);
|
||||
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.mDB.clearLogs();
|
||||
updateList();
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unbinder.unbind();
|
||||
}
|
||||
}
|
||||
63
src/full/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.mDB.getPolicyList(pm);
|
||||
|
||||
if (policyList.size() == 0) {
|
||||
emptyRv.setVisibility(View.VISIBLE);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
} else {
|
||||
recyclerView.setAdapter(new PolicyAdapter(policyList, mm.mDB, 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,158 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
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.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
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> fullList, showList;
|
||||
private List<String> hideList;
|
||||
private PackageManager pm;
|
||||
private ApplicationFilter filter;
|
||||
|
||||
public ApplicationAdapter() {
|
||||
fullList = showList = Collections.emptyList();
|
||||
hideList = Collections.emptyList();
|
||||
filter = new ApplicationFilter();
|
||||
pm = MagiskManager.get().getPackageManager();
|
||||
new LoadApps().exec();
|
||||
}
|
||||
|
||||
@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 = showList.get(position);
|
||||
|
||||
holder.appIcon.setImageDrawable(info.loadIcon(pm));
|
||||
holder.appName.setText(info.loadLabel(pm));
|
||||
holder.appPackage.setText(info.packageName);
|
||||
|
||||
holder.checkBox.setOnCheckedChangeListener(null);
|
||||
holder.checkBox.setChecked(hideList.contains(info.packageName));
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if (isChecked) {
|
||||
Shell.Async.su("magiskhide --add " + info.packageName);
|
||||
hideList.add(info.packageName);
|
||||
} else {
|
||||
Shell.Async.su("magiskhide --rm " + info.packageName);
|
||||
hideList.remove(info.packageName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return showList.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 {
|
||||
|
||||
private boolean lowercaseContains(String s, CharSequence filter) {
|
||||
return !TextUtils.isEmpty(s) && s.toLowerCase().contains(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
if (constraint == null || constraint.length() == 0) {
|
||||
showList = fullList;
|
||||
} else {
|
||||
showList = new ArrayList<>();
|
||||
String filter = constraint.toString().toLowerCase();
|
||||
for (ApplicationInfo info : fullList) {
|
||||
if (lowercaseContains(info.loadLabel(pm).toString(), filter)
|
||||
|| lowercaseContains(info.packageName, filter)) {
|
||||
showList.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) {
|
||||
fullList = pm.getInstalledApplications(0);
|
||||
hideList = Shell.Sync.su("magiskhide --ls");
|
||||
for (Iterator<ApplicationInfo> i = fullList.iterator(); i.hasNext(); ) {
|
||||
ApplicationInfo info = i.next();
|
||||
if (Const.HIDE_BLACKLIST.contains(info.packageName) || !info.enabled) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
Collections.sort(fullList, (a, b) -> {
|
||||
boolean ah = hideList.contains(a.packageName);
|
||||
boolean bh = hideList.contains(b.packageName);
|
||||
if (ah == bh) {
|
||||
return a.loadLabel(pm).toString().toLowerCase().compareTo(
|
||||
b.loadLabel(pm).toString().toLowerCase());
|
||||
} else if (ah) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
MagiskManager.get().magiskHideDone.publish(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,9 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.module.Module;
|
||||
import com.topjohnwu.magisk.utils.Async;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -32,7 +32,6 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_module, parent, false);
|
||||
ButterKnife.bind(this, view);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@@ -41,52 +40,43 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
|
||||
Context context = holder.itemView.getContext();
|
||||
final Module module = mList.get(position);
|
||||
|
||||
holder.title.setText(module.getName());
|
||||
holder.versionName.setText(module.getVersion());
|
||||
String version = module.getVersion();
|
||||
String author = module.getAuthor();
|
||||
holder.author.setText(TextUtils.isEmpty(author) ? null : context.getString(R.string.author, author));
|
||||
holder.description.setText(module.getDescription());
|
||||
String description = 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.setChecked(module.isEnabled());
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> new Async.RootTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
if (isChecked) {
|
||||
module.removeDisableFile();
|
||||
} else {
|
||||
module.createDisableFile();
|
||||
}
|
||||
return null;
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
int snack;
|
||||
if (isChecked) {
|
||||
module.removeDisableFile();
|
||||
snack = R.string.disable_file_removed;
|
||||
} else {
|
||||
module.createDisableFile();
|
||||
snack = R.string.disable_file_created;
|
||||
}
|
||||
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
|
||||
});
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
int title = isChecked ? R.string.disable_file_removed : R.string.disable_file_created;
|
||||
Snackbar.make(holder.title, title, Snackbar.LENGTH_SHORT).show();
|
||||
holder.delete.setOnClickListener(v -> {
|
||||
boolean removed = module.willBeRemoved();
|
||||
int snack;
|
||||
if (removed) {
|
||||
module.deleteRemoveFile();
|
||||
snack = R.string.remove_file_deleted;
|
||||
} else {
|
||||
module.createRemoveFile();
|
||||
snack = R.string.remove_file_created;
|
||||
}
|
||||
}.exec());
|
||||
|
||||
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());
|
||||
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
|
||||
updateDeleteButton(holder, module);
|
||||
});
|
||||
|
||||
if (module.isUpdated()) {
|
||||
holder.notice.setVisibility(View.VISIBLE);
|
||||
150
src/full/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.MagiskDatabaseHelper;
|
||||
|
||||
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 MagiskDatabaseHelper dbHelper;
|
||||
private PackageManager pm;
|
||||
private Set<Policy> expandList = new HashSet<>();
|
||||
|
||||
public PolicyAdapter(List<Policy> list, MagiskDatabaseHelper 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/full/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/full/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.MagiskDatabaseHelper;
|
||||
|
||||
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 MagiskDatabaseHelper suDB;
|
||||
private Cursor suLogCursor = null;
|
||||
|
||||
public SuLogAdapter(MagiskDatabaseHelper 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;
|
||||
}
|
||||
}
|
||||
}
|
||||