mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-14 10:27:29 +00:00
Compare commits
1072 Commits
manager-v5
...
manager-v5
Author | SHA1 | Date | |
---|---|---|---|
![]() |
298d5e197b | ||
![]() |
d73c0a998d | ||
![]() |
1b79a3ddbf | ||
![]() |
a8478ace18 | ||
![]() |
72cf5f3f9f | ||
![]() |
6f9d493a18 | ||
![]() |
08f7d5ebff | ||
![]() |
1fe3675403 | ||
![]() |
a0f956d2c1 | ||
![]() |
1560f91b4a | ||
![]() |
c20f362594 | ||
![]() |
7ae8c26e50 | ||
![]() |
adfffe6121 | ||
![]() |
64601baa76 | ||
![]() |
aa374b51f1 | ||
![]() |
5c483745ff | ||
![]() |
0c247110a0 | ||
![]() |
1643638a78 | ||
![]() |
4ace228fc2 | ||
![]() |
25aa86a0dc | ||
![]() |
70d3b24338 | ||
![]() |
8664e9d19b | ||
![]() |
50d9877446 | ||
![]() |
fe06352089 | ||
![]() |
7b599419b5 | ||
![]() |
491adf072e | ||
![]() |
f6aae2b048 | ||
![]() |
d2d5c94633 | ||
![]() |
10581f9ef2 | ||
![]() |
c7e0e1c038 | ||
![]() |
a914d701eb | ||
![]() |
0f9dee6e9c | ||
![]() |
aa383e2190 | ||
![]() |
9bbfcf326c | ||
![]() |
3948e67c8f | ||
![]() |
d56e1b2cc5 | ||
![]() |
bfac1f1bc2 | ||
![]() |
d4a956c355 | ||
![]() |
6c71fefa58 | ||
![]() |
ad3003c00a | ||
![]() |
0ad5dcb258 | ||
![]() |
d790309b02 | ||
![]() |
1072faf309 | ||
![]() |
d2c196896d | ||
![]() |
e42b608444 | ||
![]() |
89a501a3af | ||
![]() |
c19b78180c | ||
![]() |
c0b750a09a | ||
![]() |
c967e618a1 | ||
![]() |
59f78d7dfc | ||
![]() |
d8405f0d05 | ||
![]() |
0f34f0033c | ||
![]() |
190646d50c | ||
![]() |
a46c6252c6 | ||
![]() |
5c1886c8f5 | ||
![]() |
afcb3d8f34 | ||
![]() |
9fbffafdbf | ||
![]() |
075f0458f7 | ||
![]() |
d4568aa0a7 | ||
![]() |
97588408a2 | ||
![]() |
1def9b301b | ||
![]() |
5bac442b18 | ||
![]() |
6add682705 | ||
![]() |
8b50d84a05 | ||
![]() |
d3858b81e2 | ||
![]() |
bdff9769be | ||
![]() |
c61df75e5e | ||
![]() |
a74bf2cc27 | ||
![]() |
ada0f93686 | ||
![]() |
ff36f2ba17 | ||
![]() |
5164cfd399 | ||
![]() |
5fa021503e | ||
![]() |
7b5d79d313 | ||
![]() |
3e3f38500d | ||
![]() |
5109b9abfd | ||
![]() |
7fb4777c1c | ||
![]() |
c38533e0f8 | ||
![]() |
51ba99d09e | ||
![]() |
9159f86a9e | ||
![]() |
e139f4fc13 | ||
![]() |
2fbfeacb87 | ||
![]() |
ebb7a9fcda | ||
![]() |
9e72317302 | ||
![]() |
d764c20c08 | ||
![]() |
9c17b8a098 | ||
![]() |
3084873154 | ||
![]() |
32809e56d0 | ||
![]() |
9f05b182a2 | ||
![]() |
525484e834 | ||
![]() |
65a4e69cae | ||
![]() |
e973f8bab9 | ||
![]() |
92466671ff | ||
![]() |
6d61106070 | ||
![]() |
ac13749fb8 | ||
![]() |
7ec1a9a316 | ||
![]() |
cf17e21ad3 | ||
![]() |
0e0240c4ab | ||
![]() |
d1b290b91a | ||
![]() |
a63696836c | ||
![]() |
46aad00f16 | ||
![]() |
252afe8932 | ||
![]() |
9dd467a613 | ||
![]() |
4c14df67cc | ||
![]() |
20e0fe3ba1 | ||
![]() |
6a005135f2 | ||
![]() |
82e8375957 | ||
![]() |
bb25edc09e | ||
![]() |
169c0fe4af | ||
![]() |
cd6918e6eb | ||
![]() |
5be035fd44 | ||
![]() |
f1edc8443c | ||
![]() |
d9564bd04c | ||
![]() |
35f1c396f2 | ||
![]() |
6acb950990 | ||
![]() |
27e0d1641a | ||
![]() |
9ac71ff8af | ||
![]() |
075737a4ec | ||
![]() |
6d0e4a6a5e | ||
![]() |
a2544768a0 | ||
![]() |
8574a14ed2 | ||
![]() |
e90c555c18 | ||
![]() |
863b9a410f | ||
![]() |
23c7bbc7d5 | ||
![]() |
f900189f90 | ||
![]() |
7c74be2790 | ||
![]() |
70dd2d4829 | ||
![]() |
914b7ee056 | ||
![]() |
e39f83edbf | ||
![]() |
52fe0c6abb | ||
![]() |
5cb3e5937f | ||
![]() |
e0cd224831 | ||
![]() |
de225ac64a | ||
![]() |
5807808a10 | ||
![]() |
362877d18f | ||
![]() |
88b8dd0149 | ||
![]() |
1552f32e09 | ||
![]() |
50b73a6720 | ||
![]() |
53e51f1735 | ||
![]() |
40b63bfebe | ||
![]() |
89861eceef | ||
![]() |
b8eaff66fa | ||
![]() |
a747fdd27d | ||
![]() |
27851bdefa | ||
![]() |
3fdeb40ddf | ||
![]() |
546c7cebd3 | ||
![]() |
473902f5f4 | ||
![]() |
41c0721159 | ||
![]() |
413d4badfd | ||
![]() |
c5d67ebf72 | ||
![]() |
91818cfa1a | ||
![]() |
6263d684d9 | ||
![]() |
07140d33a7 | ||
![]() |
4ffc388491 | ||
![]() |
0ef026c610 | ||
![]() |
153c7fdf20 | ||
![]() |
90379eeb35 | ||
![]() |
3ae959af95 | ||
![]() |
c8cc652b71 | ||
![]() |
4b6285e5c2 | ||
![]() |
013de7b3ef | ||
![]() |
e11e88a9c5 | ||
![]() |
7cec8baa55 | ||
![]() |
e987db9fb5 | ||
![]() |
c603b9084f | ||
![]() |
492d6dfcf0 | ||
![]() |
a3e0f2dcc3 | ||
![]() |
cf211e26f4 | ||
![]() |
c5aaaa7c55 | ||
![]() |
f86d077e27 | ||
![]() |
f8076825cb | ||
![]() |
201d8a97d4 | ||
![]() |
d08f326990 | ||
![]() |
8dc9d3bc78 | ||
![]() |
adf95ce3a0 | ||
![]() |
3c1aca114f | ||
![]() |
18d0fd9d2a | ||
![]() |
c2e673f978 | ||
![]() |
2bde8a1975 | ||
![]() |
bf9927c7dd | ||
![]() |
f339a087a2 | ||
![]() |
6ccc5f3788 | ||
![]() |
1affb91f17 | ||
![]() |
7779c3e372 | ||
![]() |
49ba7ad22e | ||
![]() |
6ad33d60f7 | ||
![]() |
0117274061 | ||
![]() |
e50192a407 | ||
![]() |
c6fc0e587e | ||
![]() |
68c448bc34 | ||
![]() |
ef62272df7 | ||
![]() |
375cd0e42b | ||
![]() |
3934821436 | ||
![]() |
c3b473e4bc | ||
![]() |
7ed2c077de | ||
![]() |
52a6a7bce8 | ||
![]() |
1283167595 | ||
![]() |
23c2e22910 | ||
![]() |
f44b2dbd45 | ||
![]() |
46ee2c3f4e | ||
![]() |
5d5ec08566 | ||
![]() |
c3a6179a21 | ||
![]() |
ef175e3cbe | ||
![]() |
4de51d93ef | ||
![]() |
e7a2144def | ||
![]() |
52a2c6958b | ||
![]() |
70243d7a47 | ||
![]() |
b5b8c4b725 | ||
![]() |
6c4d81b1e9 | ||
![]() |
513d732934 | ||
![]() |
c88dc8795b | ||
![]() |
a8030c39b1 | ||
![]() |
7243b9e72f | ||
![]() |
d149af9628 | ||
![]() |
c0ac2d540b | ||
![]() |
528634d755 | ||
![]() |
3283439fd4 | ||
![]() |
c8216f9bc5 | ||
![]() |
e579f314a6 | ||
![]() |
2c4001387e | ||
![]() |
caa39474cb | ||
![]() |
d1a7372bd2 | ||
![]() |
e837bdc8ad | ||
![]() |
7265450e2e | ||
![]() |
058dbc9f9e | ||
![]() |
daf9b019c6 | ||
![]() |
14eebd582f | ||
![]() |
9a8eeacee8 | ||
![]() |
45b0bf5bc5 | ||
![]() |
88db822c43 | ||
![]() |
fbf3588fdf | ||
![]() |
a82ef6bd35 | ||
![]() |
312466aaf8 | ||
![]() |
c0ca99f4b4 | ||
![]() |
196f15d240 | ||
![]() |
bfddef2671 | ||
![]() |
44395e8ff0 | ||
![]() |
835ece5469 | ||
![]() |
d93fc67a75 | ||
![]() |
838f3cc01e | ||
![]() |
4d5841332a | ||
![]() |
9b41976252 | ||
![]() |
d08fd0561a | ||
![]() |
30e459252c | ||
![]() |
a6958ac139 | ||
![]() |
d7d76f54cc | ||
![]() |
970a2e87b3 | ||
![]() |
cabaae8403 | ||
![]() |
79eb5b2ed2 | ||
![]() |
f0533fca70 | ||
![]() |
08e98eeb15 | ||
![]() |
f2064a84ed | ||
![]() |
b2f719989d | ||
![]() |
6db27c7758 | ||
![]() |
3f83919e09 | ||
![]() |
72a5b83544 | ||
![]() |
d2e8ecc646 | ||
![]() |
30eb4074cb | ||
![]() |
9929e7d8e8 | ||
![]() |
f6ee252572 | ||
![]() |
90d218ebc8 | ||
![]() |
b0a5dbb4c2 | ||
![]() |
0abdfda5a2 | ||
![]() |
a7ceb04cb7 | ||
![]() |
274efb49e7 | ||
![]() |
b3cd83bbca | ||
![]() |
b8bd83ba05 | ||
![]() |
34dcf49fbc | ||
![]() |
ef2f8d485b | ||
![]() |
9fb9212b0a | ||
![]() |
f31a24b16d | ||
![]() |
b436bce565 | ||
![]() |
886286a819 | ||
![]() |
6d93831488 | ||
![]() |
bcdadc6581 | ||
![]() |
36448191b7 | ||
![]() |
be5be108c3 | ||
![]() |
c9ca42aaa9 | ||
![]() |
c0e2f44092 | ||
![]() |
1412fcbb22 | ||
![]() |
c69dc0f036 | ||
![]() |
9b445d89a1 | ||
![]() |
c3c78428c4 | ||
![]() |
c6d2bf577f | ||
![]() |
25703c1750 | ||
![]() |
3a9a3ed184 | ||
![]() |
88fae36b8a | ||
![]() |
fc9d4034a9 | ||
![]() |
cecc0b932d | ||
![]() |
0faed7159c | ||
![]() |
fb491cfdcf | ||
![]() |
fc706dcb40 | ||
![]() |
a2c1b024f3 | ||
![]() |
3d865394d7 | ||
![]() |
76ef1d0d86 | ||
![]() |
c694776162 | ||
![]() |
9484ec0c17 | ||
![]() |
7e2ba41c64 | ||
![]() |
614c552e55 | ||
![]() |
7db3d84ba2 | ||
![]() |
bb2c744ec0 | ||
![]() |
87f6018468 | ||
![]() |
9194c50590 | ||
![]() |
873f14bbe0 | ||
![]() |
31110b1927 | ||
![]() |
7ff45974c6 | ||
![]() |
2533a4fc4a | ||
![]() |
42284c5efb | ||
![]() |
7d7686da33 | ||
![]() |
65e455ef0b | ||
![]() |
ac05e2f2e2 | ||
![]() |
787f7b3035 | ||
![]() |
31bd642b80 | ||
![]() |
f0bac6b154 | ||
![]() |
cc7e74ca11 | ||
![]() |
e8a44646b8 | ||
![]() |
ae97d011ae | ||
![]() |
1b7657a374 | ||
![]() |
5665e04014 | ||
![]() |
bb70385a42 | ||
![]() |
9855877b03 | ||
![]() |
21a557a184 | ||
![]() |
76c9188fae | ||
![]() |
e4e5269836 | ||
![]() |
9e737df534 | ||
![]() |
0b3192c4d5 | ||
![]() |
968e6237bd | ||
![]() |
d780b5a0e4 | ||
![]() |
3e48427eaf | ||
![]() |
31360c34ed | ||
![]() |
e9624e2304 | ||
![]() |
9c6e64f47d | ||
![]() |
0afa601551 | ||
![]() |
26dfbb3028 | ||
![]() |
f16cd987e4 | ||
![]() |
a94fa81195 | ||
![]() |
6119c24720 | ||
![]() |
7da205f4c8 | ||
![]() |
754fafcfe9 | ||
![]() |
bd7766b17e | ||
![]() |
70b7d73453 | ||
![]() |
5ad4702a5b | ||
![]() |
40b6fe03c2 | ||
![]() |
49ecba2476 | ||
![]() |
ebd509d92d | ||
![]() |
7193374a7e | ||
![]() |
6728445542 | ||
![]() |
10ed299c78 | ||
![]() |
32b124913e | ||
![]() |
599ae95251 | ||
![]() |
d1be34c34a | ||
![]() |
bc2cac90fe | ||
![]() |
28350e3ad9 | ||
![]() |
f48e6c93b8 | ||
![]() |
7cfc24d68f | ||
![]() |
a58d3ea04d | ||
![]() |
dfee9954e0 | ||
![]() |
eed86c760f | ||
![]() |
c471bb6f67 | ||
![]() |
518c2b0f95 | ||
![]() |
328fc44194 | ||
![]() |
b6f735a8f6 | ||
![]() |
deae08fc4b | ||
![]() |
c61135ee7b | ||
![]() |
97cf15007f | ||
![]() |
e8302dfbe2 | ||
![]() |
558f95cf7e | ||
![]() |
18f6ead891 | ||
![]() |
10bd25be52 | ||
![]() |
65511845d2 | ||
![]() |
1c743839ea | ||
![]() |
bcae9dec41 | ||
![]() |
482c9af41f | ||
![]() |
2bf2e7461f | ||
![]() |
7d1082b1cb | ||
![]() |
0dbae83aec | ||
![]() |
f927c1b997 | ||
![]() |
89ec7dad2b | ||
![]() |
4fd61345af | ||
![]() |
66cca24453 | ||
![]() |
2f4062a923 | ||
![]() |
e733484fab | ||
![]() |
e5c3183025 | ||
![]() |
930c82316a | ||
![]() |
3dc22db265 | ||
![]() |
d8c51cb286 | ||
![]() |
2f79d0c3b3 | ||
![]() |
5c338cd0a7 | ||
![]() |
d8bb3af06b | ||
![]() |
e139e8777b | ||
![]() |
d52d7cfbd9 | ||
![]() |
4f74a259e3 | ||
![]() |
74da6e1dc0 | ||
![]() |
84ffdf0ed5 | ||
![]() |
022b18c8ce | ||
![]() |
b92b1dcddb | ||
![]() |
1472dbb291 | ||
![]() |
d58a8dc868 | ||
![]() |
e94be0b70e | ||
![]() |
f6ae7e1bf1 | ||
![]() |
190d857949 | ||
![]() |
f7b4935677 | ||
![]() |
a3c49de6a5 | ||
![]() |
e8dd1b292f | ||
![]() |
d21264d01b | ||
![]() |
b0567eadfd | ||
![]() |
5fc2058336 | ||
![]() |
d0567d29d2 | ||
![]() |
2b6c271d37 | ||
![]() |
b0c1a6f73a | ||
![]() |
4db0ad32f0 | ||
![]() |
d065040321 | ||
![]() |
17f0fea3fc | ||
![]() |
8ca1e43533 | ||
![]() |
bd01c314dc | ||
![]() |
257308d5db | ||
![]() |
d4620e1654 | ||
![]() |
e404476609 | ||
![]() |
942c870981 | ||
![]() |
baff9256c5 | ||
![]() |
b4c0a255fc | ||
![]() |
9f6a27c20d | ||
![]() |
742dc137ed | ||
![]() |
39a6bd33ce | ||
![]() |
4672a5fad6 | ||
![]() |
e649b0a2df | ||
![]() |
fd8dbe3eff | ||
![]() |
bb97cc594d | ||
![]() |
70a322263e | ||
![]() |
c6f144d482 | ||
![]() |
3709489b3a | ||
![]() |
145ef32e28 | ||
![]() |
2212800a23 | ||
![]() |
2e25431bb6 | ||
![]() |
32c8e7522f | ||
![]() |
a5e4f3cc6b | ||
![]() |
a30777bd9f | ||
![]() |
e989195a68 | ||
![]() |
d7a6127273 | ||
![]() |
997d58932e | ||
![]() |
b4015f877f | ||
![]() |
8ee9984e4e | ||
![]() |
d15fff95b9 | ||
![]() |
687e3b13ea | ||
![]() |
8c6bb383b7 | ||
![]() |
18bee21cfc | ||
![]() |
e5b6121d17 | ||
![]() |
bc592c1d13 | ||
![]() |
968bd8be67 | ||
![]() |
d8b8adb88c | ||
![]() |
f42d820891 | ||
![]() |
bc21a1fb71 | ||
![]() |
b7e717ee8c | ||
![]() |
3bc31374ac | ||
![]() |
858e7bae2b | ||
![]() |
8c02d120a2 | ||
![]() |
07e353f4ff | ||
![]() |
bb33d9e600 | ||
![]() |
68eb0bdec9 | ||
![]() |
32ee8e462c | ||
![]() |
e79aa54b70 | ||
![]() |
9a95652034 | ||
![]() |
912c188b53 | ||
![]() |
e9d0f615ba | ||
![]() |
9136573596 | ||
![]() |
2487ec94e6 | ||
![]() |
811489f157 | ||
![]() |
b438cc9335 | ||
![]() |
1d3d30fa45 | ||
![]() |
72b5985398 | ||
![]() |
2db60e0a6b | ||
![]() |
e710848345 | ||
![]() |
9e96824161 | ||
![]() |
8d6f3c2450 | ||
![]() |
f863d127e7 | ||
![]() |
a831110816 | ||
![]() |
e97bdb53f4 | ||
![]() |
fe1439fbac | ||
![]() |
2bc30e5c22 | ||
![]() |
7244c02a0d | ||
![]() |
84ca8e1f3e | ||
![]() |
6c229ffa68 | ||
![]() |
cdc5d983f3 | ||
![]() |
96688e4dac | ||
![]() |
28a945fee9 | ||
![]() |
c7e777255a | ||
![]() |
2dd4cf040e | ||
![]() |
d1b9eca5eb | ||
![]() |
594a67fe28 | ||
![]() |
cddeaffada | ||
![]() |
6b7b71b1f8 | ||
![]() |
2a8898e7c3 | ||
![]() |
ce3f3b09b4 | ||
![]() |
fe4b3df7e9 | ||
![]() |
25bdbcf526 | ||
![]() |
df7eaa5598 | ||
![]() |
bb7099376b | ||
![]() |
0327fd9710 | ||
![]() |
e645c6e465 | ||
![]() |
78a3d36ccc | ||
![]() |
3942858ccd | ||
![]() |
03c8d716cc | ||
![]() |
60181c4fcb | ||
![]() |
c215447405 | ||
![]() |
89330b89d8 | ||
![]() |
a8f3718ed0 | ||
![]() |
a78ba44709 | ||
![]() |
ff110e3513 | ||
![]() |
cfae6c63b5 | ||
![]() |
dbfe49c56f | ||
![]() |
98e21f9f5b | ||
![]() |
83af0497e4 | ||
![]() |
6ce37b44db | ||
![]() |
9cb1cf756f | ||
![]() |
ffa005e4ab | ||
![]() |
af102e47f1 | ||
![]() |
73064a816d | ||
![]() |
9b4ae8fcc5 | ||
![]() |
a1a2c52409 | ||
![]() |
9a0b26e0b0 | ||
![]() |
b805b96e16 | ||
![]() |
590e7f7724 | ||
![]() |
1a702b08b9 | ||
![]() |
8c52dfb804 | ||
![]() |
4d61e5e319 | ||
![]() |
8c8a63ebfb | ||
![]() |
e5e34797a8 | ||
![]() |
8516ebe6f5 | ||
![]() |
9f6205f47f | ||
![]() |
8b2ec23a89 | ||
![]() |
1816ca6b02 | ||
![]() |
7394ff9346 | ||
![]() |
bb5a6a1c28 | ||
![]() |
b614b06736 | ||
![]() |
7a376c9efc | ||
![]() |
518f3d229f | ||
![]() |
46c91f923d | ||
![]() |
3a2262dfb3 | ||
![]() |
ff7c38f8e9 | ||
![]() |
4229ba364f | ||
![]() |
ba8e7a211a | ||
![]() |
6b41653a32 | ||
![]() |
59c1125e72 | ||
![]() |
b536046720 | ||
![]() |
619b805894 | ||
![]() |
8662537883 | ||
![]() |
717890395b | ||
![]() |
b7b4164f4f | ||
![]() |
7e65296470 | ||
![]() |
cd5f5d702f | ||
![]() |
44b93e7cc4 | ||
![]() |
0eb79e5acd | ||
![]() |
eceba26894 | ||
![]() |
0bf404f75e | ||
![]() |
cd8dd65a65 | ||
![]() |
50c56f8b50 | ||
![]() |
9e9f8ca8f3 | ||
![]() |
f63af0601c | ||
![]() |
189c671ce2 | ||
![]() |
bb39a01361 | ||
![]() |
3c6a170138 | ||
![]() |
764999704a | ||
![]() |
ecfa4aafc1 | ||
![]() |
a1e33c4d2f | ||
![]() |
7f8ba74dac | ||
![]() |
e3df62d812 | ||
![]() |
1913125881 | ||
![]() |
e8e58f3fed | ||
![]() |
1ca9ec384b | ||
![]() |
9522255e3a | ||
![]() |
2a22fa694e | ||
![]() |
1591f5a0ca | ||
![]() |
c0c38022ea | ||
![]() |
3bc4e9a724 | ||
![]() |
f7a6bb0723 | ||
![]() |
e9c17a3ef7 | ||
![]() |
29bb5840b5 | ||
![]() |
c9d8d860f6 | ||
![]() |
cc18096882 | ||
![]() |
15f2a664d1 | ||
![]() |
93b66d26ff | ||
![]() |
70b4f62ddc | ||
![]() |
e1023fdfaf | ||
![]() |
5e9648387a | ||
![]() |
2ba8b4df67 | ||
![]() |
3a084c5d7b | ||
![]() |
f7200e39c3 | ||
![]() |
a7dfc20967 | ||
![]() |
6eb7c0b5d6 | ||
![]() |
0b3c078aeb | ||
![]() |
750872cc37 | ||
![]() |
29895ff474 | ||
![]() |
9be2844c82 | ||
![]() |
44adccc147 | ||
![]() |
2a7e2c70b5 | ||
![]() |
8d431b6762 | ||
![]() |
273849c0c8 | ||
![]() |
5cc14405c7 | ||
![]() |
f0cfd60e62 | ||
![]() |
875c687e3f | ||
![]() |
d6547f0701 | ||
![]() |
3b68905037 | ||
![]() |
eae611c54d | ||
![]() |
b37bad35c2 | ||
![]() |
5fab15fee5 | ||
![]() |
10c8ea17aa | ||
![]() |
7058c8ff5a | ||
![]() |
64e85da59f | ||
![]() |
f79fad64aa | ||
![]() |
cb70eebb08 | ||
![]() |
edaf8787d1 | ||
![]() |
24164c8580 | ||
![]() |
9fca7011aa | ||
![]() |
a0be47ab8b | ||
![]() |
b13eb3fd40 | ||
![]() |
b7986a351c | ||
![]() |
ce87591c62 | ||
![]() |
25c289ad3e | ||
![]() |
8c5f11b7dd | ||
![]() |
7f7dda9ec2 | ||
![]() |
9c1005ff0c | ||
![]() |
5b36b4472c | ||
![]() |
a3fcc64aaa | ||
![]() |
f3078bc903 | ||
![]() |
6072744f7e | ||
![]() |
40b6de599c | ||
![]() |
a87ad35a50 | ||
![]() |
cf56d7e4ed | ||
![]() |
e33a5eb307 | ||
![]() |
c1c6f55f8f | ||
![]() |
e5b704eb32 | ||
![]() |
56457bd325 | ||
![]() |
bdbb3c6657 | ||
![]() |
4c7e081e15 | ||
![]() |
c4d7001489 | ||
![]() |
c07bac9a63 | ||
![]() |
d27d04783f | ||
![]() |
58de5a7ec7 | ||
![]() |
504a9b4746 | ||
![]() |
cccb5a3e08 | ||
![]() |
d75fa62cab | ||
![]() |
3d43c3c5bc | ||
![]() |
b570b363d9 | ||
![]() |
09392be069 | ||
![]() |
5529dab84e | ||
![]() |
b9968aa1e6 | ||
![]() |
60ca704a9e | ||
![]() |
c0d77808f6 | ||
![]() |
9679492c28 | ||
![]() |
f3b68e6543 | ||
![]() |
0dcfaaf5ff | ||
![]() |
ba513dcb9a | ||
![]() |
ebabc60477 | ||
![]() |
cf565d0145 | ||
![]() |
52a23e7904 | ||
![]() |
9e22b80714 | ||
![]() |
7eed9c4a6d | ||
![]() |
bf42fce17e | ||
![]() |
9d421226a7 | ||
![]() |
7c4d5cee95 | ||
![]() |
7b9be8369e | ||
![]() |
7cf4b819ae | ||
![]() |
9e1aea33c3 | ||
![]() |
8767a88854 | ||
![]() |
47c0084641 | ||
![]() |
54e6a790cf | ||
![]() |
2a86bc8695 | ||
![]() |
04538372c6 | ||
![]() |
9430ed66cd | ||
![]() |
96f8efc27a | ||
![]() |
a90e8b6112 | ||
![]() |
561c1fb798 | ||
![]() |
806fec7017 | ||
![]() |
b3da28eade | ||
![]() |
166f6412c2 | ||
![]() |
1e877808bc | ||
![]() |
1777d9f751 | ||
![]() |
309b99eac0 | ||
![]() |
a5aa1b3917 | ||
![]() |
aced0632ec | ||
![]() |
4e801788d7 | ||
![]() |
0b4baad78b | ||
![]() |
c9286624d4 | ||
![]() |
201e32d4c4 | ||
![]() |
0980cb6eb5 | ||
![]() |
f75d23363b | ||
![]() |
6c0ba66f17 | ||
![]() |
f32ce7392e | ||
![]() |
193d160bed | ||
![]() |
8bf382adad | ||
![]() |
541ba357bb | ||
![]() |
b6578b52e3 | ||
![]() |
fb01c43ece | ||
![]() |
b9a012c6e3 | ||
![]() |
17684ed8a8 | ||
![]() |
1b6b3b2cd5 | ||
![]() |
acd8567586 | ||
![]() |
e780c76c93 | ||
![]() |
532c6caddf | ||
![]() |
ef8d9be633 | ||
![]() |
2cdbcc5666 | ||
![]() |
c282a8f328 | ||
![]() |
b9eab39541 | ||
![]() |
20903784a4 | ||
![]() |
3ec9ff7467 | ||
![]() |
1ddd746862 | ||
![]() |
17d3a87b1f | ||
![]() |
14c5c60863 | ||
![]() |
48ace3de57 | ||
![]() |
70a80090c4 | ||
![]() |
3395c84560 | ||
![]() |
b6cb5d09cb | ||
![]() |
94c2fc80d2 | ||
![]() |
69cfde4516 | ||
![]() |
bdc83da098 | ||
![]() |
f872a122a9 | ||
![]() |
aa92e4cbd0 | ||
![]() |
e603877a17 | ||
![]() |
bb96477779 | ||
![]() |
543ee79720 | ||
![]() |
371db886b4 | ||
![]() |
ea8cd98361 | ||
![]() |
3904ca38c0 | ||
![]() |
58849f28a8 | ||
![]() |
16527ceaf6 | ||
![]() |
d66c284bed | ||
![]() |
693848280b | ||
![]() |
396afaa181 | ||
![]() |
05ed29133b | ||
![]() |
a31c1e8084 | ||
![]() |
21891230f2 | ||
![]() |
47da76c5a5 | ||
![]() |
6017ff2318 | ||
![]() |
e16d604d0d | ||
![]() |
feec3e8255 | ||
![]() |
d3d5703f3f | ||
![]() |
62fe92d922 | ||
![]() |
512e7be74f | ||
![]() |
f799db67eb | ||
![]() |
3e106a9dc5 | ||
![]() |
727abbea8f | ||
![]() |
76f81ece62 | ||
![]() |
495654f9ff | ||
![]() |
a0de3fc643 | ||
![]() |
95fec2100e | ||
![]() |
623a879797 | ||
![]() |
4c96d23f48 | ||
![]() |
9bc8f6e9d7 | ||
![]() |
e00e6509ee | ||
![]() |
be5739508b | ||
![]() |
38c867ea94 | ||
![]() |
2a985ce6c0 | ||
![]() |
e4f3fb36f3 | ||
![]() |
b2f8792873 | ||
![]() |
2065133e2d | ||
![]() |
86da87f254 | ||
![]() |
102a7f8723 | ||
![]() |
e9afc15719 | ||
![]() |
08527dde9b | ||
![]() |
d9c3a3c9a9 | ||
![]() |
fe89f9e55e | ||
![]() |
73802aabac | ||
![]() |
bc66733289 | ||
![]() |
f4c93b2251 | ||
![]() |
c079c598f2 | ||
![]() |
8a2f0063d4 | ||
![]() |
dfe4b33f2f | ||
![]() |
2f7cfa7ab2 | ||
![]() |
bdcb813ee6 | ||
![]() |
f0751007f3 | ||
![]() |
6ad993704c | ||
![]() |
796c3009c7 | ||
![]() |
144ff5e716 | ||
![]() |
054a1e5ea4 | ||
![]() |
a223f6056e | ||
![]() |
a1fd7704e0 | ||
![]() |
b94227efc9 | ||
![]() |
3a7e782c07 | ||
![]() |
8f6b33d790 | ||
![]() |
f476daa041 | ||
![]() |
acfde9458d | ||
![]() |
82e969627a | ||
![]() |
9de3c582c0 | ||
![]() |
45cff2b51b | ||
![]() |
670397a73e | ||
![]() |
272eb37e9a | ||
![]() |
ca79e58ab9 | ||
![]() |
977c049875 | ||
![]() |
aefbc1c9bf | ||
![]() |
c37a2e61ed | ||
![]() |
7f6cd5e469 | ||
![]() |
f6d1f1985c | ||
![]() |
222c31b306 | ||
![]() |
e99185f011 | ||
![]() |
5c662f1230 | ||
![]() |
a65c7ee2fc | ||
![]() |
743c4f554d | ||
![]() |
838b2757eb | ||
![]() |
a92c9fc226 | ||
![]() |
ed052e0b0b | ||
![]() |
7bb8b9039c | ||
![]() |
3800b4b45c | ||
![]() |
cd498711bc | ||
![]() |
40766b3375 | ||
![]() |
d274e45587 | ||
![]() |
0a0eb3f710 | ||
![]() |
81d054a525 | ||
![]() |
dc9670c439 | ||
![]() |
2e185f4ec9 | ||
![]() |
67f347f880 | ||
![]() |
03c8079858 | ||
![]() |
0cfc527328 | ||
![]() |
81542fc6a8 | ||
![]() |
5aced279d6 | ||
![]() |
3f016f785f | ||
![]() |
a6427d081e | ||
![]() |
8c7fbe20f9 | ||
![]() |
f66a820e14 | ||
![]() |
469aba8ed0 | ||
![]() |
6e8e4ad5da | ||
![]() |
2f33d654e4 | ||
![]() |
760b6385f1 | ||
![]() |
91527500f9 | ||
![]() |
e87d989ca3 | ||
![]() |
64d61bae08 | ||
![]() |
9862265465 | ||
![]() |
624b7616d0 | ||
![]() |
d53f33bed8 | ||
![]() |
02e039d792 | ||
![]() |
9f9333315e | ||
![]() |
0d10b812fe | ||
![]() |
2e6bea23ac | ||
![]() |
b4fe4f3d10 | ||
![]() |
ba93fcbda0 | ||
![]() |
88d19a4ca4 | ||
![]() |
ca75dd0728 | ||
![]() |
af7b9ea898 | ||
![]() |
e103676b65 | ||
![]() |
09cd0468cf | ||
![]() |
529aa754f5 | ||
![]() |
17e395c2a8 | ||
![]() |
d50c1f39ab | ||
![]() |
ef6b25b3bb | ||
![]() |
3c7e865555 | ||
![]() |
7877ac0c3b | ||
![]() |
1442e29d0e | ||
![]() |
9a7e9b736e | ||
![]() |
c421e45fa0 | ||
![]() |
8833d21ac3 | ||
![]() |
1a3c522c94 | ||
![]() |
c55aa92d4f | ||
![]() |
212a303347 | ||
![]() |
3f3568d8af | ||
![]() |
1e3bcfc8cd | ||
![]() |
a4ce9f6f05 | ||
![]() |
65dc99744e | ||
![]() |
c6d4740b0c | ||
![]() |
9f91c8b59d | ||
![]() |
2b3b087c29 | ||
![]() |
e08d46aa76 | ||
![]() |
9f35fa0fa3 | ||
![]() |
ff48996bbe | ||
![]() |
feccc97a14 | ||
![]() |
77eec3d21d | ||
![]() |
ecaafd1b70 | ||
![]() |
0d51997e46 | ||
![]() |
463cbceb07 | ||
![]() |
2fe4d97061 | ||
![]() |
eb38393cad | ||
![]() |
1437c5c63f | ||
![]() |
52f1d50902 | ||
![]() |
a839cb787e | ||
![]() |
f621fb2060 | ||
![]() |
2ccd8b8838 | ||
![]() |
7ef0746c52 | ||
![]() |
6f609f0dd7 | ||
![]() |
ee2a30470a | ||
![]() |
e11fb2c09e | ||
![]() |
c6e9270590 | ||
![]() |
3e2e171407 | ||
![]() |
e205969b11 | ||
![]() |
332f531a10 | ||
![]() |
6bf19ecc34 | ||
![]() |
bae2c9bc63 | ||
![]() |
5ac68f8df8 | ||
![]() |
6ff45a754d | ||
![]() |
06d3b94804 | ||
![]() |
32d2df0f08 | ||
![]() |
e7c314fefc | ||
![]() |
faab79b41a | ||
![]() |
14204c9bfc | ||
![]() |
45dbd4464b | ||
![]() |
472255924a | ||
![]() |
6d3ac2aa55 | ||
![]() |
9ad03994d1 | ||
![]() |
35228f80b8 | ||
![]() |
69ded881c6 | ||
![]() |
d9bce45db4 | ||
![]() |
5e92b4faa9 | ||
![]() |
db501822ef | ||
![]() |
f269695d4a | ||
![]() |
443af5f760 | ||
![]() |
0e35350160 | ||
![]() |
10bf497cda | ||
![]() |
76eb629fc2 | ||
![]() |
91de738563 | ||
![]() |
ef9948a967 | ||
![]() |
0cf13f6393 | ||
![]() |
4a8acfd123 | ||
![]() |
abaffc1908 | ||
![]() |
ea61d5c1a5 | ||
![]() |
298f09402f | ||
![]() |
d4149d4b7a | ||
![]() |
9a14931175 | ||
![]() |
165eee102a | ||
![]() |
6900c197cd | ||
![]() |
fe3c66a7c8 | ||
![]() |
1d728475e3 | ||
![]() |
827057b9f1 | ||
![]() |
1716452203 | ||
![]() |
54827cacb9 | ||
![]() |
e3a4a16507 | ||
![]() |
3315228a90 | ||
![]() |
f72205c401 | ||
![]() |
11862bbaee | ||
![]() |
ef1d1303f4 | ||
![]() |
8d846993ee | ||
![]() |
1f84626278 | ||
![]() |
b4cfe6e9c0 | ||
![]() |
94a861e318 | ||
![]() |
1421e775d2 | ||
![]() |
f8eab72c7a | ||
![]() |
2afd2f0d3b | ||
![]() |
2b72f40cec | ||
![]() |
ff5c0d6361 | ||
![]() |
edd4b477f8 | ||
![]() |
198b14e5fc | ||
![]() |
f9fea265cf | ||
![]() |
668601ca23 | ||
![]() |
04fcb33d7e | ||
![]() |
99406f2099 | ||
![]() |
632b3cb9ae | ||
![]() |
f31d2486c9 | ||
![]() |
7dea682713 | ||
![]() |
7955ddceb2 | ||
![]() |
0bf04c04f9 | ||
![]() |
dc29018ec0 | ||
![]() |
b6412afe96 | ||
![]() |
4e88186903 | ||
![]() |
f387378b69 | ||
![]() |
f894e6b4ea | ||
![]() |
e33f5996f3 | ||
![]() |
8f7f1ff7dd | ||
![]() |
54a0e52e05 | ||
![]() |
b2431b982f | ||
![]() |
8d6d619eed | ||
![]() |
70e332b9e8 | ||
![]() |
0a53c42a8a | ||
![]() |
42a66ad49e | ||
![]() |
2d1d70b3b6 | ||
![]() |
0ccc92dc1e | ||
![]() |
c9157cc13b | ||
![]() |
2b1270381d | ||
![]() |
cdb8ee3946 | ||
![]() |
1e3586621b | ||
![]() |
c07e9ac29d | ||
![]() |
6e3bb48574 | ||
![]() |
a180395832 | ||
![]() |
3dfcc6b0be | ||
![]() |
16d7ae62bd | ||
![]() |
eea3cb32a5 | ||
![]() |
670fe8590c | ||
![]() |
30c048723c | ||
![]() |
85dc669ddf | ||
![]() |
397c1a1c2b | ||
![]() |
f1d3e35aac | ||
![]() |
0e69201f05 | ||
![]() |
f8fdaf5c1f | ||
![]() |
1f3b81338c | ||
![]() |
5921d3a42a | ||
![]() |
dbbc85719e | ||
![]() |
0ddb6c3f10 | ||
![]() |
f760a9d0c2 | ||
![]() |
e13281726c | ||
![]() |
0ddf4355a1 | ||
![]() |
7c8a3ca1a8 | ||
![]() |
3068738a70 | ||
![]() |
cfa0d8b7c0 | ||
![]() |
7ac41652f7 | ||
![]() |
24a510bc2e | ||
![]() |
0498540439 | ||
![]() |
da94c2e1e5 | ||
![]() |
bcdd74514f | ||
![]() |
1d0c36a0ab | ||
![]() |
be1dcb7264 | ||
![]() |
a34ea8f131 | ||
![]() |
7fbfa6a52b | ||
![]() |
799ef3380d | ||
![]() |
d5087858ca | ||
![]() |
d9fc5650b8 | ||
![]() |
9ea028f5ab | ||
![]() |
aa309087fd | ||
![]() |
57bdd9d3bf | ||
![]() |
dc9871fe5b | ||
![]() |
3255ca3ea4 | ||
![]() |
a06ef6fe25 | ||
![]() |
696d256fa0 | ||
![]() |
70e8ad7104 | ||
![]() |
f785dcac3d | ||
![]() |
aa54ef10ae | ||
![]() |
14946da163 | ||
![]() |
5f9bcfbefe | ||
![]() |
aa2eed2c38 | ||
![]() |
6bff6e9cff | ||
![]() |
023d369b74 | ||
![]() |
c9d4241afe | ||
![]() |
e1279c29c2 | ||
![]() |
2d6fb1c45e | ||
![]() |
cd9643fb15 | ||
![]() |
f64f95d8ff | ||
![]() |
88c25a9390 | ||
![]() |
e4ace49536 | ||
![]() |
3a0df56605 | ||
![]() |
98cdee7f03 | ||
![]() |
f140f5f14b | ||
![]() |
88a97319cc | ||
![]() |
c69db035ee | ||
![]() |
60a7eaf2bb | ||
![]() |
3f43567c8f | ||
![]() |
0f12a9de3b | ||
![]() |
937b9009e0 | ||
![]() |
66b6098d32 | ||
![]() |
348bc1d0fc | ||
![]() |
b56a757f2e | ||
![]() |
f7c0499158 | ||
![]() |
9ebcefee00 | ||
![]() |
957e319649 | ||
![]() |
a8978a0d4d | ||
![]() |
10712c5ec0 | ||
![]() |
83c39f57f0 | ||
![]() |
173757cfa2 | ||
![]() |
c6be73dba2 | ||
![]() |
ccf293906a | ||
![]() |
0f4c0b95e2 | ||
![]() |
82973e7608 | ||
![]() |
c011bccc45 | ||
![]() |
8473caf5a6 | ||
![]() |
85b038525b | ||
![]() |
51a5c3c664 | ||
![]() |
d6cda9df0a | ||
![]() |
ca7d09d1cb | ||
![]() |
4ab478c49c | ||
![]() |
1a1c1fd0da | ||
![]() |
370951ab67 | ||
![]() |
a0632a572a | ||
![]() |
10601e7760 | ||
![]() |
088ce9c2ad | ||
![]() |
e1a69b97db | ||
![]() |
a2fd45bb95 | ||
![]() |
01ddd8eaa8 | ||
![]() |
22fa57b82c | ||
![]() |
92a51ca546 | ||
![]() |
6a9234e634 | ||
![]() |
e8d062a95a | ||
![]() |
3394d64f6c | ||
![]() |
0fd5a277ed | ||
![]() |
8eef2818fa | ||
![]() |
a15703d5af | ||
![]() |
34d8165edd | ||
![]() |
1759add2b6 | ||
![]() |
dd80f1b997 | ||
![]() |
90ff602ecd | ||
![]() |
0099ff1321 |
18
.gitattributes
vendored
Normal file
18
.gitattributes
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
* text eol=lf
|
||||
|
||||
# Explicitly declare text files you want to always be normalized and converted
|
||||
# to native line endings on checkout.
|
||||
# *.c text
|
||||
# *.h text
|
||||
|
||||
# Declare files that will always have CRLF line endings on checkout.
|
||||
*.cmd text eol=crlf
|
||||
*.bat text eol=crlf
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
chromeos/** binary
|
||||
*.jar binary
|
||||
*.exe binary
|
||||
*.apk binary
|
||||
*.png binary
|
24
.gitignore
vendored
24
.gitignore
vendored
@@ -1,12 +1,20 @@
|
||||
out
|
||||
*.zip
|
||||
*.jks
|
||||
*.apk
|
||||
config.prop
|
||||
|
||||
# Manually dumped jars
|
||||
snet/libs
|
||||
|
||||
# Built binaries
|
||||
native/out
|
||||
|
||||
# Android Studio / Gradle
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
.idea/
|
||||
/.idea
|
||||
/build
|
||||
app/release
|
||||
*.hprof
|
||||
.externalNativeBuild/
|
||||
src/full/res/raw/util_functions.sh
|
||||
public.certificate.x509.pem
|
||||
private.key.pk8
|
||||
*.apk
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
|
21
.gitmodules
vendored
Normal file
21
.gitmodules
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
[submodule "selinux"]
|
||||
path = native/jni/external/selinux
|
||||
url = https://github.com/topjohnwu/selinux.git
|
||||
[submodule "busybox"]
|
||||
path = native/jni/external/busybox
|
||||
url = https://github.com/topjohnwu/ndk-busybox.git
|
||||
[submodule "dtc"]
|
||||
path = native/jni/external/dtc
|
||||
url = https://github.com/dgibson/dtc
|
||||
[submodule "lz4"]
|
||||
path = native/jni/external/lz4
|
||||
url = https://github.com/lz4/lz4.git
|
||||
[submodule "bzip2"]
|
||||
path = native/jni/external/bzip2
|
||||
url = https://github.com/nemequ/bzip2.git
|
||||
[submodule "xz"]
|
||||
path = native/jni/external/xz
|
||||
url = https://github.com/xz-mirror/xz.git
|
||||
[submodule "nanopb"]
|
||||
path = native/jni/external/nanopb
|
||||
url = https://github.com/nanopb/nanopb.git
|
674
LICENSE
Normal file
674
LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
35
README.MD
Normal file
35
README.MD
Normal file
@@ -0,0 +1,35 @@
|
||||
# Magisk
|
||||
|
||||
## Building Environment Requirements
|
||||
|
||||
1. Python 3.5+: run `build.py` script
|
||||
2. Java Development Kit (JDK) 8: Compile Magisk Manager and sign zips
|
||||
3. Latest Android SDK: set `ANDROID_HOME` environment variable to the path to Android SDK
|
||||
4. Android NDK: Install NDK along with SDK (`$ANDROID_HOME/ndk-bundle`), or optionally specify a custom path `ANDROID_NDK_HOME`
|
||||
5. (Windows Only) Python package Colorama: Install with `pip install colorama`, used for ANSI color codes
|
||||
|
||||
## Building Notes and Instructions
|
||||
1. Building is supported on macOS, Linux, and Windows using the custom NDK: [FrankeNDK](https://github.com/topjohnwu/FrankeNDK).
|
||||
2. Set configurations in `config.prop`. A sample file `config.prop.sample` is provided as an example.
|
||||
3. Run `build.py` with argument `-h` to see the built-in help message. The `-h` option also works for each supported actions, e.g. `./build.py binary -h`
|
||||
4. By default, `build.py` build binaries and Magisk Manager in debug mode. If you want to build Magisk Manager in release mode (via the `-r, --release` flag), you need a Java Keystore file `release-key.jks` (only `JKS` format is supported) to sign APKs and zips. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually).
|
||||
|
||||
## Documentation
|
||||
[Link to Documentation](docs/README.MD)
|
||||
|
||||
## License
|
||||
|
||||
```
|
||||
Magisk, including all git submodules are free software:
|
||||
you can redistribute it and/or modify it under the terms of the
|
||||
GNU General Public License as published by the Free Software Foundation,
|
||||
either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
```
|
@@ -1,7 +0,0 @@
|
||||
# Magisk Manager
|
||||
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.
|
12
app/.gitignore
vendored
Normal file
12
app/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
.idea/
|
||||
/build
|
||||
app/release
|
||||
*.hprof
|
||||
.externalNativeBuild/
|
||||
src/full/res/raw/util_functions.sh
|
||||
public.certificate.x509.pem
|
||||
private.key.pk8
|
||||
*.apk
|
7
app/README.md
Normal file
7
app/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Magisk Manager
|
||||
This repo is no longer an independent component. It is merged into the [Magisk Project](https://github.com/topjohnwu/Magisk).
|
||||
|
||||
# Translations
|
||||
The default (English) strings are mainly in `src/full/res/values/strings.xml`; some are scattered in `src/main/res/values/strings.xml` and `src/stub/res/values/strings.xml`.
|
||||
Translations are highly appreciated via pull requests here on Github.
|
||||
Place translated XMLs in the corresponding locale folder.
|
83
app/build.gradle
Normal file
83
app/build.gradle
Normal file
@@ -0,0 +1,83 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
def configProps = new Properties()
|
||||
configProps.load(new FileInputStream(rootProject.file('config.prop')))
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.topjohnwu.magisk"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion rootProject.ext.compileSdkVersion
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
config {
|
||||
storeFile rootProject.file('release-key.jks')
|
||||
storePassword configProps['keyStorePass']
|
||||
keyAlias configProps['keyAlias']
|
||||
keyPassword configProps['keyPass']
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
// If keystore exists, sign the APK with custom signature
|
||||
if (signingConfigs.config.storeFile.exists())
|
||||
signingConfig signingConfigs.config
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.config
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "mode"
|
||||
|
||||
productFlavors {
|
||||
full {
|
||||
versionName configProps['appVersion']
|
||||
versionCode configProps['appVersionCode'] as Integer
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
argument('butterknife.debuggable', 'false')
|
||||
}
|
||||
}
|
||||
}
|
||||
stub {
|
||||
versionCode 1
|
||||
versionName "stub"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
dexOptions {
|
||||
preDexLibraries true
|
||||
javaMaxHeapSize "2g"
|
||||
}
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
}
|
||||
|
||||
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:2.0.1'
|
||||
fullImplementation 'com.atlassian.commonmark:commonmark:0.11.0'
|
||||
fullImplementation 'org.kamranzafar:jtar:2.3'
|
||||
fullImplementation 'com.jakewharton:butterknife:8.8.1'
|
||||
fullAnnotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
|
||||
}
|
11
proguard-rules.pro → app/proguard-rules.pro
vendored
11
proguard-rules.pro → app/proguard-rules.pro
vendored
@@ -16,8 +16,8 @@
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Keep all names, we are open source anyway :)
|
||||
-keepnames class ** { *; }
|
||||
# Don't obfuscate, we are open source anyway :)
|
||||
-dontobfuscate
|
||||
|
||||
# BouncyCastle
|
||||
-keep class org.bouncycastle.jcajce.provider.asymmetric.rsa.**SHA1** { *; }
|
||||
@@ -26,4 +26,9 @@
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
# Gson
|
||||
-keepattributes Signature
|
||||
-keepattributes Signature
|
||||
|
||||
# Strip logging
|
||||
-assumenosideeffects class com.topjohnwu.magisk.utils.Logger {
|
||||
public *** debug(...);
|
||||
}
|
@@ -5,6 +5,7 @@
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:name=".MagiskManager"
|
||||
@@ -28,8 +29,8 @@
|
||||
android:name=".AboutActivity"
|
||||
android:theme="@style/AppTheme.StatusBar" />
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:theme="@style/AppTheme.StatusBar" />
|
||||
android:name=".DonationActivity"
|
||||
android:theme="@style/AppTheme.StatusBar"/>
|
||||
<activity
|
||||
android:name=".FlashActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
@@ -66,7 +67,10 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name=".services.OnBootIntentService" />
|
||||
<service
|
||||
android:name=".services.OnBootService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
<service
|
||||
android:name=".services.UpdateCheckService"
|
||||
android:exported="true"
|
||||
@@ -75,7 +79,7 @@
|
||||
<!-- Hardcode GMS version -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="7095000" />
|
||||
android:value="12451000" />
|
||||
|
||||
</application>
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
@@ -11,15 +10,15 @@ 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 com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class AboutActivity extends Activity {
|
||||
public class AboutActivity extends BaseActivity {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.app_version_info) AboutCardRow appVersionInfo;
|
||||
@@ -27,7 +26,7 @@ public class AboutActivity extends Activity {
|
||||
@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;
|
||||
@BindView(R.id.follow_twitter) AboutCardRow twitter;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
@@ -52,7 +51,6 @@ public class AboutActivity extends Activity {
|
||||
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();
|
||||
@@ -65,14 +63,9 @@ public class AboutActivity extends Activity {
|
||||
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))));
|
||||
appSourceCode.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.SOURCE_CODE_URL)));
|
||||
supportThread.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.XDA_THREAD)));
|
||||
twitter.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.TWITTER_URL)));
|
||||
|
||||
setFloating();
|
||||
}
|
@@ -1,11 +1,7 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.os.Environment;
|
||||
import android.os.Process;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -34,21 +30,19 @@ public class Const {
|
||||
public static final String BUSYBOX_PATH = "/sbin/.core/busybox";
|
||||
public static final String TMP_FOLDER_PATH = "/dev/tmp";
|
||||
public static final String MAGISK_LOG = "/cache/magisk.log";
|
||||
public static final File EXTERNAL_PATH = new File(Environment.getExternalStorageDirectory(), "MagiskManager");
|
||||
public static final String MANAGER_CONFIGS = ".tmp.magisk.config";
|
||||
|
||||
// Versions
|
||||
public static final int UPDATE_SERVICE_VER = 1;
|
||||
public static final int SNET_VER = 8;
|
||||
|
||||
public static int MIN_MODULE_VER() {
|
||||
return MagiskManager.get().magiskVersionCode >= MAGISK_VER.REMOVE_LEGACY_LINK ? 1500 : 1400;
|
||||
return Data.magiskVersionCode >= MAGISK_VER.REMOVE_LEGACY_LINK ? 1500 : 1400;
|
||||
}
|
||||
|
||||
/* A list of apps that should not be shown as hide-able */
|
||||
public static final List<String> HIDE_BLACKLIST = Arrays.asList(
|
||||
"android",
|
||||
MagiskManager.get().getPackageName(),
|
||||
Data.MM().getPackageName(),
|
||||
"com.google.android.gms"
|
||||
);
|
||||
|
||||
@@ -59,7 +53,6 @@ public class Const {
|
||||
public static final int FBE_AWARE = 1410;
|
||||
public static final int RESETPROP_PERSIST = 1436;
|
||||
public static final int MANAGER_HIDE = 1440;
|
||||
public static final int DTBO_SUPPORT = 1446;
|
||||
public static final int HIDDEN_PATH = 1460;
|
||||
public static final int REMOVE_LEGACY_LINK = 1630;
|
||||
public static final int SEPOL_REFACTOR = 1640;
|
||||
@@ -70,25 +63,26 @@ public class Const {
|
||||
public static final int UPDATE_SERVICE_ID = 1;
|
||||
public static final int FETCH_ZIP = 2;
|
||||
public static final int SELECT_BOOT = 3;
|
||||
public static final int ONBOOT_SERVICE_ID = 6;
|
||||
|
||||
// notifications
|
||||
public static final int MAGISK_UPDATE_NOTIFICATION_ID = 4;
|
||||
public static final int APK_UPDATE_NOTIFICATION_ID = 5;
|
||||
public static final int ONBOOT_NOTIFICATION_ID = 6;
|
||||
public static final int DTBO_NOTIFICATION_ID = 7;
|
||||
public static final String NOTIFICATION_CHANNEL = "magisk_notification";
|
||||
}
|
||||
|
||||
public static class Url {
|
||||
public static final String STABLE_URL = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/update/stable.json";
|
||||
public static final String BETA_URL = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/update/beta.json";
|
||||
public static final String SNET_URL = "https://github.com/topjohnwu/MagiskManager/raw/788f01f499714471949613820d43bc364786e85d/snet.apk";
|
||||
public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&page=%d";
|
||||
public static final String STABLE_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/stable.json";
|
||||
public static final String BETA_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/beta.json";
|
||||
public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed&page=%d";
|
||||
public static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s";
|
||||
public static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
|
||||
public static final String DONATION_URL = "https://www.paypal.me/topjohnwu";
|
||||
public static final String PAYPAL_URL = "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=CC7FZ7526MNGG";
|
||||
public static final String PATREON_URL = "https://www.patreon.com/topjohnwu";
|
||||
public static final String TWITTER_URL = "https://twitter.com/topjohnwu";
|
||||
public static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382";
|
||||
public static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/MagiskManager";
|
||||
public static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk";
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +102,6 @@ public class Const {
|
||||
public static final String OPEN_SECTION = "section";
|
||||
public static final String INTENT_SET_FILENAME = "filename";
|
||||
public static final String INTENT_SET_LINK = "link";
|
||||
public static final String INTENT_PERM = "perm_dialog";
|
||||
public static final String FLASH_ACTION = "action";
|
||||
public static final String FLASH_SET_BOOT = "boot";
|
||||
|
||||
@@ -156,7 +149,7 @@ public class Const {
|
||||
public static final String FLASH_ZIP = "flash";
|
||||
public static final String PATCH_BOOT = "patch";
|
||||
public static final String FLASH_MAGISK = "magisk";
|
||||
public static final String FLASH_SECOND_SLOT = "slot";
|
||||
public static final String FLASH_INACTIVE_SLOT = "slot";
|
||||
public static final String UNINSTALL = "uninstall";
|
||||
public static final int[] timeoutList = {0, -1, 10, 20, 30, 60};
|
||||
public static final int ORDER_NAME = 0;
|
187
app/src/full/java/com/topjohnwu/magisk/Data.java
Normal file
187
app/src/full/java/com/topjohnwu/magisk/Data.java
Normal file
@@ -0,0 +1,187 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
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;
|
||||
|
||||
public class Data {
|
||||
// Global app instance
|
||||
public static WeakReference<MagiskManager> weakApp;
|
||||
public static Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
// Current status
|
||||
public static String magiskVersionString;
|
||||
public static int magiskVersionCode = -1;
|
||||
public static boolean magiskHide;
|
||||
|
||||
// Update Info
|
||||
public static String remoteMagiskVersionString;
|
||||
public static int remoteMagiskVersionCode = -1;
|
||||
public static String magiskLink;
|
||||
public static String magiskNoteLink;
|
||||
public static String magiskMD5;
|
||||
public static String remoteManagerVersionString;
|
||||
public static int remoteManagerVersionCode = -1;
|
||||
public static String managerLink;
|
||||
public static String managerNoteLink;
|
||||
public static String uninstallerLink;
|
||||
public static int snetVersionCode;
|
||||
public static String snetLink;
|
||||
|
||||
// Install flags
|
||||
public static boolean keepVerity = false;
|
||||
public static boolean keepEnc = false;
|
||||
|
||||
// Configs
|
||||
public static boolean isDarkTheme;
|
||||
public static int suRequestTimeout;
|
||||
public static int suLogTimeout = 14;
|
||||
public static int suAccessState;
|
||||
public static boolean suFingerprint;
|
||||
public static int multiuserMode;
|
||||
public static int suResponseType;
|
||||
public static int suNotificationType;
|
||||
public static int suNamespaceMode;
|
||||
public static int updateChannel;
|
||||
public static int repoOrder;
|
||||
|
||||
public static 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.isEmpty() || Integer.parseInt(s) != 0;
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
public static MagiskManager MM() {
|
||||
return weakApp.get();
|
||||
}
|
||||
|
||||
public static void exportPrefs() {
|
||||
// Flush prefs to disk
|
||||
MagiskManager mm = MM();
|
||||
mm.prefs.edit().commit();
|
||||
File xml = new File(mm.getFilesDir().getParent() + "/shared_prefs",
|
||||
mm.getPackageName() + "_preferences.xml");
|
||||
Shell.su(Utils.fmt("for usr in /data/user/*; do cat %s > ${usr}/%s; done", xml, Const.MANAGER_CONFIGS)).exec();
|
||||
}
|
||||
|
||||
public static void importPrefs() {
|
||||
MagiskManager mm = MM();
|
||||
SuFile config = new SuFile(Utils.fmt("/data/user/%d/%s", Const.USER_ID, Const.MANAGER_CONFIGS));
|
||||
if (config.exists()) {
|
||||
SharedPreferences.Editor editor = mm.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();
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadConfig() {
|
||||
MagiskManager mm = MM();
|
||||
// su
|
||||
suRequestTimeout = Utils.getPrefsInt(mm.prefs, Const.Key.SU_REQUEST_TIMEOUT, Const.Value.timeoutList[2]);
|
||||
suResponseType = Utils.getPrefsInt(mm.prefs, Const.Key.SU_AUTO_RESPONSE, Const.Value.SU_PROMPT);
|
||||
suNotificationType = Utils.getPrefsInt(mm.prefs, Const.Key.SU_NOTIFICATION, Const.Value.NOTIFICATION_TOAST);
|
||||
suAccessState = mm.mDB.getSettings(Const.Key.ROOT_ACCESS, Const.Value.ROOT_ACCESS_APPS_AND_ADB);
|
||||
multiuserMode = mm.mDB.getSettings(Const.Key.SU_MULTIUSER_MODE, Const.Value.MULTIUSER_MODE_OWNER_ONLY);
|
||||
suNamespaceMode = mm.mDB.getSettings(Const.Key.SU_MNT_NS, Const.Value.NAMESPACE_MODE_REQUESTER);
|
||||
suFingerprint = mm.mDB.getSettings(Const.Key.SU_FINGERPRINT, 0) != 0;
|
||||
if (suFingerprint && !FingerprintHelper.canUseFingerprint()) {
|
||||
// User revoked the fingerprint
|
||||
mm.mDB.setSettings(Const.Key.SU_FINGERPRINT, 0);
|
||||
suFingerprint = false;
|
||||
}
|
||||
|
||||
// config
|
||||
isDarkTheme = mm.prefs.getBoolean(Const.Key.DARK_THEME, false);
|
||||
updateChannel = Utils.getPrefsInt(mm.prefs, Const.Key.UPDATE_CHANNEL, Const.Value.STABLE_CHANNEL);
|
||||
repoOrder = mm.prefs.getInt(Const.Key.REPO_ORDER, Const.Value.ORDER_DATE);
|
||||
}
|
||||
|
||||
public static void writeConfig() {
|
||||
MM().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())
|
||||
.putBoolean(Const.Key.SU_FINGERPRINT, suFingerprint)
|
||||
.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))
|
||||
.putInt(Const.Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
|
||||
.putInt(Const.Key.REPO_ORDER, repoOrder)
|
||||
.apply();
|
||||
}
|
||||
}
|
45
app/src/full/java/com/topjohnwu/magisk/DonationActivity.java
Normal file
45
app/src/full/java/com/topjohnwu/magisk/DonationActivity.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
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 com.topjohnwu.magisk.components.AboutCardRow;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class DonationActivity extends BaseActivity {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.paypal) AboutCardRow paypal;
|
||||
@BindView(R.id.patreon) AboutCardRow patreon;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_StatusBar_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_donation);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
toolbar.setNavigationOnClickListener(view -> finish());
|
||||
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setTitle(R.string.donation);
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
paypal.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.PAYPAL_URL)));
|
||||
patreon.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.PATREON_URL)));
|
||||
}
|
||||
}
|
@@ -1,9 +1,9 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.Manifest;
|
||||
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;
|
||||
@@ -16,9 +16,10 @@ 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.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.CallbackList;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
@@ -34,7 +35,7 @@ import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class FlashActivity extends Activity {
|
||||
public class FlashActivity extends BaseActivity {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.txtLog) TextView flashLogs;
|
||||
@@ -51,30 +52,31 @@ public class FlashActivity extends Activity {
|
||||
|
||||
@OnClick(R.id.reboot)
|
||||
void reboot() {
|
||||
Shell.Async.su("/system/bin/reboot");
|
||||
Shell.su("/system/bin/reboot").submit();
|
||||
}
|
||||
|
||||
@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));
|
||||
runWithPermission(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, () -> {
|
||||
Calendar now = Calendar.getInstance();
|
||||
String filename = String.format(Locale.US,
|
||||
"magisk_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');
|
||||
File logFile = new File(Download.EXTERNAL_PATH, filename);
|
||||
try (FileWriter writer = new FileWriter(logFile)) {
|
||||
for (String s : logs) {
|
||||
writer.write(s);
|
||||
writer.write('\n');
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
MagiskManager.toast(logFile.getPath(), Toast.LENGTH_LONG);
|
||||
Utils.toast(logFile.getPath(), Toast.LENGTH_LONG);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,11 +101,23 @@ public class FlashActivity extends Activity {
|
||||
|
||||
logs = new ArrayList<>();
|
||||
CallbackList<String> console = new CallbackList<String>(new ArrayList<>()) {
|
||||
|
||||
private void updateUI() {
|
||||
flashLogs.setText(TextUtils.join("\n", this));
|
||||
sv.postDelayed(() -> sv.fullScroll(ScrollView.FOCUS_DOWN), 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddElement(String s) {
|
||||
logs.add(s);
|
||||
flashLogs.setText(TextUtils.join("\n", this));
|
||||
sv.postDelayed(() -> sv.fullScroll(ScrollView.FOCUS_DOWN), 10);
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String set(int i, String s) {
|
||||
String ret = super.set(i, s);
|
||||
Data.mainHandler.post(this::updateUI);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -119,13 +133,13 @@ public class FlashActivity extends Activity {
|
||||
new UninstallMagisk(this, uri, console, logs).exec();
|
||||
break;
|
||||
case Const.Value.FLASH_MAGISK:
|
||||
new InstallMagisk(this, console, logs, uri, InstallMagisk.DIRECT_MODE).exec();
|
||||
new InstallMagisk(this, console, logs, InstallMagisk.DIRECT_MODE).exec();
|
||||
break;
|
||||
case Const.Value.FLASH_SECOND_SLOT:
|
||||
new InstallMagisk(this, console, logs, uri, InstallMagisk.SECOND_SLOT_MODE).exec();
|
||||
case Const.Value.FLASH_INACTIVE_SLOT:
|
||||
new InstallMagisk(this, console, logs, InstallMagisk.SECOND_SLOT_MODE).exec();
|
||||
break;
|
||||
case Const.Value.PATCH_BOOT:
|
||||
new InstallMagisk(this, console, logs, uri,
|
||||
new InstallMagisk(this, console, logs,
|
||||
intent.getParcelableExtra(Const.Key.FLASH_SET_BOOT)).exec();
|
||||
break;
|
||||
}
|
||||
@@ -138,14 +152,14 @@ public class FlashActivity extends Activity {
|
||||
|
||||
private static class UninstallMagisk extends FlashZip {
|
||||
|
||||
private UninstallMagisk(Activity context, Uri uri, List<String> console, List<String> logs) {
|
||||
private UninstallMagisk(BaseActivity 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(() ->
|
||||
Data.mainHandler.postDelayed(() ->
|
||||
RootUtils.uninstallPkg(getActivity().getPackageName()), 3000);
|
||||
} else {
|
||||
super.onPostExecute(result);
|
65
app/src/full/java/com/topjohnwu/magisk/MagiskManager.java
Normal file
65
app/src/full/java/com/topjohnwu/magisk/MagiskManager.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.database.MagiskDatabaseHelper;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.superuser.ContainerApp;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class MagiskManager extends ContainerApp {
|
||||
|
||||
// Info
|
||||
public boolean hasInit = false;
|
||||
|
||||
// Global resources
|
||||
public SharedPreferences prefs;
|
||||
public MagiskDatabaseHelper mDB;
|
||||
public RepoDatabaseHelper repoDB;
|
||||
|
||||
public MagiskManager() {
|
||||
Data.weakApp = new WeakReference<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER);
|
||||
Shell.Config.verboseLogging(BuildConfig.DEBUG);
|
||||
Shell.Config.setInitializer(RootUtils.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);
|
||||
Shell.su("pm uninstall " + pkg).exec();
|
||||
}
|
||||
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) {}
|
||||
}
|
||||
|
||||
LocaleManager.setLocale(this);
|
||||
Data.loadConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
LocaleManager.setLocale(this);
|
||||
}
|
||||
}
|
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -15,24 +14,30 @@ 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.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.fragments.LogFragment;
|
||||
import com.topjohnwu.magisk.fragments.MagiskFragment;
|
||||
import com.topjohnwu.magisk.fragments.MagiskHideFragment;
|
||||
import com.topjohnwu.magisk.fragments.ModulesFragment;
|
||||
import com.topjohnwu.magisk.fragments.ReposFragment;
|
||||
import com.topjohnwu.magisk.fragments.SettingsFragment;
|
||||
import com.topjohnwu.magisk.fragments.SuperuserFragment;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
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
|
||||
public class MainActivity extends BaseActivity
|
||||
implements NavigationView.OnNavigationItemSelectedListener, Topic.Subscriber {
|
||||
|
||||
private final Handler mDrawerHandler = new Handler();
|
||||
private int mDrawerItem;
|
||||
private boolean fromShortcut = true;
|
||||
private static boolean fromShortcut = false;
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.drawer_layout) DrawerLayout drawer;
|
||||
@BindView(R.id.toolbar) public Toolbar toolbar;
|
||||
@BindView(R.id.nav_view) public NavigationView navigationView;
|
||||
|
||||
private float toolbarElevation;
|
||||
@@ -44,24 +49,11 @@ public class MainActivity extends Activity
|
||||
|
||||
@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);
|
||||
startActivity(new Intent(this, SplashActivity.class));
|
||||
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);
|
||||
@@ -86,8 +78,11 @@ public class MainActivity extends Activity
|
||||
drawer.addDrawerListener(toggle);
|
||||
toggle.syncState();
|
||||
|
||||
if (savedInstanceState == null)
|
||||
navigate(getIntent().getStringExtra(Const.Key.OPEN_SECTION));
|
||||
if (savedInstanceState == null) {
|
||||
String section = getIntent().getStringExtra(Const.Key.OPEN_SECTION);
|
||||
fromShortcut = section != null;
|
||||
navigate(section);
|
||||
}
|
||||
|
||||
navigationView.setNavigationItemSelectedListener(this);
|
||||
}
|
||||
@@ -118,28 +113,26 @@ public class MainActivity extends Activity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
recreate();
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.RELOAD_ACTIVITY};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { getMagiskManager().reloadActivity };
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
recreate();
|
||||
}
|
||||
|
||||
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.magiskhide).setVisible(Shell.rootAccess() &&
|
||||
Data.magiskVersionCode >= Const.MAGISK_VER.UNIFIED &&
|
||||
mm.prefs.getBoolean(Const.Key.MAGISKHIDE, false));
|
||||
menu.findItem(R.id.modules).setVisible(Shell.rootAccess() && Data.magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.downloads).setVisible(Download.checkNetworkStatus(this)
|
||||
&& Shell.rootAccess() && Data.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));
|
||||
!(Const.USER_ID > 0 && Data.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED));
|
||||
}
|
||||
|
||||
public void navigate(String item) {
|
||||
@@ -167,6 +160,9 @@ public class MainActivity extends Activity
|
||||
case "about":
|
||||
itemId = R.id.app_about;
|
||||
break;
|
||||
case "donation":
|
||||
itemId = R.id.donation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
navigate(itemId);
|
||||
@@ -197,13 +193,16 @@ public class MainActivity extends Activity
|
||||
displayFragment(new LogFragment(), false);
|
||||
break;
|
||||
case R.id.settings:
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
mDrawerItem = bak;
|
||||
displayFragment(new SettingsFragment(), true);
|
||||
break;
|
||||
case R.id.app_about:
|
||||
startActivity(new Intent(this, AboutActivity.class));
|
||||
mDrawerItem = bak;
|
||||
break;
|
||||
case R.id.donation:
|
||||
startActivity(new Intent(this, DonationActivity.class));
|
||||
mDrawerItem = bak;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
13
app/src/full/java/com/topjohnwu/magisk/NoUIActivity.java
Normal file
13
app/src/full/java/com/topjohnwu/magisk/NoUIActivity.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
|
||||
public class NoUIActivity extends BaseActivity {
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
finish();
|
||||
}
|
||||
}
|
@@ -7,33 +7,34 @@ 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.components.BaseActivity;
|
||||
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.Download;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class SplashActivity extends Activity {
|
||||
public class SplashActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
RootUtils.init();
|
||||
MagiskManager mm = getMagiskManager();
|
||||
// Magisk working as expected
|
||||
if (Shell.rootAccess() && Data.magiskVersionCode > 0) {
|
||||
// Update check service
|
||||
Utils.setupUpdateCheck();
|
||||
// Load modules
|
||||
Utils.loadModules();
|
||||
}
|
||||
|
||||
mm.repoDB = new RepoDatabaseHelper(this);
|
||||
mm.loadMagiskInfo();
|
||||
mm.getDefaultInstallFlags();
|
||||
mm.loadPrefs();
|
||||
Data.importPrefs();
|
||||
|
||||
// Dynamic detect all locales
|
||||
new LoadLocale().exec();
|
||||
LocaleManager.loadAvailableLocales();
|
||||
|
||||
// Create notification channel on Android O
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
@@ -45,44 +46,22 @@ public class SplashActivity extends Activity {
|
||||
// Setup shortcuts
|
||||
sendBroadcast(new Intent(this, ShortcutReceiver.class));
|
||||
|
||||
LoadModules loadModuleTask = new LoadModules();
|
||||
|
||||
if (Utils.checkNetworkStatus()) {
|
||||
if (Download.checkNetworkStatus(this)) {
|
||||
// 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();
|
||||
CheckUpdates.check();
|
||||
// Repo update check
|
||||
new UpdateRepos().exec();
|
||||
}
|
||||
|
||||
// Write back default values
|
||||
mm.writeConfig();
|
||||
Data.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));
|
||||
intent.putExtra(BaseActivity.INTENT_PERM, getIntent().getStringExtra(BaseActivity.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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,12 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -12,10 +17,10 @@ import android.widget.Filter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -33,36 +38,73 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
private PackageManager pm;
|
||||
private ApplicationFilter filter;
|
||||
|
||||
public ApplicationAdapter() {
|
||||
public ApplicationAdapter(Context context) {
|
||||
fullList = showList = Collections.emptyList();
|
||||
hideList = Collections.emptyList();
|
||||
filter = new ApplicationFilter();
|
||||
pm = MagiskManager.get().getPackageManager();
|
||||
new LoadApps().exec();
|
||||
pm = context.getPackageManager();
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(this::loadApps);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_app, parent, false);
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
private String getLabel(ApplicationInfo info) {
|
||||
if (info.labelRes > 0) {
|
||||
try {
|
||||
Resources res = pm.getResourcesForApplication(info);
|
||||
Configuration config = new Configuration();
|
||||
config.setLocale(LocaleManager.locale);
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
return res.getString(info.labelRes);
|
||||
} catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
|
||||
}
|
||||
return info.loadLabel(pm).toString();
|
||||
}
|
||||
|
||||
private void loadApps() {
|
||||
fullList = pm.getInstalledApplications(0);
|
||||
hideList = Shell.su("magiskhide --ls").exec().getOut();
|
||||
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 getLabel(a).toLowerCase().compareTo(getLabel(b).toLowerCase());
|
||||
} else if (ah) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
Topic.publish(false, Topic.MAGISK_HIDE_DONE);
|
||||
}
|
||||
|
||||
@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) {
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
ApplicationInfo info = showList.get(position);
|
||||
|
||||
holder.appIcon.setImageDrawable(info.loadIcon(pm));
|
||||
holder.appName.setText(info.loadLabel(pm));
|
||||
holder.appName.setText(getLabel(info));
|
||||
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);
|
||||
Shell.su("magiskhide --add " + info.packageName).submit();
|
||||
hideList.add(info.packageName);
|
||||
} else {
|
||||
Shell.Async.su("magiskhide --rm " + info.packageName);
|
||||
Shell.su("magiskhide --rm " + info.packageName).submit();
|
||||
hideList.remove(info.packageName);
|
||||
}
|
||||
});
|
||||
@@ -78,7 +120,7 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
new LoadApps().exec();
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(this::loadApps);
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
@@ -108,7 +150,7 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
showList = new ArrayList<>();
|
||||
String filter = constraint.toString().toLowerCase();
|
||||
for (ApplicationInfo info : fullList) {
|
||||
if (lowercaseContains(info.loadLabel(pm).toString(), filter)
|
||||
if (lowercaseContains(getLabel(info), filter)
|
||||
|| lowercaseContains(info.packageName, filter)) {
|
||||
showList.add(info);
|
||||
}
|
||||
@@ -122,37 +164,4 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
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,7 +12,7 @@ import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
@@ -93,7 +93,7 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
||||
dbHelper.updatePolicy(policy);
|
||||
}
|
||||
});
|
||||
holder.delete.setOnClickListener(v -> new AlertDialogBuilder((Activity) v.getContext())
|
||||
holder.delete.setOnClickListener(v -> new CustomAlertDialog((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) -> {
|
@@ -1,6 +1,5 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -16,11 +15,11 @@ 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.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
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;
|
||||
@@ -98,21 +97,18 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
|
||||
holder.updateTime.setText(context.getString(R.string.updated_on, repo.getLastUpdateString()));
|
||||
|
||||
holder.infoLayout.setOnClickListener(v ->
|
||||
new MarkDownWindow((Activity) context, null, repo.getDetailUrl()).exec());
|
||||
new MarkDownWindow((BaseActivity) context, null, repo.getDetailUrl()).exec());
|
||||
|
||||
holder.downloadImage.setOnClickListener(v -> {
|
||||
String filename = repo.getName() + "-" + repo.getVersion() + ".zip";
|
||||
new AlertDialogBuilder((Activity) context)
|
||||
new CustomAlertDialog((BaseActivity) context)
|
||||
.setTitle(context.getString(R.string.repo_install_title, repo.getName()))
|
||||
.setMessage(context.getString(R.string.repo_install_msg, filename))
|
||||
.setMessage(context.getString(R.string.repo_install_msg, repo.getDownloadFilename()))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.install, (d, i) ->
|
||||
new ProcessRepoZip((Activity) context, repo.getZipUrl(),
|
||||
Utils.getLegalFilename(filename), true).exec()
|
||||
new ProcessRepoZip((BaseActivity) context, repo, true).exec()
|
||||
)
|
||||
.setNeutralButton(R.string.download, (d, i) ->
|
||||
new ProcessRepoZip((Activity) context, repo.getZipUrl(),
|
||||
Utils.getLegalFilename(filename), false).exec())
|
||||
new ProcessRepoZip((BaseActivity) context, repo, false).exec())
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
});
|
@@ -2,9 +2,9 @@ package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.utils.ISafetyNetHelper;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
@@ -19,10 +19,10 @@ import java.net.HttpURLConnection;
|
||||
|
||||
import dalvik.system.DexClassLoader;
|
||||
|
||||
public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
public class CheckSafetyNet extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
public static final File dexPath =
|
||||
new File(MagiskManager.get().getFilesDir().getParent() + "/snet", "snet.apk");
|
||||
new File(Data.MM().getFilesDir().getParent() + "/snet", "snet.apk");
|
||||
private ISafetyNetHelper helper;
|
||||
|
||||
public CheckSafetyNet(Activity activity) {
|
||||
@@ -30,9 +30,9 @@ public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
}
|
||||
|
||||
private void dlSnet() throws Exception {
|
||||
Shell.Sync.sh("rm -rf " + dexPath.getParent());
|
||||
Shell.sh("rm -rf " + dexPath.getParent()).exec();
|
||||
dexPath.getParentFile().mkdir();
|
||||
HttpURLConnection conn = WebService.request(Const.Url.SNET_URL, null);
|
||||
HttpURLConnection conn = WebService.mustRequest(Data.snetLink, null);
|
||||
try (
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(dexPath));
|
||||
InputStream in = new BufferedInputStream(conn.getInputStream())) {
|
||||
@@ -45,17 +45,19 @@ public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
private void dyload() throws Exception {
|
||||
DexClassLoader loader = new DexClassLoader(dexPath.getPath(), dexPath.getParent(),
|
||||
null, ISafetyNetHelper.class.getClassLoader());
|
||||
Class<?> clazz = loader.loadClass("com.topjohnwu.snet.SafetyNetHelper");
|
||||
helper = (ISafetyNetHelper) clazz.getConstructors()[0]
|
||||
.newInstance(getActivity(), (ISafetyNetHelper.Callback)
|
||||
code -> MagiskManager.get().safetyNetDone.publish(false, code));
|
||||
if (helper.getVersion() != Const.SNET_VER) {
|
||||
Class<?> clazz = loader.loadClass("com.topjohnwu.snet.Snet");
|
||||
helper = (ISafetyNetHelper) clazz.getMethod("newHelper",
|
||||
Class.class, String.class, Activity.class, Object.class)
|
||||
.invoke(null, ISafetyNetHelper.class, dexPath.getPath(), getActivity(),
|
||||
(ISafetyNetHelper.Callback) code ->
|
||||
Topic.publish(false, Topic.SNET_CHECK_DONE, code));
|
||||
if (helper.getVersion() < Data.snetVersionCode) {
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Exception doInBackground(Void... voids) {
|
||||
protected Void doInBackground(Void... voids) {
|
||||
try {
|
||||
try {
|
||||
dyload();
|
||||
@@ -64,21 +66,12 @@ public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
dlSnet();
|
||||
dyload();
|
||||
}
|
||||
// Run attestation
|
||||
helper.attest();
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
e.printStackTrace();
|
||||
Topic.publish(false, Topic.SNET_CHECK_DONE, -1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Exception e) {
|
||||
if (e == null) {
|
||||
helper.attest();
|
||||
} else {
|
||||
e.printStackTrace();
|
||||
MagiskManager.get().safetyNetDone.publish(false, -1);
|
||||
}
|
||||
super.onPostExecute(e);
|
||||
}
|
||||
}
|
105
app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java
Normal file
105
app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.utils.NotificationMgr;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class CheckUpdates {
|
||||
|
||||
private static int getInt(JSONObject json, String name, int defValue) {
|
||||
if (json == null)
|
||||
return defValue;
|
||||
try {
|
||||
return json.getInt(name);
|
||||
} catch (JSONException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getString(JSONObject json, String name, String defValue) {
|
||||
if (json == null)
|
||||
return defValue;
|
||||
try {
|
||||
return json.getString(name);
|
||||
} catch (JSONException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static JSONObject getJson(JSONObject json, String name) {
|
||||
try {
|
||||
return json.getJSONObject(name);
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void fetchUpdates() {
|
||||
String jsonStr = "";
|
||||
switch (Data.updateChannel) {
|
||||
case Const.Value.STABLE_CHANNEL:
|
||||
jsonStr = WebService.getString(Const.Url.STABLE_URL);
|
||||
break;
|
||||
case Const.Value.BETA_CHANNEL:
|
||||
jsonStr = WebService.getString(Const.Url.BETA_URL);
|
||||
break;
|
||||
case Const.Value.CUSTOM_CHANNEL:
|
||||
jsonStr = WebService.getString(Data.MM().prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||
break;
|
||||
}
|
||||
|
||||
JSONObject json;
|
||||
try {
|
||||
json = new JSONObject(jsonStr);
|
||||
} catch (JSONException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject magisk = getJson(json, "magisk");
|
||||
Data.remoteMagiskVersionString = getString(magisk, "version", null);
|
||||
Data.remoteMagiskVersionCode = getInt(magisk, "versionCode", -1);
|
||||
Data.magiskLink = getString(magisk, "link", null);
|
||||
Data.magiskNoteLink = getString(magisk, "note", null);
|
||||
Data.magiskMD5 = getString(magisk, "md5", null);
|
||||
|
||||
JSONObject manager = getJson(json, "app");
|
||||
Data.remoteManagerVersionString = getString(manager, "version", null);
|
||||
Data.remoteManagerVersionCode = getInt(manager, "versionCode", -1);
|
||||
Data.managerLink = getString(manager, "link", null);
|
||||
Data.managerNoteLink = getString(manager, "note", null);
|
||||
|
||||
JSONObject uninstaller = getJson(json, "uninstaller");
|
||||
Data.uninstallerLink = getString(uninstaller, "link", null);
|
||||
|
||||
JSONObject snet = getJson(json, "snet");
|
||||
Data.snetVersionCode = getInt(snet, "versionCode", -1);
|
||||
Data.snetLink = getString(snet, "link", null);
|
||||
}
|
||||
|
||||
public static void check(Runnable cb) {
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
fetchUpdates();
|
||||
if (cb != null) {
|
||||
if (BuildConfig.VERSION_CODE < Data.remoteManagerVersionCode) {
|
||||
NotificationMgr.managerUpdate();
|
||||
} else if (Data.magiskVersionCode < Data.remoteMagiskVersionCode) {
|
||||
NotificationMgr.magiskUpdate();
|
||||
}
|
||||
cb.run();
|
||||
}
|
||||
Topic.publish(Topic.UPDATE_CHECK_DONE);
|
||||
});
|
||||
}
|
||||
|
||||
public static void check() {
|
||||
check(null);
|
||||
}
|
||||
}
|
@@ -2,13 +2,13 @@ package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
@@ -45,7 +45,7 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... voids) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
MagiskManager mm = Data.MM();
|
||||
try {
|
||||
console.add("- Copying zip to temp directory");
|
||||
|
||||
@@ -66,12 +66,10 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
}
|
||||
if (!unzipAndCheck()) return 0;
|
||||
console.add("- Installing " + Utils.getNameFromUri(mm, mUri));
|
||||
Shell.Sync.su(console, logs,
|
||||
"cd " + mCachedFile.getParent(),
|
||||
"BOOTMODE=true sh update-binary dummy 1 " + mCachedFile + " || echo 'Failed!'"
|
||||
);
|
||||
|
||||
if (TextUtils.equals(console.get(console.size() - 1), "Failed!"))
|
||||
if (!Shell.su("cd " + mCachedFile.getParent(),
|
||||
"BOOTMODE=true sh update-binary dummy 1 " + mCachedFile)
|
||||
.to(console, logs)
|
||||
.exec().isSuccess())
|
||||
return -1;
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -86,10 +84,7 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
FlashActivity activity = (FlashActivity) getActivity();
|
||||
Shell.Async.su(
|
||||
"rm -rf " + mCachedFile.getParent(),
|
||||
"rm -rf " + Const.TMP_FOLDER_PATH
|
||||
);
|
||||
Shell.su("rm -rf " + mCachedFile.getParent(), "rm -rf " + Const.TMP_FOLDER_PATH).submit();
|
||||
switch (result) {
|
||||
case -1:
|
||||
console.add("! Installation failed");
|
||||
@@ -99,8 +94,8 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
console.add("! This zip is not a Magisk Module!");
|
||||
break;
|
||||
case 1:
|
||||
// Success
|
||||
new LoadModules().exec();
|
||||
// Reload modules
|
||||
Utils.loadModules();
|
||||
break;
|
||||
}
|
||||
activity.reboot.setVisibility(result > 0 ? View.VISIBLE : View.GONE);
|
@@ -4,20 +4,27 @@ import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.TarEntry;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.internal.NOPList;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
import com.topjohnwu.utils.SignBoot;
|
||||
|
||||
import org.kamranzafar.jtar.TarInputStream;
|
||||
@@ -26,12 +33,14 @@ import org.kamranzafar.jtar.TarOutputStream;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.AbstractList;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@@ -42,7 +51,7 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
private static final int FIX_ENV_MODE = 2;
|
||||
public static final int SECOND_SLOT_MODE = 3;
|
||||
|
||||
private Uri bootUri, mZip;
|
||||
private Uri bootUri;
|
||||
private List<String> console, logs;
|
||||
private String mBoot;
|
||||
private int mode;
|
||||
@@ -50,22 +59,21 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
private ProgressDialog dialog;
|
||||
private MagiskManager mm;
|
||||
|
||||
public InstallMagisk(Activity context, Uri zip) {
|
||||
public InstallMagisk(Activity context) {
|
||||
super(context);
|
||||
mZip = zip;
|
||||
mm = MagiskManager.get();
|
||||
mm = Data.MM();
|
||||
mode = FIX_ENV_MODE;
|
||||
}
|
||||
|
||||
public InstallMagisk(Activity context, List<String> console, List<String> logs, Uri zip, int mode) {
|
||||
this(context, zip);
|
||||
public InstallMagisk(Activity context, List<String> console, List<String> logs, int mode) {
|
||||
this(context);
|
||||
this.console = console;
|
||||
this.logs = logs;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public InstallMagisk(FlashActivity context, List<String> console, List<String> logs, Uri zip, Uri boot) {
|
||||
this(context, console, logs, zip, PATCH_MODE);
|
||||
public InstallMagisk(FlashActivity context, List<String> console, List<String> logs, Uri boot) {
|
||||
this(context, console, logs, PATCH_MODE);
|
||||
bootUri = boot;
|
||||
}
|
||||
|
||||
@@ -74,37 +82,92 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
if (mode == FIX_ENV_MODE) {
|
||||
Activity a = getActivity();
|
||||
dialog = ProgressDialog.show(a, a.getString(R.string.setup_title), a.getString(R.string.setup_msg));
|
||||
console = new NOPList<>();
|
||||
console = NOPList.getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
private class ProgressStream extends FilterInputStream {
|
||||
|
||||
private int prev = -1;
|
||||
private int progress = 0;
|
||||
private int total;
|
||||
|
||||
private ProgressStream(HttpURLConnection conn) throws IOException {
|
||||
super(conn.getInputStream());
|
||||
total = conn.getContentLength();
|
||||
console.add("... 0%");
|
||||
}
|
||||
|
||||
private void update(int step) {
|
||||
progress += step;
|
||||
int curr = (int) (100 * (double) progress / total);
|
||||
if (prev != curr) {
|
||||
prev = curr;
|
||||
console.set(console.size() - 1, "... " + prev + "%");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int b = super.read();
|
||||
if (b > 0)
|
||||
update(1);
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b, int off, int len) throws IOException {
|
||||
int step = super.read(b, off, len);
|
||||
if (step > 0)
|
||||
update(step);
|
||||
return step;
|
||||
}
|
||||
}
|
||||
|
||||
private void extractFiles(String arch) throws IOException {
|
||||
File zip = new File(mm.getFilesDir(), "magisk.zip");
|
||||
BufferedInputStream buf;
|
||||
|
||||
if (!ShellUtils.checkSum("MD5", zip, Data.magiskMD5)) {
|
||||
console.add("- Downloading zip");
|
||||
HttpURLConnection conn = WebService.mustRequest(Data.magiskLink, null);
|
||||
buf = new BufferedInputStream(new ProgressStream(conn), conn.getContentLength());
|
||||
buf.mark(conn.getContentLength() + 1);
|
||||
try (OutputStream out = new FileOutputStream(zip)) {
|
||||
ShellUtils.pump(buf, out);
|
||||
}
|
||||
buf.reset();
|
||||
conn.disconnect();
|
||||
} else {
|
||||
console.add("- Existing zip found");
|
||||
buf = new BufferedInputStream(new FileInputStream(zip), (int) zip.length());
|
||||
buf.mark((int) zip.length() + 1);
|
||||
}
|
||||
|
||||
console.add("- Extracting files");
|
||||
try (InputStream in = mm.getContentResolver().openInputStream(mZip)) {
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
BufferedInputStream buf = new BufferedInputStream(in);
|
||||
buf.mark(Integer.MAX_VALUE);
|
||||
ZipUtils.unzip(buf, installDir, arch + "/", true);
|
||||
buf.reset();
|
||||
ZipUtils.unzip(buf, installDir, "common/", true);
|
||||
buf.reset();
|
||||
ZipUtils.unzip(buf, installDir, "chromeos/", false);
|
||||
buf.reset();
|
||||
ZipUtils.unzip(buf, installDir, "META-INF/com/google/android/update-binary", true);
|
||||
buf.close();
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
throw e;
|
||||
try (InputStream in = buf) {
|
||||
ZipUtils.unzip(in, installDir, arch + "/", true);
|
||||
in.reset();
|
||||
ZipUtils.unzip(in, installDir, "common/", true);
|
||||
in.reset();
|
||||
ZipUtils.unzip(in, installDir, "chromeos/", false);
|
||||
in.reset();
|
||||
ZipUtils.unzip(in, installDir, "META-INF/com/google/android/update-binary", true);
|
||||
} catch (IOException e) {
|
||||
console.add("! Cannot unzip zip");
|
||||
throw e;
|
||||
}
|
||||
Shell.Sync.sh(Utils.fmt("chmod -R 755 %s/*; %s/magiskinit -x magisk %s/magisk",
|
||||
installDir, installDir, installDir));
|
||||
Shell.sh(Utils.fmt("chmod -R 755 %s/*; %s/magiskinit -x magisk %s/magisk",
|
||||
installDir, installDir, installDir)).exec();
|
||||
}
|
||||
|
||||
private boolean dumpBoot() {
|
||||
console.add("- Copying image locally");
|
||||
console.add("- Copying image to cache");
|
||||
// Copy boot image to local
|
||||
try (InputStream in = mm.getContentResolver().openInputStream(bootUri);
|
||||
OutputStream out = new FileOutputStream(mBoot)
|
||||
@@ -150,16 +213,13 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
}
|
||||
|
||||
// Patch boot image
|
||||
Shell.Sync.sh(console, logs,
|
||||
"cd " + installDir,
|
||||
Utils.fmt("KEEPFORCEENCRYPT=%b KEEPVERITY=%b sh update-binary indep " +
|
||||
"boot_patch.sh %s || echo 'Failed!'",
|
||||
mm.keepEnc, mm.keepVerity, mBoot));
|
||||
|
||||
if (TextUtils.equals(console.get(console.size() - 1), "Failed!"))
|
||||
if (!Shell.sh("cd " + installDir, Utils.fmt(
|
||||
"KEEPFORCEENCRYPT=%b KEEPVERITY=%b sh update-binary indep boot_patch.sh %s",
|
||||
Data.keepEnc, Data.keepVerity, mBoot))
|
||||
.to(console, logs).exec().isSuccess())
|
||||
return null;
|
||||
|
||||
Shell.Sync.sh("mv bin/busybox busybox",
|
||||
Shell.Job job = Shell.sh("mv bin/busybox busybox",
|
||||
"rm -rf magisk.apk bin boot.img update-binary",
|
||||
"cd /");
|
||||
|
||||
@@ -172,18 +232,20 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
) {
|
||||
SignBoot.doSignature("/boot", in, out, null, null);
|
||||
}
|
||||
Shell.Sync.su("mv -f " + signed + " " + patched);
|
||||
job.add("mv -f " + signed + " " + patched);
|
||||
}
|
||||
job.exec();
|
||||
return patched;
|
||||
}
|
||||
|
||||
private void outputBoot(File patched) throws IOException {
|
||||
private boolean outputBoot(File patched) throws IOException {
|
||||
switch (mode) {
|
||||
case PATCH_MODE:
|
||||
File dest = new File(Const.EXTERNAL_PATH, "patched_boot" + mm.bootFormat);
|
||||
String fmt = mm.prefs.getString(Const.Key.BOOT_FORMAT, ".img");
|
||||
File dest = new File(Download.EXTERNAL_PATH, "patched_boot" + fmt);
|
||||
dest.getParentFile().mkdirs();
|
||||
OutputStream out;
|
||||
switch (mm.bootFormat) {
|
||||
switch (fmt) {
|
||||
case ".img.tar":
|
||||
out = new TarOutputStream(new BufferedOutputStream(new FileOutputStream(dest)));
|
||||
((TarOutputStream) out).putNextEntry(new TarEntry(patched, "boot.img"));
|
||||
@@ -197,7 +259,7 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
ShellUtils.pump(in, out);
|
||||
out.close();
|
||||
}
|
||||
Shell.Sync.su("rm -f " + patched);
|
||||
Shell.sh("rm -f " + patched).exec();
|
||||
console.add("");
|
||||
console.add("****************************");
|
||||
console.add(" Patched image is placed in ");
|
||||
@@ -206,26 +268,42 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
break;
|
||||
case SECOND_SLOT_MODE:
|
||||
case DIRECT_MODE:
|
||||
Shell.Sync.sh(console, logs,
|
||||
Utils.fmt("direct_install %s %s %s", patched, mBoot, installDir));
|
||||
if (!mm.keepVerity)
|
||||
Shell.Sync.sh(console, logs, "find_dtbo_image", "patch_dtbo_image");
|
||||
if (!Shell.su(Utils.fmt("direct_install %s %s", installDir, mBoot))
|
||||
.to(console, logs).exec().isSuccess())
|
||||
return false;
|
||||
if (!Data.keepVerity)
|
||||
Shell.su("find_dtbo_image", "patch_dtbo_image").to(console, logs).exec();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void postOTA() {
|
||||
SuFile bootctl = new SuFile(Const.MAGISK_PATH + "/.core/bootctl");
|
||||
try (InputStream in = mm.getResources().openRawResource(R.raw.bootctl);
|
||||
OutputStream out = new SuFileOutputStream(bootctl)) {
|
||||
ShellUtils.pump(in, out);
|
||||
Shell.su("post_ota " + bootctl.getParent()).exec();
|
||||
console.add("***************************************");
|
||||
console.add(" Next reboot will boot to second slot!");
|
||||
console.add("***************************************");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
if (mode == FIX_ENV_MODE) {
|
||||
installDir = new File("/data/adb/magisk");
|
||||
Shell.Sync.sh("rm -rf /data/adb/magisk/*");
|
||||
Shell.su("rm -rf /data/adb/magisk/*").exec();
|
||||
} else {
|
||||
installDir = new File(
|
||||
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
|
||||
mm.createDeviceProtectedStorageContext() : mm)
|
||||
.getFilesDir().getParent()
|
||||
, "install");
|
||||
Shell.Sync.sh("rm -rf " + installDir);
|
||||
Shell.sh("rm -rf " + installDir).exec();
|
||||
installDir.mkdirs();
|
||||
}
|
||||
|
||||
@@ -240,13 +318,16 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
mBoot = ShellUtils.fastCmd("find_boot_image", "echo \"$BOOTIMAGE\"");
|
||||
break;
|
||||
case SECOND_SLOT_MODE:
|
||||
String slot = ShellUtils.fastCmd("echo $SLOT");
|
||||
String target = (TextUtils.equals(slot, "_a") ? "_b" : "_a");
|
||||
console.add("- Target slot: " + target);
|
||||
console.add("- Detecting target image");
|
||||
char slot[] = ShellUtils.fastCmd("echo $SLOT").toCharArray();
|
||||
if (slot[1] == 'a') slot[1] = 'b';
|
||||
else slot[1] = 'a';
|
||||
mBoot = ShellUtils.fastCmd("SLOT=" + String.valueOf(slot),
|
||||
"find_boot_image", "echo \"$BOOTIMAGE\"");
|
||||
Shell.Async.su("mount_partitions");
|
||||
mBoot = ShellUtils.fastCmd(
|
||||
"SLOT=" + target,
|
||||
"find_boot_image",
|
||||
"SLOT=" + slot,
|
||||
"echo \"$BOOTIMAGE\""
|
||||
);
|
||||
break;
|
||||
case FIX_ENV_MODE:
|
||||
mBoot = "";
|
||||
@@ -257,12 +338,13 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
console.add("- Target image: " + mBoot);
|
||||
if (mode == DIRECT_MODE || mode == SECOND_SLOT_MODE)
|
||||
console.add("- Target image: " + mBoot);
|
||||
|
||||
List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
|
||||
String arch;
|
||||
|
||||
if (mm.remoteMagiskVersionCode >= Const.MAGISK_VER.SEPOL_REFACTOR) {
|
||||
if (Data.remoteMagiskVersionCode >= Const.MAGISK_VER.SEPOL_REFACTOR) {
|
||||
// 32-bit only
|
||||
if (abis.contains("x86")) arch = "x86";
|
||||
else arch = "arm";
|
||||
@@ -278,12 +360,15 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
try {
|
||||
extractFiles(arch);
|
||||
if (mode == FIX_ENV_MODE) {
|
||||
Shell.Sync.sh("fix_env");
|
||||
Shell.su("fix_env").exec();
|
||||
} else {
|
||||
File patched = patchBoot();
|
||||
if (patched == null)
|
||||
return false;
|
||||
outputBoot(patched);
|
||||
if (!outputBoot(patched))
|
||||
return false;
|
||||
if (mode == SECOND_SLOT_MODE)
|
||||
postOTA();
|
||||
console.add("- All done!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -297,31 +382,16 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
protected void onPostExecute(Boolean result) {
|
||||
if (mode == FIX_ENV_MODE) {
|
||||
dialog.dismiss();
|
||||
MagiskManager.toast(result ? R.string.setup_done : R.string.setup_fail, Toast.LENGTH_LONG);
|
||||
Utils.toast(result ? R.string.setup_done : R.string.setup_fail, Toast.LENGTH_LONG);
|
||||
} else {
|
||||
// Running in FlashActivity
|
||||
FlashActivity activity = (FlashActivity) getActivity();
|
||||
if (!result) {
|
||||
Shell.Async.sh("rm -rf " + installDir);
|
||||
Shell.sh("rm -rf " + installDir).submit();
|
||||
console.add("! Installation failed");
|
||||
activity.reboot.setVisibility(View.GONE);
|
||||
}
|
||||
activity.buttonPanel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private static class NOPList<E> extends AbstractList<E> {
|
||||
@Override
|
||||
public E get(int index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, E element) {}
|
||||
}
|
||||
}
|
@@ -4,6 +4,7 @@ import android.app.Activity;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
@@ -38,7 +39,7 @@ public class MarkDownWindow extends ParallelTask<Void, Void, String> {
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Void... voids) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
MagiskManager mm = Data.MM();
|
||||
String md;
|
||||
if (mUrl != null) {
|
||||
md = WebService.getString(mUrl);
|
||||
@@ -54,9 +55,9 @@ public class MarkDownWindow extends ParallelTask<Void, Void, String> {
|
||||
}
|
||||
String css;
|
||||
try (
|
||||
InputStream in = mm.getResources().openRawResource(
|
||||
mm.isDarkTheme ? R.raw.dark : R.raw.light);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream()
|
||||
InputStream in = mm.getResources().openRawResource(
|
||||
Data.isDarkTheme ? R.raw.dark : R.raw.light);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream()
|
||||
) {
|
||||
ShellUtils.pump(in, out);
|
||||
css = out.toString();
|
@@ -9,8 +9,6 @@ public abstract class ParallelTask<Params, Progress, Result> extends AsyncTask<P
|
||||
|
||||
private WeakReference<Activity> weakActivity;
|
||||
|
||||
private Runnable callback = null;
|
||||
|
||||
public ParallelTask() {}
|
||||
|
||||
public ParallelTask(Activity context) {
|
||||
@@ -22,18 +20,7 @@ public abstract class ParallelTask<Params, Progress, Result> extends AsyncTask<P
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ParallelTask<Params, Progress, Result> exec(Params... params) {
|
||||
public void exec(Params... params) {
|
||||
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Result result) {
|
||||
if (callback != null) callback.run();
|
||||
}
|
||||
|
||||
public ParallelTask<Params, Progress, Result> setCallBack(Runnable next) {
|
||||
callback = next;
|
||||
return this;
|
||||
}
|
||||
}
|
153
app/src/full/java/com/topjohnwu/magisk/asyncs/PatchAPK.java
Normal file
153
app/src/full/java/com/topjohnwu/magisk/asyncs/PatchAPK.java
Normal file
@@ -0,0 +1,153 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.os.AsyncTask;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
import com.topjohnwu.utils.JarMap;
|
||||
import com.topjohnwu.utils.SignAPK;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
public class PatchAPK {
|
||||
|
||||
private static String genPackageName(String prefix, int length) {
|
||||
StringBuilder builder = new StringBuilder(length);
|
||||
builder.append(prefix);
|
||||
length -= prefix.length();
|
||||
SecureRandom random = new SecureRandom();
|
||||
String base = "abcdefghijklmnopqrstuvwxyz";
|
||||
String alpha = base + base.toUpperCase();
|
||||
String full = alpha + "0123456789..........";
|
||||
char next, prev = '\0';
|
||||
for (int i = 0; i < length; ++i) {
|
||||
if (prev == '.' || i == length - 1 || i == 0) {
|
||||
next = alpha.charAt(random.nextInt(alpha.length()));
|
||||
} else {
|
||||
next = full.charAt(random.nextInt(full.length()));
|
||||
}
|
||||
builder.append(next);
|
||||
prev = next;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static int findOffset(byte buf[], byte pattern[]) {
|
||||
int offset = -1;
|
||||
for (int i = 0; i < buf.length - pattern.length; ++i) {
|
||||
boolean match = true;
|
||||
for (int j = 0; j < pattern.length; ++j) {
|
||||
if (buf[i + j] != pattern[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
offset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
/* It seems that AAPT sometimes generate another type of string format */
|
||||
private static boolean fallbackPatch(byte xml[], String from, String to) {
|
||||
|
||||
byte[] target = new byte[from.length() * 2 + 2];
|
||||
for (int i = 0; i < from.length(); ++i) {
|
||||
target[i * 2] = (byte) from.charAt(i);
|
||||
}
|
||||
int offset = findOffset(xml, target);
|
||||
if (offset < 0)
|
||||
return false;
|
||||
byte[] dest = new byte[target.length - 2];
|
||||
for (int i = 0; i < to.length(); ++i) {
|
||||
dest[i * 2] = (byte) to.charAt(i);
|
||||
}
|
||||
System.arraycopy(dest, 0, xml, offset, dest.length);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean findAndPatch(byte xml[], String from, String to) {
|
||||
byte target[] = (from + '\0').getBytes();
|
||||
int offset = findOffset(xml, target);
|
||||
if (offset < 0)
|
||||
return fallbackPatch(xml, from, to);
|
||||
System.arraycopy(to.getBytes(), 0, xml, offset, to.length());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean patchAndHide() {
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
// Generate a new app with random package name
|
||||
SuFile repack = new SuFile("/data/local/tmp/repack.apk");
|
||||
String pkg = genPackageName("com.", Const.ORIG_PKG_NAME.length());
|
||||
|
||||
try {
|
||||
JarMap apk = new JarMap(mm.getPackageCodePath());
|
||||
if (!patchPackageID(apk, Const.ORIG_PKG_NAME, pkg))
|
||||
return false;
|
||||
SignAPK.sign(apk, new SuFileOutputStream(repack));
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Install the application
|
||||
if (!ShellUtils.fastCmdResult("pm install " + repack))
|
||||
return false;
|
||||
|
||||
repack.delete();
|
||||
|
||||
mm.mDB.setStrings(Const.Key.SU_MANAGER, pkg);
|
||||
Data.exportPrefs();
|
||||
RootUtils.uninstallPkg(Const.ORIG_PKG_NAME);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean patchPackageID(JarMap apk, String from, String to) {
|
||||
try {
|
||||
JarEntry je = apk.getJarEntry(Const.ANDROID_MANIFEST);
|
||||
byte xml[] = apk.getRawData(je);
|
||||
|
||||
if (!findAndPatch(xml, from, to))
|
||||
return false;
|
||||
if (!findAndPatch(xml, from + ".provider", to + ".provider"))
|
||||
return false;
|
||||
|
||||
// Write in changes
|
||||
apk.getOutputStream(je).write(xml);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void hideManager(Activity activity) {
|
||||
ProgressDialog dialog = ProgressDialog.show(activity,
|
||||
activity.getString(R.string.hide_manager_toast),
|
||||
activity.getString(R.string.hide_manager_toast2));
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
boolean b = patchAndHide();
|
||||
Data.mainHandler.post(() -> {
|
||||
dialog.cancel();
|
||||
if (!b) {
|
||||
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,19 +1,21 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
@@ -37,17 +39,15 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
|
||||
private ProgressDialog progressDialog;
|
||||
private boolean mInstall;
|
||||
private String mLink;
|
||||
private File mFile;
|
||||
private Repo mRepo;
|
||||
private int progress = 0, total = -1;
|
||||
private Handler mHandler;
|
||||
|
||||
public ProcessRepoZip(Activity context, String link, String filename, boolean install) {
|
||||
public ProcessRepoZip(BaseActivity context, Repo repo, boolean install) {
|
||||
super(context);
|
||||
mLink = link;
|
||||
mFile = new File(Const.EXTERNAL_PATH, filename);
|
||||
mInstall = install;
|
||||
mHandler = new Handler();
|
||||
mRepo = repo;
|
||||
mInstall = install && Shell.rootAccess();
|
||||
mFile = new File(Download.EXTERNAL_PATH, repo.getDownloadFilename());
|
||||
}
|
||||
|
||||
private void removeTopFolder(File input, File output) throws IOException {
|
||||
@@ -74,28 +74,26 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BaseActivity getActivity() {
|
||||
return (BaseActivity) super.getActivity();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
Activity activity = getActivity();
|
||||
BaseActivity activity = getActivity();
|
||||
mFile.getParentFile().mkdirs();
|
||||
progressDialog = ProgressDialog.show(activity, activity.getString(R.string.zip_download_title), activity.getString(R.string.zip_download_msg, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
Activity activity = getActivity();
|
||||
BaseActivity activity = getActivity();
|
||||
if (activity == null) return null;
|
||||
try {
|
||||
// Request zip from Internet
|
||||
HttpURLConnection conn;
|
||||
do {
|
||||
conn = WebService.request(mLink, null);
|
||||
total = conn.getContentLength();
|
||||
if (total < 0)
|
||||
conn.disconnect();
|
||||
else
|
||||
break;
|
||||
} while (true);
|
||||
HttpURLConnection conn = WebService.mustRequest(mRepo.getZipUrl(), null);
|
||||
total = conn.getContentLength();
|
||||
|
||||
// Temp files
|
||||
File temp1 = new File(activity.getCacheDir(), "1.zip");
|
||||
@@ -112,7 +110,7 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
}
|
||||
conn.disconnect();
|
||||
|
||||
mHandler.post(() -> {
|
||||
Data.mainHandler.post(() -> {
|
||||
progressDialog.setTitle(R.string.zip_process_title);
|
||||
progressDialog.setMessage(getActivity().getString(R.string.zip_process_msg));
|
||||
});
|
||||
@@ -136,12 +134,12 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
Activity activity = getActivity();
|
||||
BaseActivity activity = getActivity();
|
||||
if (activity == null) return;
|
||||
progressDialog.dismiss();
|
||||
if (result) {
|
||||
Uri uri = Uri.fromFile(mFile);
|
||||
if (Shell.rootAccess() && mInstall) {
|
||||
if (mInstall) {
|
||||
Intent intent = new Intent(activity, FlashActivity.class);
|
||||
intent.setData(uri).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||
activity.startActivity(intent);
|
||||
@@ -149,17 +147,15 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
SnackbarMaker.showUri(activity, uri);
|
||||
}
|
||||
} else {
|
||||
MagiskManager.toast(R.string.process_error, Toast.LENGTH_LONG);
|
||||
Utils.toast(R.string.process_error, Toast.LENGTH_LONG);
|
||||
}
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParallelTask<Void, Object, Boolean> exec(Void... voids) {
|
||||
com.topjohnwu.magisk.components.Activity.runWithPermission(
|
||||
getActivity(), new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
|
||||
() -> super.exec(voids));
|
||||
return this;
|
||||
public void exec(Void... voids) {
|
||||
getActivity().runWithPermission(
|
||||
new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, super::exec);
|
||||
}
|
||||
|
||||
private class ProgressInputStream extends FilterInputStream {
|
||||
@@ -170,14 +166,15 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
|
||||
private void updateDlProgress(int step) {
|
||||
progress += step;
|
||||
progressDialog.setMessage(getActivity().getString(R.string.zip_download_msg, (int) (100 * (double) progress / total + 0.5)));
|
||||
progressDialog.setMessage(getActivity().getString(R.string.zip_download_msg,
|
||||
(int) (100 * (double) progress / total + 0.5)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read() throws IOException {
|
||||
int b = super.read();
|
||||
if (b > 0) {
|
||||
mHandler.post(() -> updateDlProgress(1));
|
||||
Data.mainHandler.post(() -> updateDlProgress(1));
|
||||
}
|
||||
return b;
|
||||
}
|
||||
@@ -191,7 +188,7 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
public synchronized int read(@NonNull byte[] b, int off, int len) throws IOException {
|
||||
int read = super.read(b, off, len);
|
||||
if (read > 0) {
|
||||
mHandler.post(() -> updateDlProgress(read));
|
||||
Data.mainHandler.post(() -> updateDlProgress(read));
|
||||
}
|
||||
return read;
|
||||
}
|
161
app/src/full/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java
Normal file
161
app/src/full/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java
Normal file
@@ -0,0 +1,161 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class UpdateRepos {
|
||||
|
||||
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
|
||||
private static final int CORE_POOL_SIZE = Math.max(2, CPU_COUNT - 1);
|
||||
private static final DateFormat dateFormat;
|
||||
|
||||
static {
|
||||
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
private MagiskManager mm;
|
||||
private Set<String> cached;
|
||||
private ExecutorService threadPool;
|
||||
|
||||
public UpdateRepos() {
|
||||
mm = Data.MM();
|
||||
}
|
||||
|
||||
private void waitTasks() {
|
||||
threadPool.shutdown();
|
||||
try {
|
||||
threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
|
||||
private boolean loadJSON(String jsonString) throws JSONException, ParseException {
|
||||
JSONArray jsonArray = new JSONArray(jsonString);
|
||||
|
||||
// Empty page, halt
|
||||
if (jsonArray.length() == 0)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
JSONObject rawRepo = jsonArray.getJSONObject(i);
|
||||
String id = rawRepo.getString("description");
|
||||
String name = rawRepo.getString("name");
|
||||
Date date = dateFormat.parse(rawRepo.getString("pushed_at"));
|
||||
Set<String> set = Collections.synchronizedSet(cached);
|
||||
threadPool.execute(() -> {
|
||||
Repo repo = mm.repoDB.getRepo(id);
|
||||
try {
|
||||
if (repo == null)
|
||||
repo = new Repo(name);
|
||||
else
|
||||
set.remove(id);
|
||||
repo.update(date);
|
||||
mm.repoDB.addRepo(repo);
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.debug(e.getMessage());
|
||||
mm.repoDB.removeRepo(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* We sort repos by last push, which means that we only need to check whether the
|
||||
* first page is updated to determine whether the online repo database is changed
|
||||
*/
|
||||
private boolean loadPage(int page) {
|
||||
Map<String, String> header = new HashMap<>();
|
||||
if (page == 0)
|
||||
header.put(Const.Key.IF_NONE_MATCH, mm.prefs.getString(Const.Key.ETAG_KEY, ""));
|
||||
String url = Utils.fmt(Const.Url.REPO_URL, page + 1);
|
||||
|
||||
try {
|
||||
HttpURLConnection conn = WebService.request(url, header);
|
||||
// No updates
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED)
|
||||
return false;
|
||||
// Current page is the last page
|
||||
if (!loadJSON(WebService.getString(conn)))
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
// Should not happen, but if exception occurs, page load fails
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update ETAG
|
||||
if (page == 0) {
|
||||
String etag = header.get(Const.Key.ETAG_KEY);
|
||||
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
|
||||
mm.prefs.edit().putString(Const.Key.ETAG_KEY, etag).apply();
|
||||
}
|
||||
|
||||
String links = header.get(Const.Key.LINK_KEY);
|
||||
return links == null || !links.contains("next") || loadPage(page + 1);
|
||||
}
|
||||
|
||||
private void fullReload() {
|
||||
Cursor c = mm.repoDB.getRawCursor();
|
||||
while (c.moveToNext()) {
|
||||
Repo repo = new Repo(c);
|
||||
threadPool.execute(() -> {
|
||||
try {
|
||||
repo.update();
|
||||
mm.repoDB.addRepo(repo);
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.debug(e.getMessage());
|
||||
mm.repoDB.removeRepo(repo);
|
||||
}
|
||||
});
|
||||
}
|
||||
waitTasks();
|
||||
}
|
||||
|
||||
public void exec(boolean force) {
|
||||
Topic.reset(Topic.REPO_LOAD_DONE);
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
cached = mm.repoDB.getRepoIDSet();
|
||||
threadPool = Executors.newFixedThreadPool(CORE_POOL_SIZE);
|
||||
|
||||
if (loadPage(0)) {
|
||||
waitTasks();
|
||||
// The leftover cached means they are removed from online repo
|
||||
mm.repoDB.removeRepo(cached);
|
||||
} else if (force) {
|
||||
fullReload();
|
||||
}
|
||||
Topic.publish(Topic.REPO_LOAD_DONE);
|
||||
});
|
||||
}
|
||||
|
||||
public void exec() {
|
||||
exec(false);
|
||||
}
|
||||
}
|
@@ -28,19 +28,18 @@ import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
/**
|
||||
* @author dvdandroid
|
||||
*/
|
||||
public class AboutCardRow extends LinearLayout {
|
||||
|
||||
private final String title;
|
||||
private final Drawable icon;
|
||||
|
||||
private final TextView mTitle;
|
||||
private final TextView mSummary;
|
||||
private final ImageView mIcon;
|
||||
|
||||
private final View mView;
|
||||
@BindView(android.R.id.title) TextView mTitle;
|
||||
@BindView(android.R.id.summary) TextView mSummary;
|
||||
@BindView(android.R.id.icon) ImageView mIcon;
|
||||
@BindView(R.id.container) View mView;
|
||||
|
||||
public AboutCardRow(Context context) {
|
||||
this(context, null);
|
||||
@@ -53,21 +52,17 @@ public class AboutCardRow extends LinearLayout {
|
||||
public AboutCardRow(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
LayoutInflater.from(context).inflate(R.layout.info_item_row, this);
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AboutCardRow, 0, 0);
|
||||
ButterKnife.bind(this, this);
|
||||
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AboutCardRow, 0, 0);
|
||||
String title;
|
||||
Drawable icon;
|
||||
try {
|
||||
title = a.getString(R.styleable.AboutCardRow_text);
|
||||
icon = a.getDrawable(R.styleable.AboutCardRow_icon);
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
mView = findViewById(R.id.container);
|
||||
|
||||
mTitle = (TextView) findViewById(android.R.id.title);
|
||||
mSummary = (TextView) findViewById(android.R.id.summary);
|
||||
mIcon = (ImageView) findViewById(android.R.id.icon);
|
||||
|
||||
mTitle.setText(title);
|
||||
mIcon.setImageDrawable(icon);
|
||||
}
|
||||
@@ -80,10 +75,7 @@ public class AboutCardRow extends LinearLayout {
|
||||
}
|
||||
|
||||
public void setSummary(String s) {
|
||||
mSummary.setVisibility(VISIBLE);
|
||||
mSummary.setText(s);
|
||||
}
|
||||
|
||||
public void removeSummary() {
|
||||
mSummary.setVisibility(GONE);
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
public class BaseFragment extends Fragment implements Topic.AutoSubscriber {
|
||||
|
||||
public MagiskManager mm;
|
||||
|
||||
public BaseFragment() {
|
||||
mm = Data.MM();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Topic.subscribe(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
Topic.unsubscribe(this);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startActivityForResult(Intent intent, int requestCode) {
|
||||
startActivityForResult(intent, requestCode, this::onActivityResult);
|
||||
}
|
||||
|
||||
public void startActivityForResult(Intent intent, int requestCode, BaseActivity.ActivityResultListener listener) {
|
||||
((BaseActivity) requireActivity()).startActivityForResult(intent, requestCode, listener);
|
||||
}
|
||||
|
||||
public void runWithPermission(String[] permissions, Runnable callback) {
|
||||
((BaseActivity) requireActivity()).runWithPermission(permissions,callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return FlavorActivity.EMPTY_INT_ARRAY;
|
||||
}
|
||||
}
|
@@ -0,0 +1,162 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.annotation.StyleRes;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class CustomAlertDialog extends AlertDialog.Builder {
|
||||
|
||||
private DialogInterface.OnClickListener positiveListener;
|
||||
private DialogInterface.OnClickListener negativeListener;
|
||||
private DialogInterface.OnClickListener neutralListener;
|
||||
private AlertDialog dialog;
|
||||
|
||||
private ViewHolder vh;
|
||||
|
||||
public class ViewHolder {
|
||||
@BindView(R.id.dialog_layout) public LinearLayout dialogLayout;
|
||||
@BindView(R.id.button_panel) public LinearLayout buttons;
|
||||
|
||||
@BindView(R.id.message) public TextView messageView;
|
||||
@BindView(R.id.negative) public Button negative;
|
||||
@BindView(R.id.positive) public Button positive;
|
||||
@BindView(R.id.neutral) public Button neutral;
|
||||
|
||||
ViewHolder(View v) {
|
||||
ButterKnife.bind(this, v);
|
||||
messageView.setVisibility(View.GONE);
|
||||
negative.setVisibility(View.GONE);
|
||||
positive.setVisibility(View.GONE);
|
||||
neutral.setVisibility(View.GONE);
|
||||
buttons.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
View v = LayoutInflater.from(getContext()).inflate(R.layout.alert_dialog, null);
|
||||
vh = new ViewHolder(v);
|
||||
super.setView(v);
|
||||
|
||||
}
|
||||
|
||||
public CustomAlertDialog(@NonNull Activity context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CustomAlertDialog(@NonNull Activity context, @StyleRes int themeResId) {
|
||||
super(context, themeResId);
|
||||
}
|
||||
|
||||
public ViewHolder getViewHolder() {
|
||||
return vh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setView(int layoutResId) { return this; }
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setView(View view) { return this; }
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setMessage(@Nullable CharSequence message) {
|
||||
vh.messageView.setVisibility(View.VISIBLE);
|
||||
vh.messageView.setText(message);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setMessage(@StringRes int messageId) {
|
||||
return setMessage(getContext().getString(messageId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setPositiveButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
vh.buttons.setVisibility(View.VISIBLE);
|
||||
vh.positive.setVisibility(View.VISIBLE);
|
||||
vh.positive.setText(text);
|
||||
positiveListener = listener;
|
||||
vh.positive.setOnClickListener((v) -> {
|
||||
if (positiveListener != null) {
|
||||
positiveListener.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setPositiveButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setPositiveButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNegativeButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
vh.buttons.setVisibility(View.VISIBLE);
|
||||
vh.negative.setVisibility(View.VISIBLE);
|
||||
vh.negative.setText(text);
|
||||
negativeListener = listener;
|
||||
vh.negative.setOnClickListener((v) -> {
|
||||
if (negativeListener != null) {
|
||||
negativeListener.onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNegativeButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setNegativeButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNeutralButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
vh.buttons.setVisibility(View.VISIBLE);
|
||||
vh.neutral.setVisibility(View.VISIBLE);
|
||||
vh.neutral.setText(text);
|
||||
neutralListener = listener;
|
||||
vh.neutral.setOnClickListener((v) -> {
|
||||
if (neutralListener != null) {
|
||||
neutralListener.onClick(dialog, DialogInterface.BUTTON_NEUTRAL);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNeutralButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setNeutralButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog create() {
|
||||
dialog = super.create();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog show() {
|
||||
create();
|
||||
dialog.show();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.InstallMagisk;
|
||||
|
||||
public class EnvFixDialog extends CustomAlertDialog {
|
||||
|
||||
public EnvFixDialog(@NonNull Activity activity) {
|
||||
super(activity);
|
||||
setTitle(R.string.env_fix_title);
|
||||
setMessage(R.string.env_fix_msg);
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.yes, (d, i) -> new InstallMagisk(activity).exec());
|
||||
setNegativeButton(R.string.no_thanks, null);
|
||||
}
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StyleRes;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
public abstract class FlavorActivity extends AppCompatActivity implements Topic.AutoSubscriber {
|
||||
|
||||
private ActivityResultListener activityResultListener;
|
||||
static int[] EMPTY_INT_ARRAY = new int[0];
|
||||
public MagiskManager mm;
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
Configuration config = base.getResources().getConfiguration();
|
||||
config.setLocale(LocaleManager.locale);
|
||||
applyOverrideConfiguration(config);
|
||||
mm = Data.MM();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return EMPTY_INT_ARRAY;
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
public int getDarkTheme() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Topic.subscribe(this);
|
||||
if (Data.isDarkTheme && getDarkTheme() != -1) {
|
||||
setTheme(getDarkTheme());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
Topic.unsubscribe(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
protected 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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (activityResultListener != null)
|
||||
activityResultListener.onActivityResult(requestCode, resultCode, data);
|
||||
activityResultListener = null;
|
||||
}
|
||||
|
||||
public void startActivityForResult(Intent intent, int requestCode, ActivityResultListener listener) {
|
||||
activityResultListener = listener;
|
||||
super.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
public interface ActivityResultListener {
|
||||
void onActivityResult(int requestCode, int resultCode, Intent data);
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class InstallMethodDialog extends AlertDialog.Builder {
|
||||
|
||||
InstallMethodDialog(BaseActivity activity, List<String> options) {
|
||||
super(activity);
|
||||
setTitle(R.string.select_method);
|
||||
setItems(options.toArray(new String [0]), (dialog, idx) -> {
|
||||
Intent intent;
|
||||
switch (idx) {
|
||||
case 1:
|
||||
if (Data.remoteMagiskVersionCode < 1400) {
|
||||
SnackbarMaker.make(activity, R.string.no_boot_file_patch_support,
|
||||
Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
Utils.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
|
||||
intent = new Intent(Intent.ACTION_GET_CONTENT).setType("*/*");
|
||||
activity.startActivityForResult(intent, Const.ID.SELECT_BOOT,
|
||||
(requestCode, resultCode, data) -> {
|
||||
if (requestCode == Const.ID.SELECT_BOOT &&
|
||||
resultCode == BaseActivity.RESULT_OK && data != null) {
|
||||
Intent i = new Intent(activity, FlashActivity.class)
|
||||
.putExtra(Const.Key.FLASH_SET_BOOT, data.getData())
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_BOOT);
|
||||
activity.startActivity(i);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 0:
|
||||
String filename = Utils.fmt("Magisk-v%s(%d).zip",
|
||||
Data.remoteMagiskVersionString, Data.remoteMagiskVersionCode);
|
||||
Download.receive(activity, new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
SnackbarMaker.showUri(activity, uri);
|
||||
}
|
||||
}, Data.magiskLink, filename);
|
||||
break;
|
||||
case 2:
|
||||
intent = new Intent(activity, FlashActivity.class)
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_MAGISK);
|
||||
activity.startActivity(intent);
|
||||
break;
|
||||
case 3:
|
||||
new CustomAlertDialog(activity)
|
||||
.setTitle(R.string.warning)
|
||||
.setMessage(R.string.install_inactive_slot_msg)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.yes, (d, i) -> {
|
||||
Intent it = new Intent(activity, FlashActivity.class)
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_INACTIVE_SLOT);
|
||||
activity.startActivity(it);
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MagiskInstallDialog extends CustomAlertDialog {
|
||||
public MagiskInstallDialog(BaseActivity activity) {
|
||||
super(activity);
|
||||
MagiskManager mm = Data.MM();
|
||||
String filename = Utils.fmt("Magisk-v%s(%d).zip",
|
||||
Data.remoteMagiskVersionString, Data.remoteMagiskVersionCode);
|
||||
setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.magisk)));
|
||||
setMessage(mm.getString(R.string.repo_install_msg, filename));
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.install, (d, i) -> {
|
||||
List<String> options = new ArrayList<>();
|
||||
options.add(mm.getString(R.string.download_zip_only));
|
||||
options.add(mm.getString(R.string.patch_boot_file));
|
||||
if (Shell.rootAccess()) {
|
||||
options.add(mm.getString(R.string.direct_install));
|
||||
String s = ShellUtils.fastCmd("grep_prop ro.build.ab_update");
|
||||
if (!s.isEmpty() && Boolean.parseBoolean(s)) {
|
||||
options.add(mm.getString(R.string.install_inactive_slot));
|
||||
}
|
||||
}
|
||||
new InstallMethodDialog(activity, options).show();
|
||||
});
|
||||
setNegativeButton(R.string.no_thanks, null);
|
||||
if (!TextUtils.isEmpty(Data.magiskNoteLink)) {
|
||||
setNeutralButton(R.string.release_notes, (d, i) -> {
|
||||
if (Data.magiskNoteLink.contains("forum.xda-developers")) {
|
||||
// Open forum links in browser
|
||||
Utils.openLink(activity, Uri.parse(Data.magiskNoteLink));
|
||||
} else {
|
||||
new MarkDownWindow(activity, null, Data.magiskNoteLink).exec();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
public class ManagerInstallDialog extends CustomAlertDialog {
|
||||
|
||||
public ManagerInstallDialog(@NonNull BaseActivity activity) {
|
||||
super(activity);
|
||||
MagiskManager mm = Data.MM();
|
||||
String filename = Utils.fmt("MagiskManager-v%s(%d).apk",
|
||||
Data.remoteManagerVersionString, Data.remoteManagerVersionCode);
|
||||
setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.app_name)));
|
||||
setMessage(mm.getString(R.string.repo_install_msg, filename));
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.install, (d, i) -> activity.runWithPermission(
|
||||
new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, () -> {
|
||||
Intent intent = new Intent(mm, ManagerUpdate.class);
|
||||
intent.putExtra(Const.Key.INTENT_SET_LINK, Data.managerLink);
|
||||
intent.putExtra(Const.Key.INTENT_SET_FILENAME, filename);
|
||||
mm.sendBroadcast(intent);
|
||||
}))
|
||||
.setNegativeButton(R.string.no_thanks, null);
|
||||
if (!TextUtils.isEmpty(Data.managerNoteLink)) {
|
||||
setNeutralButton(R.string.app_changelog, (d, i) ->
|
||||
new MarkDownWindow(activity, null, Data.managerNoteLink).exec());
|
||||
}
|
||||
}
|
||||
}
|
@@ -40,7 +40,7 @@ public class SnackbarMaker {
|
||||
|
||||
public static void showUri(Activity activity, Uri uri) {
|
||||
make(activity, activity.getString(R.string.internal_storage,
|
||||
"/MagiskManager/" + Utils.getNameFromUri(activity, uri)),
|
||||
"/Download/" + Utils.getNameFromUri(activity, uri)),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.ok, (v)->{}).show();
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class UninstallDialog extends CustomAlertDialog {
|
||||
|
||||
public UninstallDialog(@NonNull Activity activity) {
|
||||
super(activity);
|
||||
MagiskManager mm = Data.MM();
|
||||
setTitle(R.string.uninstall_magisk_title);
|
||||
setMessage(R.string.uninstall_magisk_msg);
|
||||
setNeutralButton(R.string.restore_img, (d, i) -> {
|
||||
ProgressDialog dialog = ProgressDialog.show(activity,
|
||||
activity.getString(R.string.restore_img),
|
||||
activity.getString(R.string.restore_img_msg));
|
||||
Shell.su("restore_imgs").submit(result -> {
|
||||
dialog.cancel();
|
||||
if (result.isSuccess()) {
|
||||
Utils.toast(R.string.restore_done, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
Utils.toast(R.string.restore_fail, Toast.LENGTH_LONG);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!TextUtils.isEmpty(Data.uninstallerLink)) {
|
||||
setPositiveButton(R.string.complete_uninstall, (d, i) ->
|
||||
Download.receive(activity, new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
Intent intent = new Intent(context, FlashActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.setData(uri)
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.UNINSTALL);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}, Data.uninstallerLink, "magisk-uninstaller.zip"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -45,31 +45,32 @@ public abstract class BaseModule implements Comparable<BaseModule> {
|
||||
continue;
|
||||
|
||||
String key = prop[0].trim();
|
||||
if (key.charAt(0) == '#')
|
||||
String value = prop[1].trim();
|
||||
if (key.isEmpty() || key.charAt(0) == '#')
|
||||
continue;
|
||||
|
||||
switch (key) {
|
||||
case "id":
|
||||
mId = prop[1];
|
||||
mId = value;
|
||||
break;
|
||||
case "name":
|
||||
mName = prop[1];
|
||||
mName = value;
|
||||
break;
|
||||
case "version":
|
||||
mVersion = prop[1];
|
||||
mVersion = value;
|
||||
break;
|
||||
case "versionCode":
|
||||
mVersionCode = Integer.parseInt(prop[1]);
|
||||
mVersionCode = Integer.parseInt(value);
|
||||
break;
|
||||
case "author":
|
||||
mAuthor = prop[1];
|
||||
mAuthor = value;
|
||||
break;
|
||||
case "description":
|
||||
mDescription = prop[1];
|
||||
mDescription = value;
|
||||
break;
|
||||
case "minMagisk":
|
||||
case "template":
|
||||
minMagiskVersion = Integer.parseInt(prop[1]);
|
||||
minMagiskVersion = Integer.parseInt(value);
|
||||
break;
|
||||
default:
|
||||
break;
|
@@ -14,9 +14,9 @@ public class Module extends BaseModule {
|
||||
parseProps(Shell.Sync.su("dos2unix < " + path + "/module.prop"));
|
||||
} catch (NumberFormatException ignored) {}
|
||||
|
||||
mRemoveFile = new SuFile(path + "/remove");
|
||||
mDisableFile = new SuFile(path + "/disable");
|
||||
mUpdateFile = new SuFile(path + "/update");
|
||||
mRemoveFile = new SuFile(path, "remove");
|
||||
mDisableFile = new SuFile(path, "disable");
|
||||
mUpdateFile = new SuFile(path, "update");
|
||||
|
||||
if (getId() == null) {
|
||||
int sep = path.lastIndexOf('/');
|
@@ -3,8 +3,8 @@ package com.topjohnwu.magisk.container;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
@@ -76,14 +76,17 @@ public class Repo extends BaseModule {
|
||||
}
|
||||
|
||||
public String getLastUpdateString() {
|
||||
return DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM,
|
||||
MagiskManager.locale).format(mLastUpdate);
|
||||
return DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(mLastUpdate);
|
||||
}
|
||||
|
||||
public Date getLastUpdate() {
|
||||
return mLastUpdate;
|
||||
}
|
||||
|
||||
public String getDownloadFilename() {
|
||||
return Download.getLegalFilename(getName() + "-" + getVersion() + ".zip");
|
||||
}
|
||||
|
||||
public class IllegalRepoException extends Exception {
|
||||
IllegalRepoException(String message) {
|
||||
super(message);
|
@@ -3,7 +3,7 @@ package com.topjohnwu.magisk.container;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -47,10 +47,10 @@ public class SuLogEntry {
|
||||
}
|
||||
|
||||
public String getDateString() {
|
||||
return DateFormat.getDateInstance(DateFormat.MEDIUM, MagiskManager.locale).format(date);
|
||||
return DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date);
|
||||
}
|
||||
|
||||
public String getTimeString() {
|
||||
return new SimpleDateFormat("h:mm a", MagiskManager.locale).format(date);
|
||||
return new SimpleDateFormat("h:mm a", LocaleManager.locale).format(date);
|
||||
}
|
||||
}
|
@@ -11,11 +11,13 @@ import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
@@ -29,7 +31,7 @@ import java.util.List;
|
||||
|
||||
public class MagiskDatabaseHelper {
|
||||
|
||||
private static final int DATABASE_VER = 5;
|
||||
private static final int DATABASE_VER = 6;
|
||||
private static final String POLICY_TABLE = "policies";
|
||||
private static final String LOG_TABLE = "logs";
|
||||
private static final String SETTINGS_TABLE = "settings";
|
||||
@@ -37,6 +39,7 @@ public class MagiskDatabaseHelper {
|
||||
|
||||
private PackageManager pm;
|
||||
private SQLiteDatabase db;
|
||||
private MagiskManager mm;
|
||||
|
||||
@NonNull
|
||||
public static MagiskDatabaseHelper getInstance(MagiskManager mm) {
|
||||
@@ -44,12 +47,13 @@ public class MagiskDatabaseHelper {
|
||||
return new MagiskDatabaseHelper(mm);
|
||||
} catch (Exception e) {
|
||||
// Let's cleanup everything and try again
|
||||
Shell.Sync.su("db_clean '*'");
|
||||
Shell.su("db_clean '*'").exec();
|
||||
return new MagiskDatabaseHelper(mm);
|
||||
}
|
||||
}
|
||||
|
||||
private MagiskDatabaseHelper(MagiskManager mm) {
|
||||
private MagiskDatabaseHelper(MagiskManager context) {
|
||||
mm = context;
|
||||
pm = mm.getPackageManager();
|
||||
db = openDatabase(mm);
|
||||
db.disableWriteAheadLogging();
|
||||
@@ -72,13 +76,12 @@ public class MagiskDatabaseHelper {
|
||||
// We don't want the app to crash, create a db and return
|
||||
return mm.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
|
||||
}
|
||||
mm.loadMagiskInfo();
|
||||
// Cleanup
|
||||
Shell.Sync.su("db_clean " + Const.USER_ID);
|
||||
if (mm.magiskVersionCode < Const.MAGISK_VER.FBE_AWARE) {
|
||||
Shell.su("db_clean " + Const.USER_ID).exec();
|
||||
if (Data.magiskVersionCode < Const.MAGISK_VER.FBE_AWARE) {
|
||||
// Super old legacy mode
|
||||
return mm.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
|
||||
} else if (mm.magiskVersionCode < Const.MAGISK_VER.HIDDEN_PATH) {
|
||||
} else if (Data.magiskVersionCode < Const.MAGISK_VER.HIDDEN_PATH) {
|
||||
// Legacy mode with FBE aware
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
de.moveDatabaseFrom(mm, "su.db");
|
||||
@@ -89,17 +92,17 @@ public class MagiskDatabaseHelper {
|
||||
final SuFile GLOBAL_DB = new SuFile("/data/adb/magisk.db");
|
||||
mm.deleteDatabase("su.db");
|
||||
de.deleteDatabase("su.db");
|
||||
if (mm.magiskVersionCode < Const.MAGISK_VER.SEPOL_REFACTOR) {
|
||||
if (Data.magiskVersionCode < Const.MAGISK_VER.SEPOL_REFACTOR) {
|
||||
// We need some additional policies on old versions
|
||||
Shell.Sync.su("db_sepatch");
|
||||
Shell.su("db_sepatch").exec();
|
||||
}
|
||||
if (!GLOBAL_DB.exists()) {
|
||||
Shell.Sync.su("db_init");
|
||||
Shell.su("db_init").exec();
|
||||
SQLiteDatabase.openOrCreateDatabase(GLOBAL_DB, null).close();
|
||||
Shell.Sync.su("db_restore");
|
||||
Shell.su("db_restore").exec();
|
||||
}
|
||||
}
|
||||
Shell.Sync.su("db_setup " + Process.myUid());
|
||||
Shell.su("db_setup " + Process.myUid()).exec();
|
||||
}
|
||||
// Not using legacy mode, open the mounted global DB
|
||||
return SQLiteDatabase.openOrCreateDatabase(DB_FILE, null);
|
||||
@@ -123,7 +126,7 @@ public class MagiskDatabaseHelper {
|
||||
POLICY_TABLE, POLICY_TABLE));
|
||||
db.execSQL(Utils.fmt("DROP TABLE %s_old", POLICY_TABLE));
|
||||
|
||||
MagiskManager.get().deleteDatabase("sulog.db");
|
||||
Data.MM().deleteDatabase("sulog.db");
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 2) {
|
||||
@@ -138,11 +141,16 @@ public class MagiskDatabaseHelper {
|
||||
db.execSQL(Utils.fmt("UPDATE %s SET uid=uid%%100000", POLICY_TABLE));
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 5) {
|
||||
setSettings(Const.Key.SU_FINGERPRINT,
|
||||
mm.prefs.getBoolean(Const.Key.SU_FINGERPRINT, false) ? 1 : 0);
|
||||
++oldVersion;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove everything, we do not support downgrade
|
||||
public void onDowngrade(SQLiteDatabase db) {
|
||||
MagiskManager.toast(R.string.su_db_corrupt, Toast.LENGTH_LONG);
|
||||
Utils.toast(R.string.su_db_corrupt, Toast.LENGTH_LONG);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + POLICY_TABLE);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + LOG_TABLE);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + SETTINGS_TABLE);
|
||||
@@ -174,7 +182,7 @@ public class MagiskDatabaseHelper {
|
||||
// Clear outdated policies
|
||||
db.delete(POLICY_TABLE, Utils.fmt("until > 0 AND until < %d", System.currentTimeMillis() / 1000), null);
|
||||
// Clear outdated logs
|
||||
db.delete(LOG_TABLE, Utils.fmt("time < %d", System.currentTimeMillis() - MagiskManager.get().suLogTimeout * 86400000), null);
|
||||
db.delete(LOG_TABLE, Utils.fmt("time < %d", System.currentTimeMillis() - Data.suLogTimeout * 86400000), null);
|
||||
}
|
||||
|
||||
public void deletePolicy(Policy policy) {
|
||||
@@ -236,7 +244,7 @@ public class MagiskDatabaseHelper {
|
||||
String dateString = null, newString;
|
||||
while (c.moveToNext()) {
|
||||
Date date = new Date(c.getLong(c.getColumnIndex("time")));
|
||||
newString = DateFormat.getDateInstance(DateFormat.MEDIUM, MagiskManager.locale).format(date);
|
||||
newString = DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date);
|
||||
if (!TextUtils.equals(dateString, newString)) {
|
||||
dateString = newString;
|
||||
list = new ArrayList<>();
|
@@ -5,10 +5,11 @@ import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -20,10 +21,11 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
private SQLiteDatabase mDb;
|
||||
private MagiskManager mm;
|
||||
private ReposAdapter adapter;
|
||||
|
||||
public RepoDatabaseHelper(Context context) {
|
||||
super(context, "repo.db", null, DATABASE_VER);
|
||||
mm = Utils.getMagiskManager(context);
|
||||
mm = Data.MM();
|
||||
mDb = getWritableDatabase();
|
||||
|
||||
// Remove outdated repos
|
||||
@@ -63,15 +65,18 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public void clearRepo() {
|
||||
mDb.delete(TABLE_NAME, null, null);
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
|
||||
public void removeRepo(String id) {
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public void removeRepo(Repo repo) {
|
||||
mDb.delete(TABLE_NAME, "repo_name=?", new String[] { repo.getRepoName() });
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public void removeRepo(Iterable<String> list) {
|
||||
@@ -79,10 +84,12 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
if (id == null) continue;
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||
}
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public void addRepo(Repo repo) {
|
||||
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public Repo getRepo(String id) {
|
||||
@@ -100,7 +107,7 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public Cursor getRepoCursor() {
|
||||
String orderBy = null;
|
||||
switch (mm.repoOrder) {
|
||||
switch (Data.repoOrder) {
|
||||
case Const.Value.ORDER_NAME:
|
||||
orderBy = "name COLLATE NOCASE";
|
||||
break;
|
||||
@@ -108,7 +115,7 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
orderBy = "last_update DESC";
|
||||
}
|
||||
return mDb.query(TABLE_NAME, null, "minMagisk<=? AND minMagisk>=?",
|
||||
new String[] { String.valueOf(mm.magiskVersionCode), String.valueOf(Const.MIN_MODULE_VER()) },
|
||||
new String[] { String.valueOf(Data.magiskVersionCode), String.valueOf(Const.MIN_MODULE_VER()) },
|
||||
null, null, orderBy);
|
||||
}
|
||||
|
||||
@@ -121,4 +128,18 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
public void registerAdapter(ReposAdapter a) {
|
||||
adapter = a;
|
||||
}
|
||||
|
||||
public void unregisterAdapter() {
|
||||
adapter = null;
|
||||
}
|
||||
|
||||
private void notifyAdapter() {
|
||||
if (adapter != null) {
|
||||
Data.mainHandler.post(adapter::notifyDBChanged);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
|
||||
import android.os.Bundle;
|
||||
@@ -8,15 +8,18 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MainActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.TabFragmentAdapter;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class LogFragment extends Fragment {
|
||||
public class LogFragment extends BaseFragment {
|
||||
|
||||
private Unbinder unbinder;
|
||||
|
||||
@@ -30,11 +33,11 @@ public class LogFragment extends Fragment {
|
||||
View v = inflater.inflate(R.layout.fragment_log, container, false);
|
||||
unbinder = ButterKnife.bind(this, v);
|
||||
|
||||
((MainActivity) getActivity()).toolbar.setElevation(0);
|
||||
((MainActivity) requireActivity()).toolbar.setElevation(0);
|
||||
|
||||
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
|
||||
|
||||
if (!(Const.USER_ID > 0 && getApplication().multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
if (!(Const.USER_ID > 0 && Data.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
adapter.addTab(new SuLogFragment(), getString(R.string.superuser));
|
||||
}
|
||||
adapter.addTab(new MagiskLogFragment(), getString(R.string.magisk));
|
@@ -1,13 +1,15 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
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;
|
||||
@@ -18,16 +20,24 @@ import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MainActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.CheckSafetyNet;
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.components.EnvFixDialog;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.components.MagiskInstallDialog;
|
||||
import com.topjohnwu.magisk.components.ManagerInstallDialog;
|
||||
import com.topjohnwu.magisk.components.UninstallDialog;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
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;
|
||||
|
||||
@@ -37,17 +47,17 @@ import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class MagiskFragment extends Fragment
|
||||
implements Topic.Subscriber, SwipeRefreshLayout.OnRefreshListener, ExpandableView {
|
||||
public class MagiskFragment extends BaseFragment
|
||||
implements SwipeRefreshLayout.OnRefreshListener, ExpandableView, Topic.Subscriber {
|
||||
|
||||
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.core_only_notice) CardView coreOnlyNotice;
|
||||
|
||||
@BindView(R.id.magisk_update) RelativeLayout magiskUpdate;
|
||||
@BindView(R.id.magisk_update_icon) ImageView magiskUpdateIcon;
|
||||
@BindView(R.id.magisk_update_status) TextView magiskUpdateText;
|
||||
@@ -84,19 +94,12 @@ public class MagiskFragment extends Fragment
|
||||
safetyNetProgress.setVisibility(View.VISIBLE);
|
||||
safetyNetRefreshIcon.setVisibility(View.GONE);
|
||||
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
|
||||
new CheckSafetyNet(getActivity()).exec();
|
||||
new CheckSafetyNet(requireActivity()).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()) {
|
||||
if (!CheckSafetyNet.dexPath.exists()) {
|
||||
// Show dialog
|
||||
new AlertDialogBuilder(getActivity())
|
||||
new CustomAlertDialog(requireActivity())
|
||||
.setTitle(R.string.proprietary_title)
|
||||
.setMessage(R.string.proprietary_notice)
|
||||
.setCancelable(true)
|
||||
@@ -114,36 +117,35 @@ public class MagiskFragment extends Fragment
|
||||
shownDialog = true;
|
||||
|
||||
// Show Manager update first
|
||||
if (mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
ShowUI.managerInstallDialog(getActivity());
|
||||
if (Data.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
new ManagerInstallDialog((BaseActivity) requireActivity()).show();
|
||||
return;
|
||||
}
|
||||
|
||||
((NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
|
||||
ShowUI.magiskInstallDialog(getActivity());
|
||||
new MagiskInstallDialog((BaseActivity) getActivity()).show();
|
||||
}
|
||||
|
||||
@OnClick(R.id.uninstall_button)
|
||||
void uninstall() {
|
||||
ShowUI.uninstallDialog(getActivity());
|
||||
new UninstallDialog(requireActivity()).show();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
public View onCreateView(@NonNull 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();
|
||||
requireActivity().setTitle(R.string.magisk);
|
||||
|
||||
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);
|
||||
keepVerityChkbox.setChecked(Data.keepVerity);
|
||||
keepVerityChkbox.setOnCheckedChangeListener((view, checked) -> Data.keepVerity = checked);
|
||||
keepEncChkbox.setChecked(Data.keepEnc);
|
||||
keepEncChkbox.setOnCheckedChangeListener((view, checked) -> Data.keepEnc = checked);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(this);
|
||||
updateUI();
|
||||
@@ -153,7 +155,7 @@ public class MagiskFragment extends Fragment
|
||||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
mm.loadMagiskInfo();
|
||||
Data.loadMagiskInfo();
|
||||
updateUI();
|
||||
|
||||
magiskUpdateText.setText(R.string.checking_for_updates);
|
||||
@@ -162,34 +164,36 @@ public class MagiskFragment extends Fragment
|
||||
|
||||
safetyNetStatusText.setText(R.string.safetyNet_check_text);
|
||||
|
||||
mm.safetyNetDone.reset();
|
||||
mm.updateCheckDone.reset();
|
||||
mm.remoteMagiskVersionString = null;
|
||||
mm.remoteMagiskVersionCode = -1;
|
||||
Topic.reset(getSubscribedTopics());
|
||||
Data.remoteMagiskVersionString = null;
|
||||
Data.remoteMagiskVersionCode = -1;
|
||||
collapse();
|
||||
|
||||
shownDialog = false;
|
||||
|
||||
// Trigger state check
|
||||
if (Utils.checkNetworkStatus()) {
|
||||
new CheckUpdates().exec();
|
||||
if (Download.checkNetworkStatus(mm)) {
|
||||
CheckUpdates.check();
|
||||
} 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]);
|
||||
}
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.SNET_CHECK_DONE, Topic.UPDATE_CHECK_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { mm.updateCheckDone, mm.safetyNetDone };
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
switch (topic) {
|
||||
case Topic.SNET_CHECK_DONE:
|
||||
updateSafetyNetUI((int) result[0]);
|
||||
break;
|
||||
case Topic.UPDATE_CHECK_DONE:
|
||||
updateCheckUI();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -203,28 +207,39 @@ public class MagiskFragment extends Fragment
|
||||
return expandableContainer;
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
((MainActivity) getActivity()).checkHideSection();
|
||||
private boolean hasGms() {
|
||||
PackageManager pm = mm.getPackageManager();
|
||||
PackageInfo info;
|
||||
try {
|
||||
info = pm.getPackageInfo("com.google.android.gms", 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
return info.applicationInfo.enabled;
|
||||
}
|
||||
|
||||
boolean hasNetwork = Utils.checkNetworkStatus();
|
||||
private void updateUI() {
|
||||
((MainActivity) requireActivity()).checkHideSection();
|
||||
|
||||
boolean hasNetwork = Download.checkNetworkStatus(mm);
|
||||
boolean hasRoot = Shell.rootAccess();
|
||||
boolean isUpToDate = mm.magiskVersionCode > Const.MAGISK_VER.UNIFIED;
|
||||
boolean isUpToDate = Data.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);
|
||||
coreOnlyNotice.setVisibility(mm.prefs.getBoolean(Const.Key.COREONLY, false) ? View.VISIBLE : View.GONE);
|
||||
|
||||
int image, color;
|
||||
|
||||
if (mm.magiskVersionCode < 0) {
|
||||
if (Data.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));
|
||||
magiskVersionText.setText(getString(R.string.current_magisk_title, "v" + Data.magiskVersionString));
|
||||
}
|
||||
|
||||
magiskStatusIcon.setImageResource(image);
|
||||
@@ -234,7 +249,9 @@ public class MagiskFragment extends Fragment
|
||||
private void updateCheckUI() {
|
||||
int image, color;
|
||||
|
||||
if (mm.remoteMagiskVersionCode < 0) {
|
||||
safetyNetCard.setVisibility(hasGms() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (Data.remoteMagiskVersionCode < 0) {
|
||||
color = colorNeutral;
|
||||
image = R.drawable.ic_help;
|
||||
magiskUpdateText.setText(R.string.invalid_update_channel);
|
||||
@@ -242,11 +259,11 @@ public class MagiskFragment extends Fragment
|
||||
} else {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
magiskUpdateText.setText(getString(R.string.install_magisk_title, "v" + mm.remoteMagiskVersionString));
|
||||
magiskUpdateText.setText(getString(R.string.install_magisk_title, "v" + Data.remoteMagiskVersionString));
|
||||
installButton.setVisibility(View.VISIBLE);
|
||||
if (mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
if (Data.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
installText.setText(getString(R.string.update, getString(R.string.app_name)));
|
||||
} else if (mm.magiskVersionCode > 0 && mm.remoteMagiskVersionCode > mm.magiskVersionCode) {
|
||||
} else if (Data.magiskVersionCode > 0 && Data.remoteMagiskVersionCode > Data.magiskVersionCode) {
|
||||
installText.setText(getString(R.string.update, getString(R.string.magisk)));
|
||||
} else {
|
||||
installText.setText(R.string.install);
|
||||
@@ -261,12 +278,12 @@ public class MagiskFragment extends Fragment
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
|
||||
if (!shownDialog) {
|
||||
if (mm.remoteMagiskVersionCode > mm.magiskVersionCode
|
||||
|| mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
if (Data.remoteMagiskVersionCode > Data.magiskVersionCode
|
||||
|| Data.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
install();
|
||||
} else if (mm.remoteMagiskVersionCode >= Const.MAGISK_VER.FIX_ENV &&
|
||||
} else if (Data.remoteMagiskVersionCode >= Const.MAGISK_VER.FIX_ENV &&
|
||||
!ShellUtils.fastCmdResult("env_check")) {
|
||||
ShowUI.envFixDialog(getActivity());
|
||||
new EnvFixDialog(requireActivity()).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,12 +309,6 @@ public class MagiskFragment extends Fragment
|
||||
} 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;
|
@@ -1,6 +1,7 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -11,19 +12,21 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SearchView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber {
|
||||
|
||||
private Unbinder unbinder;
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
SearchView search;
|
||||
|
||||
private ApplicationAdapter appAdapter;
|
||||
|
||||
@@ -37,16 +40,16 @@ public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
public View onCreateView(@NonNull 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();
|
||||
appAdapter = new ApplicationAdapter(requireActivity());
|
||||
recyclerView.setAdapter(appAdapter);
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(appAdapter::refresh);
|
||||
|
||||
searchListener = new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
@@ -61,7 +64,7 @@ public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
}
|
||||
};
|
||||
|
||||
getActivity().setTitle(R.string.magiskhide);
|
||||
requireActivity().setTitle(R.string.magiskhide);
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -69,7 +72,7 @@ public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
@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 = (SearchView) menu.findItem(R.id.app_search).getActionView();
|
||||
search.setOnQueryTextListener(searchListener);
|
||||
}
|
||||
|
||||
@@ -80,13 +83,13 @@ public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
appAdapter.filter(null);
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.MAGISK_HIDE_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { getApplication().magiskHideDone };
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
appAdapter.filter(search.getQuery().toString());
|
||||
}
|
||||
}
|
@@ -1,8 +1,7 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
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;
|
||||
@@ -17,23 +16,23 @@ import android.widget.ProgressBar;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
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 {
|
||||
public class MagiskLogFragment extends BaseFragment {
|
||||
|
||||
private Unbinder unbinder;
|
||||
|
||||
@@ -93,24 +92,15 @@ public class MagiskLogFragment extends Fragment {
|
||||
}
|
||||
|
||||
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) {
|
||||
Shell.su("cat " + Const.MAGISK_LOG + " | tail -n 5000").submit(result -> {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (result.getOut().isEmpty())
|
||||
txtLog.setText(R.string.log_is_empty);
|
||||
}
|
||||
}, "cat " + Const.MAGISK_LOG + " | tail -n 5000");
|
||||
else
|
||||
txtLog.setText(TextUtils.join("\n", result.getOut()));
|
||||
svLog.postDelayed(() -> svLog.fullScroll(ScrollView.FOCUS_DOWN), 100);
|
||||
hsvLog.postDelayed(() -> hsvLog.fullScroll(ScrollView.FOCUS_LEFT), 100);
|
||||
});
|
||||
}
|
||||
|
||||
public void saveLogs() {
|
||||
@@ -120,26 +110,20 @@ public class MagiskLogFragment extends Fragment {
|
||||
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();
|
||||
File logFile = new File(Download.EXTERNAL_PATH, filename);
|
||||
try {
|
||||
targetFile.createNewFile();
|
||||
logFile.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);
|
||||
Shell.su("cat " + Const.MAGISK_LOG + " > " + logFile)
|
||||
.submit(result ->
|
||||
SnackbarMaker.make(txtLog, logFile.getPath(), Snackbar.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
public void clearLogs() {
|
||||
Shell.Async.su("echo -n > " + Const.MAGISK_LOG);
|
||||
Shell.su("echo -n > " + Const.MAGISK_LOG).submit();
|
||||
txtLog.setText(R.string.log_is_empty);
|
||||
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
@@ -1,9 +1,10 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -15,23 +16,26 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.ModulesAdapter;
|
||||
import com.topjohnwu.magisk.asyncs.LoadModules;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
public class ModulesFragment extends BaseFragment implements Topic.Subscriber {
|
||||
|
||||
private Unbinder unbinder;
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@@ -50,14 +54,14 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
public View onCreateView(@NonNull 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();
|
||||
Utils.loadModules();
|
||||
});
|
||||
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@@ -72,19 +76,19 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
}
|
||||
});
|
||||
|
||||
getActivity().setTitle(R.string.modules);
|
||||
requireActivity().setTitle(R.string.modules);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
updateUI();
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.MODULE_LOAD_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { getApplication().moduleLoadDone };
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
updateUI((Map<String, Module>) result[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,25 +116,25 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.reboot:
|
||||
Shell.Async.su("/system/bin/reboot");
|
||||
Shell.su("/system/bin/reboot").submit();
|
||||
return true;
|
||||
case R.id.reboot_recovery:
|
||||
Shell.Async.su("/system/bin/reboot recovery");
|
||||
Shell.su("/system/bin/reboot recovery").submit();
|
||||
return true;
|
||||
case R.id.reboot_bootloader:
|
||||
Shell.Async.su("/system/bin/reboot bootloader");
|
||||
Shell.su("/system/bin/reboot bootloader").submit();
|
||||
return true;
|
||||
case R.id.reboot_download:
|
||||
Shell.Async.su("/system/bin/reboot download");
|
||||
Shell.su("/system/bin/reboot download").submit();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
private void updateUI(Map<String, Module> moduleMap) {
|
||||
listModules.clear();
|
||||
listModules.addAll(getApplication().moduleMap.values());
|
||||
listModules.addAll(moduleMap.values());
|
||||
if (listModules.size() == 0) {
|
||||
emptyRv.setVisibility(View.VISIBLE);
|
||||
recyclerView.setVisibility(View.GONE);
|
@@ -1,7 +1,8 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -14,25 +15,29 @@ import android.view.ViewGroup;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.R;
|
||||
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.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
public class ReposFragment extends BaseFragment 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;
|
||||
private ReposAdapter adapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
@@ -42,47 +47,43 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
public View onCreateView(@NonNull 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.setRefreshing(true);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
new UpdateRepos(true).exec();
|
||||
new UpdateRepos().exec(true);
|
||||
});
|
||||
|
||||
getActivity().setTitle(R.string.downloads);
|
||||
requireActivity().setTitle(R.string.downloads);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
adapter = new ReposAdapter(mm.repoDB, mm.moduleMap);
|
||||
recyclerView.setAdapter(adapter);
|
||||
super.onResume();
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.MODULE_LOAD_DONE, Topic.REPO_LOAD_DONE};
|
||||
}
|
||||
|
||||
@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 };
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
if (topic == Topic.MODULE_LOAD_DONE) {
|
||||
adapter = new ReposAdapter(mm.repoDB, (Map<String, Module>) result[0]);
|
||||
mm.repoDB.registerAdapter(adapter);
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
}
|
||||
if (Topic.isPublished(getSubscribedTopics())) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
recyclerView.setVisibility(adapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
|
||||
emptyRv.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,9 +109,9 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
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();
|
||||
.setSingleChoiceItems(R.array.sorting_orders, Data.repoOrder, (d, which) -> {
|
||||
Data.repoOrder = which;
|
||||
mm.prefs.edit().putInt(Const.Key.REPO_ORDER, Data.repoOrder).apply();
|
||||
adapter.notifyDBChanged();
|
||||
d.dismiss();
|
||||
}).show();
|
||||
@@ -121,6 +122,7 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mm.repoDB.unregisterAdapter();
|
||||
unbinder.unbind();
|
||||
}
|
||||
}
|
@@ -0,0 +1,355 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v14.preference.SwitchPreference;
|
||||
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.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.asyncs.PatchAPK;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
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;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragmentCompat
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
Topic.Subscriber, Topic.AutoSubscriber {
|
||||
|
||||
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 = Data.MM();
|
||||
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) -> {
|
||||
mm.prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
||||
mm.repoDB.clearRepo();
|
||||
Utils.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((p, o) -> {
|
||||
String prev =String.valueOf(Data.updateChannel);
|
||||
int channel = Integer.parseInt((String) o);
|
||||
if (channel == Const.Value.CUSTOM_CHANNEL) {
|
||||
View v = LayoutInflater.from(requireActivity()).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(requireActivity())
|
||||
.setTitle(R.string.settings_update_custom)
|
||||
.setView(v)
|
||||
.setPositiveButton(R.string.ok, (d, i) ->
|
||||
mm.prefs.edit().putString(Const.Key.CUSTOM_CHANNEL,
|
||||
url.getText().toString()).apply())
|
||||
.setNegativeButton(R.string.close, (d, i) ->
|
||||
mm.prefs.edit().putString(Const.Key.UPDATE_CHANNEL, prev).apply())
|
||||
.setOnCancelListener(d ->
|
||||
mm.prefs.edit().putString(Const.Key.UPDATE_CHANNEL, prev).apply())
|
||||
.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.setChecked(false);
|
||||
reauth.setSummary(R.string.android_o_not_support);
|
||||
}
|
||||
|
||||
// Disable fingerprint option if not possible
|
||||
if (!FingerprintHelper.canUseFingerprint()) {
|
||||
fingerprint.setEnabled(false);
|
||||
fingerprint.setChecked(false);
|
||||
fingerprint.setSummary(R.string.disable_fingerprint);
|
||||
}
|
||||
|
||||
if (Data.magiskVersionCode >= Const.MAGISK_VER.MANAGER_HIDE) {
|
||||
if (mm.getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||
PatchAPK.hideManager(requireActivity());
|
||||
return true;
|
||||
});
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
} else {
|
||||
if (Download.checkNetworkStatus(mm)) {
|
||||
restoreManager.setOnPreferenceClickListener((pref) -> {
|
||||
Download.receive(
|
||||
requireActivity(), new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
Data.exportPrefs();
|
||||
Shell.su("cp " + uri.getPath() + " /data/local/tmp/manager.apk").exec();
|
||||
if (ShellUtils.fastCmdResult("pm install /data/local/tmp/manager.apk")) {
|
||||
Shell.su("rm -f /data/local/tmp/manager.apk").exec();
|
||||
RootUtils.uninstallPkg(context.getPackageName());
|
||||
return;
|
||||
}
|
||||
Shell.su("rm -f /data/local/tmp/manager.apk").exec();
|
||||
}
|
||||
},
|
||||
Data.managerLink,
|
||||
Utils.fmt("MagiskManager-v%s.apk", Data.remoteManagerVersionString)
|
||||
);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
}
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
|
||||
if (!Shell.rootAccess() || (Const.USER_ID > 0 &&
|
||||
Data.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
prefScreen.removePreference(suCategory);
|
||||
}
|
||||
|
||||
if (!Shell.rootAccess()) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
} else if (Data.magiskVersionCode < Const.MAGISK_VER.UNIFIED) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
}
|
||||
}
|
||||
|
||||
private void setLocalePreference(ListPreference lp) {
|
||||
CharSequence[] entries = new CharSequence[LocaleManager.locales.size() + 1];
|
||||
CharSequence[] entryValues = new CharSequence[LocaleManager.locales.size() + 1];
|
||||
entries[0] = LocaleManager.getString(LocaleManager.defaultLocale, R.string.system_default);
|
||||
entryValues[0] = "";
|
||||
int i = 1;
|
||||
for (Locale locale : LocaleManager.locales) {
|
||||
entries[i] = locale.getDisplayName(locale);
|
||||
entryValues[i++] = locale.toLanguageTag();
|
||||
}
|
||||
lp.setEntries(entries);
|
||||
lp.setEntryValues(entryValues);
|
||||
lp.setSummary(LocaleManager.locale.getDisplayName(LocaleManager.locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
mm.prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
Topic.subscribe(this);
|
||||
requireActivity().setTitle(R.string.settings);
|
||||
return super.onCreateView(inflater, container, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
mm.prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
Topic.unsubscribe(this);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
switch (key) {
|
||||
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;
|
||||
}
|
||||
Data.loadConfig();
|
||||
setSummary();
|
||||
switch (key) {
|
||||
case Const.Key.DARK_THEME:
|
||||
Topic.publish(false, Topic.RELOAD_ACTIVITY);
|
||||
break;
|
||||
case Const.Key.COREONLY:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
try {
|
||||
Const.MAGISK_DISABLE_FILE.createNewFile();
|
||||
} catch (IOException ignored) {}
|
||||
} else {
|
||||
Const.MAGISK_DISABLE_FILE.delete();
|
||||
}
|
||||
Utils.toast(R.string.settings_reboot_toast, Toast.LENGTH_LONG);
|
||||
break;
|
||||
case Const.Key.MAGISKHIDE:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.su("magiskhide --enable").submit();
|
||||
} else {
|
||||
Shell.su("magiskhide --disable").submit();
|
||||
}
|
||||
break;
|
||||
case Const.Key.HOSTS:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.su("cp -af /system/etc/hosts " + Const.MAGISK_HOST_FILE,
|
||||
"mount -o bind " + Const.MAGISK_HOST_FILE + " /system/etc/hosts")
|
||||
.submit();
|
||||
} else {
|
||||
Shell.su("umount -l /system/etc/hosts",
|
||||
"rm -f " + Const.MAGISK_HOST_FILE)
|
||||
.submit();
|
||||
}
|
||||
break;
|
||||
case Const.Key.LOCALE:
|
||||
LocaleManager.setLocale(mm);
|
||||
Topic.publish(false, Topic.RELOAD_ACTIVITY);
|
||||
break;
|
||||
case Const.Key.UPDATE_CHANNEL:
|
||||
case Const.Key.CUSTOM_CHANNEL:
|
||||
CheckUpdates.check();
|
||||
break;
|
||||
case Const.Key.CHECK_UPDATES:
|
||||
Utils.setupUpdateCheck();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(Preference preference) {
|
||||
String key = preference.getKey();
|
||||
switch (key) {
|
||||
case Const.Key.SU_FINGERPRINT:
|
||||
boolean checked = ((SwitchPreference) preference).isChecked();
|
||||
((SwitchPreference) preference).setChecked(!checked);
|
||||
CustomAlertDialog dialog = new CustomAlertDialog(requireActivity());
|
||||
CustomAlertDialog.ViewHolder vh = dialog.getViewHolder();
|
||||
Drawable fingerprint = getResources().getDrawable(R.drawable.ic_fingerprint);
|
||||
fingerprint.setBounds(0, 0, Utils.dpInPx(50), Utils.dpInPx(50));
|
||||
Resources.Theme theme = requireActivity().getTheme();
|
||||
TypedArray ta = theme.obtainStyledAttributes(new int[] {R.attr.imageColorTint});
|
||||
fingerprint.setTint(ta.getColor(0, Color.GRAY));
|
||||
ta.recycle();
|
||||
vh.messageView.setCompoundDrawables(null, null, null, fingerprint);
|
||||
vh.messageView.setCompoundDrawablePadding(Utils.dpInPx(20));
|
||||
vh.messageView.setGravity(Gravity.CENTER);
|
||||
try {
|
||||
FingerprintHelper helper = new FingerprintHelper() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(R.string.auth_fail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
dialog.dismiss();
|
||||
((SwitchPreference) preference).setChecked(checked);
|
||||
mm.mDB.setSettings(key, checked ? 1 : 0);
|
||||
|
||||
}
|
||||
};
|
||||
dialog.setMessage(R.string.auth_fingerprint)
|
||||
.setNegativeButton(R.string.close, (d, w) -> helper.cancel())
|
||||
.setOnCancelListener(d -> helper.cancel())
|
||||
.show();
|
||||
helper.authenticate();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setSummary() {
|
||||
updateChannel.setSummary(getResources()
|
||||
.getStringArray(R.array.update_channel)[Data.updateChannel]);
|
||||
suAccess.setSummary(getResources()
|
||||
.getStringArray(R.array.su_access)[Data.suAccessState]);
|
||||
autoRes.setSummary(getResources()
|
||||
.getStringArray(R.array.auto_response)[Data.suResponseType]);
|
||||
suNotification.setSummary(getResources()
|
||||
.getStringArray(R.array.su_notification)[Data.suNotificationType]);
|
||||
requestTimeout.setSummary(
|
||||
getString(R.string.request_timeout_summary,
|
||||
mm.prefs.getString(Const.Key.SU_REQUEST_TIMEOUT, "10")));
|
||||
multiuserMode.setSummary(getResources()
|
||||
.getStringArray(R.array.multiuser_summary)[Data.multiuserMode]);
|
||||
namespaceMode.setSummary(getResources()
|
||||
.getStringArray(R.array.namespace_summary)[Data.suNamespaceMode]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
setLocalePreference((ListPreference) findPreference(Const.Key.LOCALE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.LOCALE_FETCH_DONE};
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
@@ -11,20 +11,20 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.SuLogAdapter;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class SuLogFragment extends Fragment {
|
||||
public class SuLogFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
|
||||
private Unbinder unbinder;
|
||||
private MagiskManager mm;
|
||||
private SuLogAdapter adapter;
|
||||
|
||||
@Override
|
||||
@@ -45,7 +45,6 @@ public class SuLogFragment extends Fragment {
|
||||
// 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);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
@@ -9,8 +9,9 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.PolicyAdapter;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
|
||||
import java.util.List;
|
||||
@@ -19,9 +20,10 @@ import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class SuperuserFragment extends Fragment {
|
||||
public class SuperuserFragment extends BaseFragment {
|
||||
|
||||
private Unbinder unbinder;
|
||||
private PackageManager pm;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
|
||||
@@ -31,9 +33,29 @@ public class SuperuserFragment extends Fragment {
|
||||
View view = inflater.inflate(R.layout.fragment_superuser, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
|
||||
PackageManager pm = getActivity().getPackageManager();
|
||||
MagiskManager mm = getApplication();
|
||||
pm = getActivity().getPackageManager();
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
requireActivity().setTitle(getString(R.string.superuser));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
displayPolicyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unbinder.unbind();
|
||||
}
|
||||
|
||||
private void displayPolicyList() {
|
||||
List<Policy> policyList = mm.mDB.getPolicyList(pm);
|
||||
|
||||
if (policyList.size() == 0) {
|
||||
@@ -44,20 +66,6 @@ public class SuperuserFragment extends Fragment {
|
||||
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,18 @@
|
||||
package com.topjohnwu.magisk.receivers;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.services.OnBootService;
|
||||
|
||||
public class BootReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (TextUtils.equals(intent.getAction(), Intent.ACTION_BOOT_COMPLETED))
|
||||
OnBootService.enqueueWork(context);
|
||||
}
|
||||
|
||||
}
|
@@ -6,20 +6,21 @@ import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.PatchAPK;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.asyncs.PatchAPK;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.utils.JarMap;
|
||||
import com.topjohnwu.utils.SignAPK;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
public class ManagerUpdate extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Utils.dlAndReceive(
|
||||
Download.receive(
|
||||
context, new PatchedInstall(),
|
||||
intent.getStringExtra(Const.Key.INTENT_SET_LINK),
|
||||
intent.getStringExtra(Const.Key.INTENT_SET_FILENAME)
|
||||
@@ -31,13 +32,14 @@ public class ManagerUpdate extends BroadcastReceiver {
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
if (!context.getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
String o = uri.getPath();
|
||||
String p = o.substring(0, o.lastIndexOf('.')) + "-patched.apk";
|
||||
String orig = uri.getPath();
|
||||
String patch = orig.substring(0, orig.lastIndexOf('.')) + "-patched.apk";
|
||||
try {
|
||||
PatchAPK.patchPackageID(o, new BufferedOutputStream(new FileOutputStream(p)),
|
||||
Const.ORIG_PKG_NAME, context.getPackageName());
|
||||
} catch (FileNotFoundException ignored) { }
|
||||
super.onDownloadDone(context, Uri.fromFile(new File(p)));
|
||||
JarMap apk = new JarMap(orig);
|
||||
PatchAPK.patchPackageID(apk, Const.ORIG_PKG_NAME, context.getPackageName());
|
||||
SignAPK.sign(apk, new BufferedOutputStream(new FileOutputStream(patch)));
|
||||
super.onDownloadDone(context, Uri.fromFile(new File(patch)));
|
||||
} catch (Exception ignored) { }
|
||||
});
|
||||
} else {
|
||||
super.onDownloadDone(context, uri);
|
@@ -4,15 +4,15 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class PackageReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
MagiskManager mm = Utils.getMagiskManager(context);
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
String pkg = intent.getData().getEncodedSchemeSpecificPart();
|
||||
|
||||
@@ -25,7 +25,7 @@ public class PackageReceiver extends BroadcastReceiver {
|
||||
break;
|
||||
case Intent.ACTION_PACKAGE_FULLY_REMOVED:
|
||||
mm.mDB.deletePolicy(pkg);
|
||||
Shell.Async.su("magiskhide --rm " + pkg);
|
||||
Shell.su("magiskhide --rm " + pkg).submit();
|
||||
break;
|
||||
}
|
||||
}
|
@@ -9,6 +9,6 @@ import com.topjohnwu.superuser.Shell;
|
||||
public class RebootReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Shell.Async.su("/system/bin/reboot");
|
||||
Shell.su("/system/bin/reboot").submit();
|
||||
}
|
||||
}
|
@@ -8,13 +8,12 @@ import android.content.pm.ShortcutManager;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -23,12 +22,8 @@ public class ShortcutReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
MagiskManager mm = Utils.getMagiskManager(context);
|
||||
MagiskManager mm = Data.MM();
|
||||
ShortcutManager manager = context.getSystemService(ShortcutManager.class);
|
||||
if (TextUtils.equals(intent.getAction(), Intent.ACTION_LOCALE_CHANGED)) {
|
||||
// It is triggered with locale change, manual load Magisk info
|
||||
mm.loadMagiskInfo();
|
||||
}
|
||||
manager.setDynamicShortcuts(getShortCuts(mm));
|
||||
}
|
||||
}
|
||||
@@ -36,9 +31,9 @@ public class ShortcutReceiver extends BroadcastReceiver {
|
||||
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
|
||||
private ArrayList<ShortcutInfo> getShortCuts(MagiskManager mm) {
|
||||
ArrayList<ShortcutInfo> shortCuts = new ArrayList<>();
|
||||
if (Shell.rootAccess() &&
|
||||
!(Const.USER_ID > 0 &&
|
||||
mm.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
boolean root = Shell.rootAccess();
|
||||
if (root && !(Const.USER_ID > 0 &&
|
||||
Data.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(mm, "superuser")
|
||||
.setShortLabel(mm.getString(R.string.superuser))
|
||||
.setIntent(new Intent(mm, SplashActivity.class)
|
||||
@@ -49,7 +44,7 @@ public class ShortcutReceiver extends BroadcastReceiver {
|
||||
.setRank(0)
|
||||
.build());
|
||||
}
|
||||
if (Shell.rootAccess() && mm.magiskVersionCode >= Const.MAGISK_VER.UNIFIED
|
||||
if (root && Data.magiskVersionCode >= Const.MAGISK_VER.UNIFIED
|
||||
&& mm.prefs.getBoolean(Const.Key.MAGISKHIDE, false)) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(mm, "magiskhide")
|
||||
.setShortLabel(mm.getString(R.string.magiskhide))
|
||||
@@ -61,8 +56,7 @@ public class ShortcutReceiver extends BroadcastReceiver {
|
||||
.setRank(1)
|
||||
.build());
|
||||
}
|
||||
if (!mm.prefs.getBoolean(Const.Key.COREONLY, false) &&
|
||||
Shell.rootAccess() && mm.magiskVersionCode >= 0) {
|
||||
if (!mm.prefs.getBoolean(Const.Key.COREONLY, false) && root && Data.magiskVersionCode >= 0) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(mm, "modules")
|
||||
.setShortLabel(mm.getString(R.string.modules))
|
||||
.setIntent(new Intent(mm, SplashActivity.class)
|
@@ -0,0 +1,31 @@
|
||||
package com.topjohnwu.magisk.services;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.JobIntentService;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.utils.NotificationMgr;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
public class OnBootService extends JobIntentService {
|
||||
|
||||
public static void enqueueWork(Context context) {
|
||||
enqueueWork(context, OnBootService.class, Const.ID.ONBOOT_SERVICE_ID, new Intent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWork(@NonNull Intent intent) {
|
||||
/* Devices with DTBO might want to patch dtbo.img.
|
||||
* However, that is not possible if Magisk is installed by
|
||||
* patching boot image with Magisk Manager and flashed via
|
||||
* fastboot, since at that time we do not have root.
|
||||
* Check for dtbo status every boot time, and prompt user
|
||||
* to reboot if dtbo wasn't patched and patched by Magisk Manager.
|
||||
* */
|
||||
if (Shell.rootAccess() && ShellUtils.fastCmdResult("mm_patch_dtbo"))
|
||||
NotificationMgr.dtboPatched();
|
||||
}
|
||||
}
|
@@ -4,15 +4,14 @@ import android.app.job.JobParameters;
|
||||
import android.app.job.JobService;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class UpdateCheckService extends JobService {
|
||||
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters params) {
|
||||
Utils.getMagiskManager(this).loadMagiskInfo();
|
||||
new CheckUpdates(true)
|
||||
.setCallBack(() -> jobFinished(params, false)).exec();
|
||||
Shell.getShell();
|
||||
CheckUpdates.check(() -> jobFinished(params, false));
|
||||
return true;
|
||||
}
|
||||
|
@@ -19,14 +19,13 @@ import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
@@ -34,7 +33,7 @@ import java.io.IOException;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class RequestActivity extends Activity {
|
||||
public class RequestActivity extends BaseActivity {
|
||||
|
||||
@BindView(R.id.su_popup) LinearLayout suPopup;
|
||||
@BindView(R.id.timeout) Spinner timeout;
|
||||
@@ -49,7 +48,6 @@ public class RequestActivity extends Activity {
|
||||
private String socketPath;
|
||||
private LocalSocket socket;
|
||||
private PackageManager pm;
|
||||
private MagiskManager mm;
|
||||
|
||||
private boolean hasTimeout;
|
||||
private Policy policy;
|
||||
@@ -67,7 +65,6 @@ public class RequestActivity extends Activity {
|
||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
pm = getPackageManager();
|
||||
mm = Utils.getMagiskManager(this);
|
||||
mm.mDB.clearOutdated();
|
||||
|
||||
Intent intent = getIntent();
|
||||
@@ -102,7 +99,7 @@ public class RequestActivity extends Activity {
|
||||
}
|
||||
|
||||
private void showRequest() {
|
||||
switch (mm.suResponseType) {
|
||||
switch (Data.suResponseType) {
|
||||
case Const.Value.SU_AUTO_DENY:
|
||||
handleAction(Policy.DENY, 0);
|
||||
return;
|
||||
@@ -131,7 +128,7 @@ public class RequestActivity extends Activity {
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
timeout.setAdapter(adapter);
|
||||
|
||||
timer = new CountDownTimer(mm.suRequestTimeout * 1000, 1000) {
|
||||
timer = new CountDownTimer(Data.suRequestTimeout * 1000, 1000) {
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
deny_btn.setText(getString(R.string.deny_with_str, "(" + millisUntilFinished / 1000 + ")"));
|
||||
@@ -143,7 +140,7 @@ public class RequestActivity extends Activity {
|
||||
}
|
||||
};
|
||||
|
||||
boolean useFingerprint = mm.prefs.getBoolean(Const.Key.SU_FINGERPRINT, false) && FingerprintHelper.canUseFingerprint();
|
||||
boolean useFingerprint = Data.suFingerprint && FingerprintHelper.canUseFingerprint();
|
||||
|
||||
if (useFingerprint) {
|
||||
try {
|
||||
@@ -168,7 +165,7 @@ public class RequestActivity extends Activity {
|
||||
warning.setText(R.string.auth_fail);
|
||||
}
|
||||
};
|
||||
fingerprintHelper.startAuth();
|
||||
fingerprintHelper.authenticate();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
useFingerprint = false;
|
||||
@@ -240,7 +237,7 @@ public class RequestActivity extends Activity {
|
||||
|
||||
private class SocketManager extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
SocketManager(Activity context) {
|
||||
SocketManager(BaseActivity context) {
|
||||
super(context);
|
||||
}
|
||||
|
@@ -7,11 +7,12 @@ import android.content.pm.PackageManager;
|
||||
import android.os.Process;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -24,7 +25,7 @@ public class SuReceiver extends BroadcastReceiver {
|
||||
String command, action;
|
||||
Policy policy;
|
||||
|
||||
MagiskManager mm = Utils.getMagiskManager(context);
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
if (intent == null) return;
|
||||
|
||||
@@ -32,7 +33,7 @@ public class SuReceiver extends BroadcastReceiver {
|
||||
if (mode < 0) return;
|
||||
|
||||
if (mode == Const.Value.NOTIFY_USER_TO_OWNER) {
|
||||
MagiskManager.toast(R.string.multiuser_hint_owner_request, Toast.LENGTH_LONG);
|
||||
Utils.toast(R.string.multiuser_hint_owner_request, Toast.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ public class SuReceiver extends BroadcastReceiver {
|
||||
policy = mm.mDB.getPolicy(fromUid);
|
||||
if (policy == null) {
|
||||
try {
|
||||
policy = new Policy(fromUid, context.getPackageManager());
|
||||
policy = new Policy(fromUid, mm.getPackageManager());
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
@@ -58,20 +59,19 @@ public class SuReceiver extends BroadcastReceiver {
|
||||
String message;
|
||||
switch (action) {
|
||||
case "allow":
|
||||
message = context.getString(R.string.su_allow_toast, policy.appName);
|
||||
message = mm.getString(R.string.su_allow_toast, policy.appName);
|
||||
log.action = true;
|
||||
break;
|
||||
case "deny":
|
||||
message = context.getString(R.string.su_deny_toast, policy.appName);
|
||||
message = mm.getString(R.string.su_deny_toast, policy.appName);
|
||||
log.action = false;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (policy.notification && mm.suNotificationType == Const.Value.NOTIFICATION_TOAST) {
|
||||
MagiskManager.toast(message, Toast.LENGTH_SHORT);
|
||||
}
|
||||
if (policy.notification && Data.suNotificationType == Const.Value.NOTIFICATION_TOAST)
|
||||
Utils.toast(message, Toast.LENGTH_SHORT);
|
||||
|
||||
if (mode == Const.Value.NOTIFY_NORMAL_LOG && policy.logging) {
|
||||
toUid = intent.getIntExtra("to.uid", -1);
|
@@ -9,6 +9,8 @@ import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||
import android.security.keystore.KeyProperties;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
import java.security.KeyStore;
|
||||
@@ -27,16 +29,15 @@ public abstract class FingerprintHelper {
|
||||
public static boolean canUseFingerprint() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
return false;
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
MagiskManager mm = Data.MM();
|
||||
KeyguardManager km = mm.getSystemService(KeyguardManager.class);
|
||||
FingerprintManager fm = mm.getSystemService(FingerprintManager.class);
|
||||
return km.isKeyguardSecure() && fm != null && fm.isHardwareDetected() && fm.hasEnrolledFingerprints();
|
||||
}
|
||||
|
||||
protected FingerprintHelper() throws Exception {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
manager = mm.getSystemService(FingerprintManager.class);
|
||||
manager = Data.MM().getSystemService(FingerprintManager.class);
|
||||
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
||||
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
||||
@@ -62,30 +63,10 @@ public abstract class FingerprintHelper {
|
||||
|
||||
public abstract void onAuthenticationFailed();
|
||||
|
||||
public void startAuth() {
|
||||
public void authenticate() {
|
||||
cancel = new CancellationSignal();
|
||||
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
|
||||
manager.authenticate(cryptoObject, cancel, 0, new FingerprintManager.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
FingerprintHelper.this.onAuthenticationError(errorCode, errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
FingerprintHelper.this.onAuthenticationHelp(helpCode, helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
FingerprintHelper.this.onAuthenticationSucceeded(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
FingerprintHelper.this.onAuthenticationFailed();
|
||||
}
|
||||
}, null);
|
||||
manager.authenticate(cryptoObject, cancel, 0, new Callback(), null);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
@@ -109,4 +90,26 @@ public abstract class FingerprintHelper {
|
||||
keygen.init(builder.build());
|
||||
return keygen.generateKey();
|
||||
}
|
||||
|
||||
private class Callback extends FingerprintManager.AuthenticationCallback {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
FingerprintHelper.this.onAuthenticationError(errorCode, errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
FingerprintHelper.this.onAuthenticationHelp(helpCode, helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
FingerprintHelper.this.onAuthenticationSucceeded(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
FingerprintHelper.this.onAuthenticationFailed();
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,15 +4,16 @@ import android.support.annotation.Keep;
|
||||
|
||||
public interface ISafetyNetHelper {
|
||||
|
||||
int CAUSE_SERVICE_DISCONNECTED = 0x01;
|
||||
int CAUSE_NETWORK_LOST = 0x02;
|
||||
int RESPONSE_ERR = 0x04;
|
||||
int CONNECTION_FAIL = 0x08;
|
||||
int RESPONSE_ERR = 0x01;
|
||||
int CONNECTION_FAIL = 0x02;
|
||||
|
||||
int BASIC_PASS = 0x10;
|
||||
int CTS_PASS = 0x20;
|
||||
|
||||
@Keep
|
||||
void attest();
|
||||
|
||||
@Keep
|
||||
int getVersion();
|
||||
|
||||
interface Callback {
|
@@ -0,0 +1,76 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.StringRes;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class LocaleManager {
|
||||
public static Locale locale = Locale.getDefault();
|
||||
public final static Locale defaultLocale = Locale.getDefault();
|
||||
public static List<Locale> locales;
|
||||
|
||||
public static void setLocale(MagiskManager mm) {
|
||||
String localeConfig = mm.prefs.getString(Const.Key.LOCALE, "");
|
||||
if (localeConfig.isEmpty()) {
|
||||
locale = defaultLocale;
|
||||
} else {
|
||||
locale = Locale.forLanguageTag(localeConfig);
|
||||
}
|
||||
Locale.setDefault(locale);
|
||||
Resources res = mm.getResources();
|
||||
Configuration config = res.getConfiguration();
|
||||
config.setLocale(locale);
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
}
|
||||
|
||||
public static String getString(Locale locale, @StringRes int id) {
|
||||
Configuration config = new Configuration();
|
||||
config.setLocale(locale);
|
||||
return Data.MM().createConfigurationContext(config).getString(id);
|
||||
}
|
||||
|
||||
public static void loadAvailableLocales() {
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
locales = new ArrayList<>();
|
||||
HashSet<String> set = new HashSet<>();
|
||||
Resources res = Data.MM().getResources();
|
||||
Locale locale;
|
||||
|
||||
@StringRes int compareId = R.string.download_file_error;
|
||||
|
||||
// Add default locale
|
||||
locales.add(Locale.ENGLISH);
|
||||
set.add(getString(Locale.ENGLISH, compareId));
|
||||
|
||||
// Add some special locales
|
||||
locales.add(Locale.TAIWAN);
|
||||
set.add(getString(Locale.TAIWAN, compareId));
|
||||
locale = new Locale("pt", "BR");
|
||||
locales.add(locale);
|
||||
set.add(getString(locale, compareId));
|
||||
|
||||
// Other locales
|
||||
for (String s : res.getAssets().getLocales()) {
|
||||
locale = Locale.forLanguageTag(s);
|
||||
if (set.add(getString(locale, compareId))) {
|
||||
locales.add(locale);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(locales, (a, b) -> a.getDisplayName(a).compareTo(b.getDisplayName(b)));
|
||||
Topic.publish(Topic.LOCALE_FETCH_DONE);
|
||||
});
|
||||
}
|
||||
}
|
@@ -3,8 +3,7 @@ package com.topjohnwu.magisk.utils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
|
||||
import java.util.Locale;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
|
||||
public class Logger {
|
||||
|
@@ -0,0 +1,86 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
import com.topjohnwu.magisk.receivers.RebootReceiver;
|
||||
|
||||
public class NotificationMgr {
|
||||
|
||||
public static void magiskUpdate() {
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
Intent intent = new Intent(mm, SplashActivity.class);
|
||||
intent.putExtra(Const.Key.OPEN_SECTION, "magisk");
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(mm);
|
||||
stackBuilder.addParentStack(SplashActivity.class);
|
||||
stackBuilder.addNextIntent(intent);
|
||||
PendingIntent pendingIntent = stackBuilder.getPendingIntent(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle(mm.getString(R.string.magisk_update_title))
|
||||
.setContentText(mm.getString(R.string.magisk_update_available, Data.remoteMagiskVersionString))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void managerUpdate() {
|
||||
MagiskManager mm = Data.MM();
|
||||
String filename = Utils.fmt("MagiskManager-v%s(%d).apk",
|
||||
Data.remoteManagerVersionString, Data.remoteManagerVersionCode);
|
||||
|
||||
Intent intent = new Intent(mm, ManagerUpdate.class);
|
||||
intent.putExtra(Const.Key.INTENT_SET_LINK, Data.managerLink);
|
||||
intent.putExtra(Const.Key.INTENT_SET_FILENAME, filename);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
|
||||
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle(mm.getString(R.string.manager_update_title))
|
||||
.setContentText(mm.getString(R.string.manager_download_install))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.APK_UPDATE_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void dtboPatched() {
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
Intent intent = new Intent(mm, RebootReceiver.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
|
||||
Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle(mm.getString(R.string.dtbo_patched_title))
|
||||
.setContentText(mm.getString(R.string.dtbo_patched_reboot))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.addAction(R.drawable.ic_refresh, mm.getString(R.string.reboot), pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.DTBO_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
}
|
58
app/src/full/java/com/topjohnwu/magisk/utils/RootUtils.java
Normal file
58
app/src/full/java/com/topjohnwu/magisk/utils/RootUtils.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.superuser.BusyBox;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class RootUtils extends Shell.Initializer {
|
||||
|
||||
static {
|
||||
BusyBox.BB_PATH = new File(Const.BUSYBOX_PATH);
|
||||
}
|
||||
|
||||
public static void uninstallPkg(String pkg) {
|
||||
Shell.su("db_clean " + Const.USER_ID, "pm uninstall " + pkg).exec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInit(Context context, @NonNull Shell shell) {
|
||||
Shell.Job job = shell.newJob();
|
||||
if (shell.isRoot()) {
|
||||
InputStream magiskUtils = context.getResources().openRawResource(R.raw.util_functions);
|
||||
InputStream managerUtils = context.getResources().openRawResource(R.raw.utils);
|
||||
job.add(magiskUtils).add(managerUtils);
|
||||
|
||||
Const.MAGISK_DISABLE_FILE = new SuFile("/cache/.disable_magisk");
|
||||
SuFile file = new SuFile("/sbin/.core/img");
|
||||
if (file.exists()) {
|
||||
Const.MAGISK_PATH = file;
|
||||
} else if ((file = new SuFile("/dev/magisk/img")).exists()) {
|
||||
Const.MAGISK_PATH = file;
|
||||
} else {
|
||||
Const.MAGISK_PATH = new SuFile("/magisk");
|
||||
}
|
||||
Const.MAGISK_HOST_FILE = new SuFile(Const.MAGISK_PATH + "/.core/hosts");
|
||||
|
||||
Data.loadMagiskInfo();
|
||||
} else {
|
||||
InputStream nonroot = context.getResources().openRawResource(R.raw.nonroot_utils);
|
||||
job.add(nonroot);
|
||||
}
|
||||
|
||||
job.add("mount_partitions", "get_flags", "run_migrations").exec();
|
||||
|
||||
Data.keepVerity = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPVERITY"));
|
||||
Data.keepEnc = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPFORCEENCRYPT"));
|
||||
return true;
|
||||
}
|
||||
}
|
108
app/src/full/java/com/topjohnwu/magisk/utils/Topic.java
Normal file
108
app/src/full/java/com/topjohnwu/magisk/utils/Topic.java
Normal file
@@ -0,0 +1,108 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class Topic {
|
||||
|
||||
public static final int MAGISK_HIDE_DONE = 0;
|
||||
public static final int RELOAD_ACTIVITY = 1;
|
||||
public static final int MODULE_LOAD_DONE = 2;
|
||||
public static final int REPO_LOAD_DONE = 3;
|
||||
public static final int UPDATE_CHECK_DONE = 4;
|
||||
public static final int SNET_CHECK_DONE = 5;
|
||||
public static final int LOCALE_FETCH_DONE = 6;
|
||||
|
||||
@IntDef({MAGISK_HIDE_DONE, RELOAD_ACTIVITY, MODULE_LOAD_DONE, REPO_LOAD_DONE,
|
||||
UPDATE_CHECK_DONE, SNET_CHECK_DONE, LOCALE_FETCH_DONE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface TopicID {}
|
||||
|
||||
// We will not dynamically add topics, so use arrays instead of hash tables
|
||||
private static Store[] topicList = new Store[7];
|
||||
|
||||
public static void subscribe(Subscriber sub, @TopicID int... topics) {
|
||||
for (int topic : topics) {
|
||||
if (topicList[topic] == null)
|
||||
topicList[topic] = new Store();
|
||||
topicList[topic].subscribers.add(sub);
|
||||
if (topicList[topic].published) {
|
||||
sub.onPublish(topic, topicList[topic].results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void subscribe(AutoSubscriber sub) {
|
||||
if (sub instanceof Subscriber)
|
||||
subscribe((Subscriber) sub, sub.getSubscribedTopics());
|
||||
}
|
||||
|
||||
public static void unsubscribe(Subscriber sub, @TopicID int... topics) {
|
||||
for (int topic : topics) {
|
||||
if (topicList[topic] == null)
|
||||
continue;
|
||||
topicList[topic].subscribers.remove(sub);
|
||||
}
|
||||
}
|
||||
|
||||
public static void unsubscribe(AutoSubscriber sub) {
|
||||
if (sub instanceof Subscriber)
|
||||
unsubscribe((Subscriber) sub, sub.getSubscribedTopics());
|
||||
}
|
||||
|
||||
public static void publish(@TopicID int topic, Object... results) {
|
||||
publish(true, topic, results);
|
||||
}
|
||||
|
||||
public static void publish(boolean persist, @TopicID int topic, Object... results) {
|
||||
if (topicList[topic] == null)
|
||||
topicList[topic] = new Store();
|
||||
if (persist) {
|
||||
topicList[topic].results = results;
|
||||
topicList[topic].published = true;
|
||||
}
|
||||
for (Subscriber sub : topicList[topic].subscribers) {
|
||||
Data.mainHandler.post(() -> sub.onPublish(topic, results));
|
||||
}
|
||||
}
|
||||
|
||||
public static void reset(@TopicID int... topics) {
|
||||
for (int topic : topics) {
|
||||
if (topicList[topic] == null)
|
||||
continue;
|
||||
topicList[topic].published = false;
|
||||
topicList[topic].results = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPublished(@TopicID int... topics) {
|
||||
for (int topic : topics) {
|
||||
if (topicList[topic] == null)
|
||||
return false;
|
||||
if (!topicList[topic].published)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class Store {
|
||||
boolean published = false;
|
||||
Set<Subscriber> subscribers = new HashSet<>();
|
||||
Object[] results;
|
||||
}
|
||||
|
||||
public interface Subscriber {
|
||||
void onPublish(int topic, Object[] result);
|
||||
}
|
||||
|
||||
public interface AutoSubscriber {
|
||||
@TopicID
|
||||
int[] getSubscribedTopics();
|
||||
}
|
||||
}
|
125
app/src/full/java/com/topjohnwu/magisk/utils/Utils.java
Normal file
125
app/src/full/java/com/topjohnwu/magisk/utils/Utils.java
Normal file
@@ -0,0 +1,125 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.container.ValueSortedMap;
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static int getPrefsInt(SharedPreferences prefs, String key, int def) {
|
||||
return Integer.parseInt(prefs.getString(key, String.valueOf(def)));
|
||||
}
|
||||
|
||||
public static int getPrefsInt(SharedPreferences prefs, String key) {
|
||||
return getPrefsInt(prefs, key, 0);
|
||||
}
|
||||
|
||||
public static String getNameFromUri(Context context, Uri uri) {
|
||||
String name = null;
|
||||
try (Cursor c = context.getContentResolver().query(uri, null, null, null, null)) {
|
||||
if (c != null) {
|
||||
int nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||
if (nameIndex != -1) {
|
||||
c.moveToFirst();
|
||||
name = c.getString(nameIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (name == null) {
|
||||
int idx = uri.getPath().lastIndexOf('/');
|
||||
name = uri.getPath().substring(idx + 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public static int dpInPx(int dp) {
|
||||
float scale = Data.MM().getResources().getDisplayMetrics().density;
|
||||
return (int) (dp * scale + 0.5);
|
||||
}
|
||||
|
||||
public static String fmt(String fmt, Object... args) {
|
||||
return String.format(Locale.US, fmt, args);
|
||||
}
|
||||
|
||||
public static String dos2unix(String s) {
|
||||
String newString = s.replace("\r\n", "\n");
|
||||
if(!newString.endsWith("\n")) {
|
||||
return newString + "\n";
|
||||
} else {
|
||||
return newString;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setupUpdateCheck() {
|
||||
MagiskManager mm = Data.MM();
|
||||
JobScheduler scheduler = (JobScheduler) mm.getSystemService(Context.JOB_SCHEDULER_SERVICE);
|
||||
|
||||
if (mm.prefs.getBoolean(Const.Key.CHECK_UPDATES, true)) {
|
||||
if (scheduler.getAllPendingJobs().isEmpty() ||
|
||||
Const.UPDATE_SERVICE_VER > mm.prefs.getInt(Const.Key.UPDATE_SERVICE_VER, -1)) {
|
||||
ComponentName service = new ComponentName(mm, 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 static void openLink(Context context, Uri link) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, link);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
toast(R.string.open_link_failed_toast, Toast.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
public static void toast(CharSequence msg, int duration) {
|
||||
Data.mainHandler.post(() -> Toast.makeText(Data.MM(), msg, duration).show());
|
||||
}
|
||||
|
||||
public static void toast(int resId, int duration) {
|
||||
Data.mainHandler.post(() -> Toast.makeText(Data.MM(), resId, duration).show());
|
||||
}
|
||||
|
||||
public static void loadModules() {
|
||||
Topic.reset(Topic.MODULE_LOAD_DONE);
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
Map<String, Module> moduleMap = new ValueSortedMap<>();
|
||||
SuFile path = new SuFile(Const.MAGISK_PATH);
|
||||
String[] modules = path.list(
|
||||
(file, name) -> !name.equals("lost+found") && !name.equals(".core"));
|
||||
for (String name : modules) {
|
||||
Module module = new Module(Const.MAGISK_PATH + "/" + name);
|
||||
moduleMap.put(module.getId(), module);
|
||||
}
|
||||
Topic.publish(Topic.MODULE_LOAD_DONE, moduleMap);
|
||||
});
|
||||
}
|
||||
}
|
@@ -59,11 +59,7 @@ public class ZipUtils {
|
||||
public static void signZip(File input, File output) throws Exception {
|
||||
try (JarMap map = new JarMap(input, false);
|
||||
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(output))) {
|
||||
signZip(map, out);
|
||||
SignAPK.sign(map, out);
|
||||
}
|
||||
}
|
||||
|
||||
public static void signZip(JarMap input, OutputStream output) throws Exception {
|
||||
SignAPK.signZip(null, null, input, output);
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user