mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-14 12:47:27 +00:00
Compare commits
1112 Commits
manager-v5
...
manager-v5
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
b885ccbd63 | ||
![]() |
da6f1d0f12 | ||
![]() |
3934821436 | ||
![]() |
c3b473e4bc | ||
![]() |
4c0d435b6b | ||
![]() |
7ed2c077de | ||
![]() |
52a6a7bce8 | ||
![]() |
1283167595 | ||
![]() |
23c2e22910 | ||
![]() |
f44b2dbd45 | ||
![]() |
46ee2c3f4e | ||
![]() |
5d5ec08566 | ||
![]() |
0e717a2de4 | ||
![]() |
cada862214 | ||
![]() |
c3a6179a21 | ||
![]() |
682c6d4e7b | ||
![]() |
d0a253c97e | ||
![]() |
c0e2b3027b | ||
![]() |
e7dc14b07d | ||
![]() |
0da9146e90 | ||
![]() |
ad05a33e02 | ||
![]() |
ef175e3cbe | ||
![]() |
4de51d93ef | ||
![]() |
8224e038a3 | ||
![]() |
03c04c2141 | ||
![]() |
2e091b04e5 | ||
![]() |
60296493fe | ||
![]() |
20c20f8f9b | ||
![]() |
f1d642a4e5 | ||
![]() |
e0e5ea17a4 | ||
![]() |
91a0ba72dc | ||
![]() |
c54c5a974a | ||
![]() |
532b8c54ab | ||
![]() |
5ac87891b5 | ||
![]() |
2d905ce3fb | ||
![]() |
831112abd2 | ||
![]() |
153d0f5505 | ||
![]() |
c78896a335 | ||
![]() |
316ec98e0f | ||
![]() |
cf58545a45 | ||
![]() |
e7a2144def | ||
![]() |
52a2c6958b | ||
![]() |
70243d7a47 | ||
![]() |
b5b8c4b725 | ||
![]() |
6c4d81b1e9 | ||
![]() |
513d732934 | ||
![]() |
c88dc8795b | ||
![]() |
a8030c39b1 | ||
![]() |
7243b9e72f | ||
![]() |
d149af9628 | ||
![]() |
c0ac2d540b | ||
![]() |
528634d755 | ||
![]() |
3283439fd4 | ||
![]() |
e86015badc | ||
![]() |
c8f65fc9a1 | ||
![]() |
c8216f9bc5 | ||
![]() |
e579f314a6 | ||
![]() |
2c4001387e | ||
![]() |
caa39474cb | ||
![]() |
7684602ea8 | ||
![]() |
d1a7372bd2 | ||
![]() |
4601989d4a | ||
![]() |
23f697d62b | ||
![]() |
e837bdc8ad | ||
![]() |
7265450e2e | ||
![]() |
058dbc9f9e | ||
![]() |
daf9b019c6 | ||
![]() |
14eebd582f | ||
![]() |
9a8eeacee8 | ||
![]() |
45b0bf5bc5 | ||
![]() |
88db822c43 | ||
![]() |
fbf3588fdf | ||
![]() |
a82ef6bd35 | ||
![]() |
312466aaf8 | ||
![]() |
c0ca99f4b4 | ||
![]() |
196f15d240 | ||
![]() |
bfddef2671 | ||
![]() |
44395e8ff0 | ||
![]() |
4ff39f8817 | ||
![]() |
1df41003ec | ||
![]() |
1f39ee41ad | ||
![]() |
42d8b1ecb9 | ||
![]() |
a4da7b33e6 | ||
![]() |
e4ee9e9095 | ||
![]() |
835ece5469 | ||
![]() |
77430a282f | ||
![]() |
d93fc67a75 | ||
![]() |
838f3cc01e | ||
![]() |
4d5841332a | ||
![]() |
9b41976252 | ||
![]() |
d08fd0561a | ||
![]() |
30e459252c | ||
![]() |
a6958ac139 | ||
![]() |
d7d76f54cc | ||
![]() |
e6c1dd532d | ||
![]() |
970a2e87b3 | ||
![]() |
cabaae8403 | ||
![]() |
d1f301e059 | ||
![]() |
79eb5b2ed2 | ||
![]() |
f0533fca70 | ||
![]() |
08e98eeb15 | ||
![]() |
f2064a84ed | ||
![]() |
b2f719989d | ||
![]() |
1e812c40ce | ||
![]() |
a949641342 | ||
![]() |
6db27c7758 | ||
![]() |
c231e88a5d | ||
![]() |
3f83919e09 | ||
![]() |
72a5b83544 | ||
![]() |
d2e8ecc646 | ||
![]() |
30eb4074cb | ||
![]() |
79c71509f6 | ||
![]() |
5dab580cfc | ||
![]() |
9929e7d8e8 | ||
![]() |
f6ee252572 | ||
![]() |
90d218ebc8 | ||
![]() |
499a157946 | ||
![]() |
c5a7ab2415 | ||
![]() |
3dd5a6f378 | ||
![]() |
7be26a0677 | ||
![]() |
c183fdd3ca | ||
![]() |
baa439457e | ||
![]() |
4dbcd54b72 | ||
![]() |
11062f2d4f | ||
![]() |
b0a5dbb4c2 | ||
![]() |
0abdfda5a2 | ||
![]() |
a0466085fe | ||
![]() |
a7ceb04cb7 | ||
![]() |
274efb49e7 | ||
![]() |
b3cd83bbca | ||
![]() |
b8bd83ba05 | ||
![]() |
34dcf49fbc | ||
![]() |
f2f7d77847 | ||
![]() |
b2105f2d88 | ||
![]() |
4126f3bdcb | ||
![]() |
74ccfe6088 | ||
![]() |
48085b5573 | ||
![]() |
ef2f8d485b | ||
![]() |
9fb9212b0a | ||
![]() |
7b9ddc9b3b | ||
![]() |
15726a759c | ||
![]() |
2c7474ea87 | ||
![]() |
c726aee643 | ||
![]() |
f31a24b16d | ||
![]() |
b436bce565 | ||
![]() |
886286a819 | ||
![]() |
c3e94e1480 | ||
![]() |
5f1343e5b4 | ||
![]() |
ffb1303d61 | ||
![]() |
a0b0d938f0 | ||
![]() |
158f5ba7d9 | ||
![]() |
b8cf40161c | ||
![]() |
fb96e6a56f | ||
![]() |
6668ba2511 | ||
![]() |
6d93831488 | ||
![]() |
4668ef3020 | ||
![]() |
bcdadc6581 | ||
![]() |
36448191b7 | ||
![]() |
be5be108c3 | ||
![]() |
c9ca42aaa9 | ||
![]() |
630f2b7d19 | ||
![]() |
dde0a4a7c8 | ||
![]() |
c0e2f44092 | ||
![]() |
1412fcbb22 | ||
![]() |
c69dc0f036 | ||
![]() |
9b445d89a1 | ||
![]() |
c3c78428c4 | ||
![]() |
c6d2bf577f | ||
![]() |
b06f69573d | ||
![]() |
8fd03f7434 | ||
![]() |
25703c1750 | ||
![]() |
90e4ac2d23 | ||
![]() |
956bceae75 | ||
![]() |
c663be86de | ||
![]() |
aca78baecf | ||
![]() |
fbcf6b7954 | ||
![]() |
84123222aa | ||
![]() |
e9dbcf693d | ||
![]() |
1cd0a9d48f | ||
![]() |
1b48e44914 | ||
![]() |
0a398f03fd | ||
![]() |
3a9a3ed184 | ||
![]() |
88fae36b8a | ||
![]() |
15ed3e52f2 | ||
![]() |
8990919dab | ||
![]() |
e5638e4b15 | ||
![]() |
404c6fac9a | ||
![]() |
fc9d4034a9 | ||
![]() |
cecc0b932d | ||
![]() |
0faed7159c | ||
![]() |
fb491cfdcf | ||
![]() |
fc706dcb40 | ||
![]() |
a2c1b024f3 | ||
![]() |
267395bfa2 | ||
![]() |
920fc5ae99 | ||
![]() |
92ed0ae51b | ||
![]() |
3d865394d7 | ||
![]() |
76ef1d0d86 | ||
![]() |
c694776162 | ||
![]() |
9484ec0c17 | ||
![]() |
7e2ba41c64 | ||
![]() |
614c552e55 | ||
![]() |
7db3d84ba2 | ||
![]() |
bb2c744ec0 | ||
![]() |
87f6018468 | ||
![]() |
9194c50590 | ||
![]() |
873f14bbe0 | ||
![]() |
31110b1927 | ||
![]() |
6764a98409 | ||
![]() |
7ff45974c6 | ||
![]() |
fd7b5f393a | ||
![]() |
2533a4fc4a | ||
![]() |
2ca528f93f | ||
![]() |
42284c5efb | ||
![]() |
ce2e6b7d35 | ||
![]() |
684c5d225a | ||
![]() |
b75018b03b | ||
![]() |
41499d4b3c | ||
![]() |
383c97c303 | ||
![]() |
74b54ef371 | ||
![]() |
bbf7b4db79 | ||
![]() |
c61f0acab5 | ||
![]() |
398af123b2 | ||
![]() |
315fa9d7d3 | ||
![]() |
fb5e8ef40c | ||
![]() |
7d7686da33 | ||
![]() |
e79d764148 | ||
![]() |
ebbee0dc43 | ||
![]() |
65e455ef0b | ||
![]() |
ed0c16e201 | ||
![]() |
209fdf349a | ||
![]() |
f49f2afacd | ||
![]() |
8c6330a3c4 | ||
![]() |
337b777125 | ||
![]() |
1b756e8d96 | ||
![]() |
ac05e2f2e2 | ||
![]() |
787f7b3035 | ||
![]() |
31bd642b80 | ||
![]() |
f0bac6b154 | ||
![]() |
cc7e74ca11 | ||
![]() |
52d478df1a | ||
![]() |
e8a44646b8 | ||
![]() |
0c782edf21 | ||
![]() |
e3948d295e | ||
![]() |
5f2c742a5c | ||
![]() |
ae97d011ae | ||
![]() |
1b7657a374 | ||
![]() |
5665e04014 | ||
![]() |
b30c77aab9 | ||
![]() |
a5916b9c49 | ||
![]() |
453180e30b | ||
![]() |
8bd432d391 | ||
![]() |
c9d3e20aef | ||
![]() |
bb70385a42 | ||
![]() |
9855877b03 | ||
![]() |
d5408d1f09 | ||
![]() |
f334532aba | ||
![]() |
be77c09f3d | ||
![]() |
7de6a92753 | ||
![]() |
36f76f5a14 | ||
![]() |
b84523d557 | ||
![]() |
21a557a184 | ||
![]() |
2c78c415e9 | ||
![]() |
79ccb30dd2 | ||
![]() |
3c566becf6 | ||
![]() |
76c9188fae | ||
![]() |
e4e5269836 | ||
![]() |
9e737df534 | ||
![]() |
151ca593af | ||
![]() |
4132eacba0 | ||
![]() |
06e6151816 | ||
![]() |
70277d4edd | ||
![]() |
d21d2f1a9c | ||
![]() |
74a7be996f | ||
![]() |
0b3192c4d5 | ||
![]() |
968e6237bd | ||
![]() |
d780b5a0e4 | ||
![]() |
3e48427eaf | ||
![]() |
31360c34ed | ||
![]() |
e9624e2304 | ||
![]() |
3f38579529 | ||
![]() |
4d5a9f6e15 | ||
![]() |
41f47acd76 | ||
![]() |
821dcaa7c7 | ||
![]() |
7135d26419 | ||
![]() |
f7fd354dce | ||
![]() |
0c69a65bc4 | ||
![]() |
2f2ca5eab4 | ||
![]() |
9c6e64f47d | ||
![]() |
0afa601551 | ||
![]() |
df9c40c035 | ||
![]() |
25b67017e4 | ||
![]() |
bc9c3346f3 | ||
![]() |
1db7e19fe8 | ||
![]() |
102c03ce2b | ||
![]() |
ec19eb4455 | ||
![]() |
6d9924d50e | ||
![]() |
16c4d74274 | ||
![]() |
e4af5fd36a | ||
![]() |
702775493a | ||
![]() |
b2ae826066 | ||
![]() |
cc3e9990fa | ||
![]() |
271cbddd5e | ||
![]() |
26dfbb3028 | ||
![]() |
f16cd987e4 | ||
![]() |
c1423ca9ad | ||
![]() |
74379150a1 | ||
![]() |
a94fa81195 | ||
![]() |
6119c24720 | ||
![]() |
c840a30c30 | ||
![]() |
ae5277a898 | ||
![]() |
bffa837825 | ||
![]() |
b9e7d0faea | ||
![]() |
860b08d9ed | ||
![]() |
691dc1d49e | ||
![]() |
7da205f4c8 | ||
![]() |
9d6886d367 | ||
![]() |
9589b68f5a | ||
![]() |
28d88af1af | ||
![]() |
8b5acd1849 | ||
![]() |
33dc63a7fd | ||
![]() |
754fafcfe9 | ||
![]() |
bd7766b17e | ||
![]() |
70b7d73453 | ||
![]() |
5ad4702a5b | ||
![]() |
40b6fe03c2 | ||
![]() |
49ecba2476 | ||
![]() |
ebd509d92d | ||
![]() |
7193374a7e | ||
![]() |
6728445542 | ||
![]() |
10ed299c78 | ||
![]() |
d0a86385b7 | ||
![]() |
32b124913e | ||
![]() |
599ae95251 | ||
![]() |
d1be34c34a | ||
![]() |
bc2cac90fe | ||
![]() |
50a49e2c8c | ||
![]() |
c60adb113e | ||
![]() |
aee015e8f6 | ||
![]() |
bf6af29205 | ||
![]() |
329905d472 | ||
![]() |
00d450d262 | ||
![]() |
2365d1bd20 | ||
![]() |
5b385c18e5 | ||
![]() |
98c0434ec0 | ||
![]() |
f318d0a3bc | ||
![]() |
27f5b410c0 | ||
![]() |
3f55be9676 | ||
![]() |
28350e3ad9 | ||
![]() |
f48e6c93b8 | ||
![]() |
7cfc24d68f | ||
![]() |
a58d3ea04d | ||
![]() |
dfee9954e0 | ||
![]() |
eed86c760f | ||
![]() |
c471bb6f67 | ||
![]() |
518c2b0f95 | ||
![]() |
328fc44194 | ||
![]() |
b6f735a8f6 | ||
![]() |
b05d2d3a2d | ||
![]() |
deae08fc4b | ||
![]() |
19af5f9e0b | ||
![]() |
c61135ee7b | ||
![]() |
f37f330670 | ||
![]() |
40082d4571 | ||
![]() |
97cf15007f | ||
![]() |
00d655f346 | ||
![]() |
821726e7c0 | ||
![]() |
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/
|
||||
*.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>.
|
74
README.MD
Normal file
74
README.MD
Normal file
@@ -0,0 +1,74 @@
|
||||
# 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 tested on macOS, Ubuntu, and Windows 10 using the latest stable NDK and NDK r10e. Officially released binaries were built with NDK r10e.
|
||||
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 `--release` flag), you need a Java Keystore file `release-key.jks` to sign Magisk Manager's APK. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually).
|
||||
5. The SafetyNet extension pack requires the full Magisk Manager as a `compileOnly` dependency. Build the **release** APK, convert it back to Java `.class` files (I use [dex2jar](https://github.com/pxb1988/dex2jar)), and place the converted JAR under `snet/libs` before compiling.
|
||||
|
||||
## 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/>.
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
**MagiskManager** (`app`)
|
||||
|
||||
* Copyright 2016-2018, John Wu (@topjohnwu)
|
||||
* All contributors and translators on Github
|
||||
|
||||
**MagiskSU** (`native/jni/su`)
|
||||
|
||||
* Copyright 2016-2018, John Wu (@topjohnwu)
|
||||
* Copyright 2015, Pierre-Hugues Husson (phh@phh.me)
|
||||
* Copyright 2013, Koushik Dutta (@koush)
|
||||
* Copyright 2010, Adam Shanks (@ChainsDD)
|
||||
* Copyright 2008, Zinx Verituse (@zinxv)
|
||||
|
||||
**MagiskPolicy** (`native/jni/magiskpolicy`)
|
||||
|
||||
* Copyright 2016-2018, John Wu (@topjohnwu)
|
||||
* Copyright 2015, Pierre-Hugues Husson (phh@phh.me)
|
||||
* Copyright 2015, Joshua Brindle (@joshua_brindle)
|
||||
|
||||
**MagiskHide** (`native/jni/magiskhide`)
|
||||
|
||||
* Copyright 2016-2018, John Wu (@topjohnwu)
|
||||
* Copyright 2016, Pierre-Hugues Husson (phh@phh.me)
|
||||
|
||||
**resetprop** (`native/jni/resetprop`)
|
||||
|
||||
* Copyright 2016-2018 John Wu (@topjohnwu)
|
||||
* Copyright 2016 nkk71 (nkk71x@gmail.com)
|
||||
|
||||
**External Dependencies** (`native/jni/external`)
|
||||
|
||||
* Makefile for busybox, generated by [ndk-busybox-kitchen](https://github.com/topjohnwu/ndk-busybox-kitchen)
|
||||
* Each dependencies has its own license/copyright information in each subdirectory.
|
||||
All of them are either GPL or GPL compatible.
|
||||
|
||||
**Others Not Mentioned**
|
||||
|
||||
* Copyright 2016-2018, John Wu (@topjohnwu)
|
@@ -1,2 +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).
|
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 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.
|
65
app/build.gradle
Normal file
65
app/build.gradle
Normal file
@@ -0,0 +1,65 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.topjohnwu.magisk"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion rootProject.ext.compileSdkVersion
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
argument('butterknife.debuggable', 'false')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "mode"
|
||||
|
||||
productFlavors {
|
||||
full {
|
||||
versionCode 129
|
||||
versionName "5.8.3"
|
||||
}
|
||||
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:1.3.0'
|
||||
fullImplementation 'com.atlassian.commonmark:commonmark:0.11.0'
|
||||
fullImplementation 'org.kamranzafar:jtar:2.3'
|
||||
fullImplementation 'com.jakewharton:butterknife:8.8.1'
|
||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
|
||||
}
|
4
proguard-rules.pro → app/proguard-rules.pro
vendored
4
proguard-rules.pro → app/proguard-rules.pro
vendored
@@ -20,7 +20,9 @@
|
||||
-keepnames class ** { *; }
|
||||
|
||||
# BouncyCastle
|
||||
-keep class org.bouncycastle.jcajce.provider.** { *; }
|
||||
-keep class org.bouncycastle.jcajce.provider.asymmetric.rsa.**SHA1** { *; }
|
||||
-keep class org.bouncycastle.jcajce.provider.asymmetric.RSA** { *; }
|
||||
-keep class org.bouncycastle.jcajce.provider.digest.SHA1** { *; }
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
# Gson
|
@@ -1,26 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.topjohnwu.magisk">
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
|
||||
<application
|
||||
android:name=".MagiskManager"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:directBootAware="true"
|
||||
tools:ignore="UnusedAttribute">
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
@@ -32,22 +21,23 @@
|
||||
android:theme="@style/SplashTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".AboutActivity"
|
||||
android:theme="@style/AppTheme.Transparent" />
|
||||
android:theme="@style/AppTheme.StatusBar" />
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:theme="@style/AppTheme.Transparent" />
|
||||
android:theme="@style/AppTheme.StatusBar" />
|
||||
<activity
|
||||
android:name=".FlashActivity"
|
||||
android:screenOrientation="nosensor"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:theme="@style/AppTheme.Transparent" />
|
||||
|
||||
android:screenOrientation="nosensor"
|
||||
android:theme="@style/AppTheme.StatusBar" />
|
||||
<activity
|
||||
android:name=".NoUIActivity"
|
||||
android:theme="@style/AppTheme.Translucent" />
|
||||
<activity
|
||||
android:name=".superuser.RequestActivity"
|
||||
android:excludeFromRecents="true"
|
||||
@@ -65,12 +55,16 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
|
||||
|
||||
<data android:scheme="package" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name=".receivers.ManagerUpdate" />
|
||||
<receiver android:name=".receivers.RebootReceiver" />
|
||||
<receiver android:name=".receivers.ShortcutReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name=".services.OnBootIntentService" />
|
||||
<service
|
||||
@@ -78,16 +72,6 @@
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="com.topjohnwu.magisk.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<!-- Hardcode GMS version -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
@@ -14,8 +14,6 @@ import com.topjohnwu.magisk.components.AboutCardRow;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
@@ -33,7 +31,7 @@ public class AboutActivity extends Activity {
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_Transparent_Dark;
|
||||
return R.style.AppTheme_StatusBar_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,12 +54,8 @@ public class AboutActivity extends Activity {
|
||||
|
||||
appChangelog.removeSummary();
|
||||
appChangelog.setOnClickListener(v -> {
|
||||
try {
|
||||
InputStream is = getAssets().open("changelog.md");
|
||||
new MarkDownWindow(this, getString(R.string.app_changelog), is).exec();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
new MarkDownWindow(this, getString(R.string.app_changelog),
|
||||
getResources().openRawResource(R.raw.changelog)).exec();
|
||||
});
|
||||
|
||||
String translators = getString(R.string.translators);
|
@@ -3,6 +3,7 @@ package com.topjohnwu.magisk;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.TextUtils;
|
||||
@@ -16,9 +17,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.container.CallbackList;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.superuser.CallbackList;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
@@ -49,7 +51,7 @@ public class FlashActivity extends Activity {
|
||||
|
||||
@OnClick(R.id.reboot)
|
||||
void reboot() {
|
||||
Shell.su_raw("/system/bin/reboot");
|
||||
Shell.Async.su("/system/bin/reboot");
|
||||
}
|
||||
|
||||
@OnClick(R.id.save_logs)
|
||||
@@ -77,7 +79,7 @@ public class FlashActivity extends Activity {
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_Transparent_Dark;
|
||||
return R.style.AppTheme_StatusBar_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,10 +98,10 @@ public class FlashActivity extends Activity {
|
||||
reboot.setVisibility(View.GONE);
|
||||
|
||||
logs = new ArrayList<>();
|
||||
List<String> console = new CallbackList<String>() {
|
||||
CallbackList<String> console = new CallbackList<String>(new ArrayList<>()) {
|
||||
@Override
|
||||
public synchronized void onAddElement(String e) {
|
||||
logs.add(e);
|
||||
public void onAddElement(String s) {
|
||||
logs.add(s);
|
||||
flashLogs.setText(TextUtils.join("\n", this));
|
||||
sv.postDelayed(() -> sv.fullScroll(ScrollView.FOCUS_DOWN), 10);
|
||||
}
|
||||
@@ -113,13 +115,18 @@ public class FlashActivity extends Activity {
|
||||
case Const.Value.FLASH_ZIP:
|
||||
new FlashZip(this, uri, console, logs).exec();
|
||||
break;
|
||||
case Const.Value.PATCH_BOOT:
|
||||
new InstallMagisk(this, console, logs, uri, (Uri) intent.getParcelableExtra(Const.Key.FLASH_SET_BOOT))
|
||||
.exec();
|
||||
case Const.Value.UNINSTALL:
|
||||
new UninstallMagisk(this, uri, console, logs).exec();
|
||||
break;
|
||||
case Const.Value.FLASH_MAGISK:
|
||||
new InstallMagisk(this, console, logs, uri, intent.getStringExtra(Const.Key.FLASH_SET_BOOT))
|
||||
.exec();
|
||||
new InstallMagisk(this, console, logs, uri, InstallMagisk.DIRECT_MODE).exec();
|
||||
break;
|
||||
case Const.Value.FLASH_SECOND_SLOT:
|
||||
new InstallMagisk(this, console, logs, uri, InstallMagisk.SECOND_SLOT_MODE).exec();
|
||||
break;
|
||||
case Const.Value.PATCH_BOOT:
|
||||
new InstallMagisk(this, console, logs, uri,
|
||||
intent.getParcelableExtra(Const.Key.FLASH_SET_BOOT)).exec();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -128,4 +135,21 @@ public class FlashActivity extends Activity {
|
||||
public void onBackPressed() {
|
||||
// Prevent user accidentally press back button
|
||||
}
|
||||
|
||||
private static class UninstallMagisk extends FlashZip {
|
||||
|
||||
private UninstallMagisk(Activity context, Uri uri, List<String> console, List<String> logs) {
|
||||
super(context, uri, console, logs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
if (result == 1) {
|
||||
new Handler().postDelayed(() ->
|
||||
RootUtils.uninstallPkg(getActivity().getPackageName()), 3000);
|
||||
} else {
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,6 +7,7 @@ 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;
|
||||
@@ -22,10 +23,13 @@ import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.ISafetyNetHelper;
|
||||
import com.topjohnwu.magisk.utils.ShowUI;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import butterknife.BindColor;
|
||||
import butterknife.BindView;
|
||||
@@ -36,14 +40,6 @@ import butterknife.Unbinder;
|
||||
public class MagiskFragment extends Fragment
|
||||
implements Topic.Subscriber, SwipeRefreshLayout.OnRefreshListener, ExpandableView {
|
||||
|
||||
private static final int CAUSE_SERVICE_DISCONNECTED = 0x01;
|
||||
private static final int CAUSE_NETWORK_LOST = 0x02;
|
||||
private static final int RESPONSE_ERR = 0x04;
|
||||
private static final int CONNECTION_FAIL = 0x08;
|
||||
|
||||
private static final int BASIC_PASS = 0x10;
|
||||
private static final int CTS_PASS = 0x20;
|
||||
|
||||
private Container expandableContainer = new Container();
|
||||
|
||||
private MagiskManager mm;
|
||||
@@ -91,7 +87,14 @@ public class MagiskFragment extends Fragment
|
||||
new CheckSafetyNet(getActivity()).exec();
|
||||
collapse();
|
||||
};
|
||||
if (!CheckSafetyNet.dexPath.exists()) {
|
||||
if (!TextUtils.equals(mm.getPackageName(), Const.ORIG_PKG_NAME)) {
|
||||
new AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.cannot_check_sn_title)
|
||||
.setMessage(R.string.cannot_check_sn_notice)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
} else if (!CheckSafetyNet.dexPath.exists()) {
|
||||
// Show dialog
|
||||
new AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.proprietary_title)
|
||||
@@ -159,8 +162,8 @@ public class MagiskFragment extends Fragment
|
||||
|
||||
safetyNetStatusText.setText(R.string.safetyNet_check_text);
|
||||
|
||||
mm.safetyNetDone.hasPublished = false;
|
||||
mm.updateCheckDone.hasPublished = false;
|
||||
mm.safetyNetDone.reset();
|
||||
mm.updateCheckDone.reset();
|
||||
mm.remoteMagiskVersionString = null;
|
||||
mm.remoteMagiskVersionCode = -1;
|
||||
collapse();
|
||||
@@ -176,11 +179,11 @@ public class MagiskFragment extends Fragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
public void onTopicPublished(Topic topic) {
|
||||
if (topic == mm.updateCheckDone) {
|
||||
updateCheckUI();
|
||||
} else if (topic == mm.safetyNetDone) {
|
||||
updateSafetyNetUI((int) result);
|
||||
updateSafetyNetUI((int) topic.getResults()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +208,7 @@ public class MagiskFragment extends Fragment
|
||||
|
||||
boolean hasNetwork = Utils.checkNetworkStatus();
|
||||
boolean hasRoot = Shell.rootAccess();
|
||||
boolean isUpToDate = mm.magiskVersionCode > 1300;
|
||||
boolean isUpToDate = mm.magiskVersionCode > Const.MAGISK_VER.UNIFIED;
|
||||
|
||||
magiskUpdate.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
safetyNetCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
@@ -250,17 +253,22 @@ public class MagiskFragment extends Fragment
|
||||
}
|
||||
}
|
||||
|
||||
if (!shownDialog && (mm.remoteMagiskVersionCode > mm.magiskVersionCode
|
||||
|| mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE)) {
|
||||
install();
|
||||
}
|
||||
|
||||
magiskUpdateIcon.setImageResource(image);
|
||||
magiskUpdateIcon.setColorFilter(color);
|
||||
magiskUpdateIcon.setVisibility(View.VISIBLE);
|
||||
|
||||
magiskUpdateProgress.setVisibility(View.GONE);
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
|
||||
if (!shownDialog) {
|
||||
if (mm.remoteMagiskVersionCode > mm.magiskVersionCode
|
||||
|| mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
install();
|
||||
} else if (mm.remoteMagiskVersionCode >= Const.MAGISK_VER.FIX_ENV &&
|
||||
!ShellUtils.fastCmdResult("env_check")) {
|
||||
ShowUI.envFixDialog(getActivity());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSafetyNetUI(int response) {
|
||||
@@ -270,12 +278,12 @@ public class MagiskFragment extends Fragment
|
||||
safetyNetStatusText.setText(R.string.safetyNet_check_success);
|
||||
|
||||
boolean b;
|
||||
b = (response & CTS_PASS) != 0;
|
||||
b = (response & ISafetyNetHelper.CTS_PASS) != 0;
|
||||
ctsStatusText.setText("ctsProfile: " + b);
|
||||
ctsStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel);
|
||||
ctsStatusIcon.setColorFilter(b ? colorOK : colorBad);
|
||||
|
||||
b = (response & BASIC_PASS) != 0;
|
||||
b = (response & ISafetyNetHelper.BASIC_PASS) != 0;
|
||||
basicStatusText.setText("basicIntegrity: " + b);
|
||||
basicStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel);
|
||||
basicStatusIcon.setColorFilter(b ? colorOK : colorBad);
|
||||
@@ -284,16 +292,16 @@ public class MagiskFragment extends Fragment
|
||||
} else {
|
||||
@StringRes int resid;
|
||||
switch (response) {
|
||||
case CAUSE_SERVICE_DISCONNECTED:
|
||||
case ISafetyNetHelper.CAUSE_SERVICE_DISCONNECTED:
|
||||
resid = R.string.safetyNet_network_loss;
|
||||
break;
|
||||
case CAUSE_NETWORK_LOST:
|
||||
case ISafetyNetHelper.CAUSE_NETWORK_LOST:
|
||||
resid = R.string.safetyNet_service_disconnected;
|
||||
break;
|
||||
case RESPONSE_ERR:
|
||||
case ISafetyNetHelper.RESPONSE_ERR:
|
||||
resid = R.string.safetyNet_res_invalid;
|
||||
break;
|
||||
case CONNECTION_FAIL:
|
||||
case ISafetyNetHelper.CONNECTION_FAIL:
|
||||
default:
|
||||
resid = R.string.safetyNet_api_error;
|
||||
break;
|
@@ -28,7 +28,6 @@ public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
private ApplicationAdapter appAdapter;
|
||||
|
||||
private SearchView.OnQueryTextListener searchListener;
|
||||
private String lastFilter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
@@ -41,25 +40,22 @@ public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
lastFilter = "";
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> appAdapter.refresh());
|
||||
|
||||
appAdapter = new ApplicationAdapter(getActivity());
|
||||
appAdapter = new ApplicationAdapter();
|
||||
recyclerView.setAdapter(appAdapter);
|
||||
|
||||
searchListener = new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
lastFilter = query;
|
||||
appAdapter.filter(query);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
lastFilter = newText;
|
||||
appAdapter.filter(newText);
|
||||
return false;
|
||||
}
|
||||
@@ -84,9 +80,9 @@ public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
public void onTopicPublished(Topic topic) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
appAdapter.filter(lastFilter);
|
||||
appAdapter.filter(null);
|
||||
}
|
||||
|
||||
@Override
|
145
app/src/full/java/com/topjohnwu/magisk/MagiskLogFragment.java
Normal file
145
app/src/full/java/com/topjohnwu/magisk/MagiskLogFragment.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.Manifest;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class MagiskLogFragment extends Fragment {
|
||||
|
||||
private Unbinder unbinder;
|
||||
|
||||
@BindView(R.id.txtLog) TextView txtLog;
|
||||
@BindView(R.id.svLog) ScrollView svLog;
|
||||
@BindView(R.id.hsvLog) HorizontalScrollView hsvLog;
|
||||
@BindView(R.id.progressBar) ProgressBar progressBar;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_magisk_log, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
setHasOptionsMenu(true);
|
||||
txtLog.setTextIsSelectable(true);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
getActivity().setTitle(R.string.log);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
readLogs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unbinder.unbind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_log, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_refresh:
|
||||
readLogs();
|
||||
return true;
|
||||
case R.id.menu_save:
|
||||
runWithPermission(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, this::saveLogs);
|
||||
return true;
|
||||
case R.id.menu_clear:
|
||||
clearLogs();
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void readLogs() {
|
||||
Shell.Async.su(new Shell.Async.Callback() {
|
||||
@Override
|
||||
public void onTaskResult(@Nullable List<String> out, @Nullable List<String> err) {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (ShellUtils.isValidOutput(out)) {
|
||||
txtLog.setText(TextUtils.join("\n", out));
|
||||
} else {
|
||||
txtLog.setText(R.string.log_is_empty);
|
||||
}
|
||||
svLog.postDelayed(() -> svLog.fullScroll(ScrollView.FOCUS_DOWN), 100);
|
||||
hsvLog.postDelayed(() -> hsvLog.fullScroll(ScrollView.FOCUS_LEFT), 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskError(@NonNull Throwable throwable) {
|
||||
txtLog.setText(R.string.log_is_empty);
|
||||
}
|
||||
}, "cat " + Const.MAGISK_LOG + " | tail -n 5000");
|
||||
}
|
||||
|
||||
public void saveLogs() {
|
||||
Calendar now = Calendar.getInstance();
|
||||
String filename = Utils.fmt("magisk_log_%04d%02d%02d_%02d%02d%02d.log",
|
||||
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
|
||||
|
||||
File targetFile = new File(Const.EXTERNAL_PATH + "/logs", filename);
|
||||
targetFile.getParentFile().mkdirs();
|
||||
try {
|
||||
targetFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
Shell.Async.su(new Shell.Async.Callback() {
|
||||
@Override
|
||||
public void onTaskResult(@Nullable List<String> out, @Nullable List<String> err) {
|
||||
SnackbarMaker.make(txtLog, targetFile.getPath(), Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskError(@NonNull Throwable throwable) {}
|
||||
}, "cat " + Const.MAGISK_LOG + " > " + targetFile);
|
||||
}
|
||||
|
||||
public void clearLogs() {
|
||||
Shell.Async.su("echo -n > " + Const.MAGISK_LOG);
|
||||
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
292
app/src/full/java/com/topjohnwu/magisk/MagiskManager.java
Normal file
292
app/src/full/java/com/topjohnwu/magisk/MagiskManager.java
Normal file
@@ -0,0 +1,292 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.content.ComponentName;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.topjohnwu.magisk.components.Application;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.database.MagiskDatabaseHelper;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.ShellInitializer;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class MagiskManager extends Application implements Shell.Container {
|
||||
|
||||
// Topics
|
||||
public final Topic magiskHideDone = new Topic();
|
||||
public final Topic reloadActivity = new Topic();
|
||||
public final Topic moduleLoadDone = new Topic();
|
||||
public final Topic repoLoadDone = new Topic();
|
||||
public final Topic updateCheckDone = new Topic();
|
||||
public final Topic safetyNetDone = new Topic();
|
||||
public final Topic localeDone = new Topic();
|
||||
|
||||
// Info
|
||||
public boolean hasInit = false;
|
||||
public String magiskVersionString;
|
||||
public int magiskVersionCode = -1;
|
||||
public String remoteMagiskVersionString;
|
||||
public int remoteMagiskVersionCode = -1;
|
||||
public String remoteManagerVersionString;
|
||||
public int remoteManagerVersionCode = -1;
|
||||
|
||||
public String magiskLink;
|
||||
public String magiskNoteLink;
|
||||
public String managerLink;
|
||||
public String managerNoteLink;
|
||||
public String uninstallerLink;
|
||||
|
||||
public boolean keepVerity = false;
|
||||
public boolean keepEnc = false;
|
||||
|
||||
// Data
|
||||
public Map<String, Module> moduleMap;
|
||||
public List<Locale> locales;
|
||||
|
||||
public boolean magiskHide;
|
||||
public boolean isDarkTheme;
|
||||
public int suRequestTimeout;
|
||||
public int suLogTimeout = 14;
|
||||
public int suAccessState;
|
||||
public int multiuserMode;
|
||||
public int suResponseType;
|
||||
public int suNotificationType;
|
||||
public int suNamespaceMode;
|
||||
public String localeConfig;
|
||||
public int updateChannel;
|
||||
public String bootFormat;
|
||||
public int repoOrder;
|
||||
|
||||
// Global resources
|
||||
public SharedPreferences prefs;
|
||||
public MagiskDatabaseHelper mDB;
|
||||
public RepoDatabaseHelper repoDB;
|
||||
|
||||
private volatile Shell mShell;
|
||||
|
||||
public MagiskManager() {
|
||||
weakSelf = new WeakReference<>(this);
|
||||
Shell.setContainer(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Shell getShell() {
|
||||
return mShell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShell(@Nullable Shell shell) {
|
||||
mShell = shell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Shell.setFlags(Shell.FLAG_MOUNT_MASTER);
|
||||
Shell.verboseLogging(BuildConfig.DEBUG);
|
||||
Shell.setInitializer(ShellInitializer.class);
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
mDB = MagiskDatabaseHelper.getInstance(this);
|
||||
|
||||
String pkg = mDB.getStrings(Const.Key.SU_MANAGER, null);
|
||||
if (pkg != null && getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
mDB.setStrings(Const.Key.SU_MANAGER, null);
|
||||
RootUtils.uninstallPkg(pkg);
|
||||
}
|
||||
if (TextUtils.equals(pkg, getPackageName())) {
|
||||
try {
|
||||
// We are the manager, remove com.topjohnwu.magisk as it could be malware
|
||||
getPackageManager().getApplicationInfo(Const.ORIG_PKG_NAME, 0);
|
||||
RootUtils.uninstallPkg(Const.ORIG_PKG_NAME);
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
}
|
||||
|
||||
setLocale();
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
public static MagiskManager get() {
|
||||
return (MagiskManager) weakSelf.get();
|
||||
}
|
||||
|
||||
public void setLocale() {
|
||||
localeConfig = prefs.getString(Const.Key.LOCALE, "");
|
||||
if (localeConfig.isEmpty()) {
|
||||
locale = defaultLocale;
|
||||
} else {
|
||||
locale = Locale.forLanguageTag(localeConfig);
|
||||
}
|
||||
Resources res = getBaseContext().getResources();
|
||||
Configuration config = new Configuration(res.getConfiguration());
|
||||
config.setLocale(locale);
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
}
|
||||
|
||||
public void loadConfig() {
|
||||
// su
|
||||
suRequestTimeout = Utils.getPrefsInt(prefs, Const.Key.SU_REQUEST_TIMEOUT, Const.Value.timeoutList[2]);
|
||||
suResponseType = Utils.getPrefsInt(prefs, Const.Key.SU_AUTO_RESPONSE, Const.Value.SU_PROMPT);
|
||||
suNotificationType = Utils.getPrefsInt(prefs, Const.Key.SU_NOTIFICATION, Const.Value.NOTIFICATION_TOAST);
|
||||
suAccessState = mDB.getSettings(Const.Key.ROOT_ACCESS, Const.Value.ROOT_ACCESS_APPS_AND_ADB);
|
||||
multiuserMode = mDB.getSettings(Const.Key.SU_MULTIUSER_MODE, Const.Value.MULTIUSER_MODE_OWNER_ONLY);
|
||||
suNamespaceMode = mDB.getSettings(Const.Key.SU_MNT_NS, Const.Value.NAMESPACE_MODE_REQUESTER);
|
||||
|
||||
// config
|
||||
isDarkTheme = prefs.getBoolean(Const.Key.DARK_THEME, false);
|
||||
updateChannel = Utils.getPrefsInt(prefs, Const.Key.UPDATE_CHANNEL, Const.Value.STABLE_CHANNEL);
|
||||
bootFormat = prefs.getString(Const.Key.BOOT_FORMAT, ".img");
|
||||
repoOrder = prefs.getInt(Const.Key.REPO_ORDER, Const.Value.ORDER_NAME);
|
||||
}
|
||||
|
||||
public void writeConfig() {
|
||||
prefs.edit()
|
||||
.putBoolean(Const.Key.DARK_THEME, isDarkTheme)
|
||||
.putBoolean(Const.Key.MAGISKHIDE, magiskHide)
|
||||
.putBoolean(Const.Key.HOSTS, Const.MAGISK_HOST_FILE.exists())
|
||||
.putBoolean(Const.Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
|
||||
.putString(Const.Key.SU_REQUEST_TIMEOUT, String.valueOf(suRequestTimeout))
|
||||
.putString(Const.Key.SU_AUTO_RESPONSE, String.valueOf(suResponseType))
|
||||
.putString(Const.Key.SU_NOTIFICATION, String.valueOf(suNotificationType))
|
||||
.putString(Const.Key.ROOT_ACCESS, String.valueOf(suAccessState))
|
||||
.putString(Const.Key.SU_MULTIUSER_MODE, String.valueOf(multiuserMode))
|
||||
.putString(Const.Key.SU_MNT_NS, String.valueOf(suNamespaceMode))
|
||||
.putString(Const.Key.UPDATE_CHANNEL, String.valueOf(updateChannel))
|
||||
.putString(Const.Key.LOCALE, localeConfig)
|
||||
.putString(Const.Key.BOOT_FORMAT, bootFormat)
|
||||
.putInt(Const.Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
|
||||
.putInt(Const.Key.REPO_ORDER, repoOrder)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public void loadMagiskInfo() {
|
||||
try {
|
||||
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
|
||||
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
|
||||
String s = ShellUtils.fastCmd((magiskVersionCode >= Const.MAGISK_VER.RESETPROP_PERSIST ?
|
||||
"resetprop -p " : "getprop ") + Const.MAGISKHIDE_PROP);
|
||||
magiskHide = s == null || Integer.parseInt(s) != 0;
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
public void getDefaultInstallFlags() {
|
||||
keepVerity = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPVERITY"));
|
||||
keepEnc = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPFORCEENCRYPT"));
|
||||
}
|
||||
|
||||
public void setupUpdateCheck() {
|
||||
JobScheduler scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
|
||||
|
||||
if (prefs.getBoolean(Const.Key.CHECK_UPDATES, true)) {
|
||||
if (scheduler.getAllPendingJobs().isEmpty() ||
|
||||
Const.UPDATE_SERVICE_VER > prefs.getInt(Const.Key.UPDATE_SERVICE_VER, -1)) {
|
||||
ComponentName service = new ComponentName(this, UpdateCheckService.class);
|
||||
JobInfo info = new JobInfo.Builder(Const.ID.UPDATE_SERVICE_ID, service)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
.setPersisted(true)
|
||||
.setPeriodic(8 * 60 * 60 * 1000)
|
||||
.build();
|
||||
scheduler.schedule(info);
|
||||
}
|
||||
} else {
|
||||
scheduler.cancel(Const.UPDATE_SERVICE_VER);
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpPrefs() {
|
||||
// Flush prefs to disk
|
||||
prefs.edit().commit();
|
||||
File xml = new File(getFilesDir().getParent() + "/shared_prefs",
|
||||
getPackageName() + "_preferences.xml");
|
||||
Shell.Sync.su(Utils.fmt("for usr in /data/user/*; do cat %s > ${usr}/%s; done", xml, Const.MANAGER_CONFIGS));
|
||||
}
|
||||
|
||||
public void loadPrefs() {
|
||||
SuFile config = new SuFile(Utils.fmt("/data/user/%d/%s", Const.USER_ID, Const.MANAGER_CONFIGS));
|
||||
if (config.exists()) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
try {
|
||||
SuFileInputStream is = new SuFileInputStream(config);
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
|
||||
parser.setInput(is, "UTF-8");
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.START_TAG, null, "map");
|
||||
while (parser.next() != XmlPullParser.END_TAG) {
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG)
|
||||
continue;
|
||||
String key = parser.getAttributeValue(null, "name");
|
||||
String value = parser.getAttributeValue(null, "value");
|
||||
switch (parser.getName()) {
|
||||
case "string":
|
||||
parser.require(XmlPullParser.START_TAG, null, "string");
|
||||
editor.putString(key, parser.nextText());
|
||||
parser.require(XmlPullParser.END_TAG, null, "string");
|
||||
break;
|
||||
case "boolean":
|
||||
parser.require(XmlPullParser.START_TAG, null, "boolean");
|
||||
editor.putBoolean(key, Boolean.parseBoolean(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "boolean");
|
||||
break;
|
||||
case "int":
|
||||
parser.require(XmlPullParser.START_TAG, null, "int");
|
||||
editor.putInt(key, Integer.parseInt(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "int");
|
||||
break;
|
||||
case "long":
|
||||
parser.require(XmlPullParser.START_TAG, null, "long");
|
||||
editor.putLong(key, Long.parseLong(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "long");
|
||||
break;
|
||||
case "float":
|
||||
parser.require(XmlPullParser.START_TAG, null, "int");
|
||||
editor.putFloat(key, Float.parseFloat(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "int");
|
||||
break;
|
||||
default:
|
||||
parser.next();
|
||||
}
|
||||
}
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
editor.remove(Const.Key.ETAG_KEY);
|
||||
editor.apply();
|
||||
loadConfig();
|
||||
config.delete();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
@@ -16,15 +15,11 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
@@ -33,8 +28,8 @@ public class MainActivity extends Activity
|
||||
implements NavigationView.OnNavigationItemSelectedListener, Topic.Subscriber {
|
||||
|
||||
private final Handler mDrawerHandler = new Handler();
|
||||
private SharedPreferences prefs;
|
||||
private int mDrawerItem;
|
||||
private boolean fromShortcut = true;
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.drawer_layout) DrawerLayout drawer;
|
||||
@@ -51,7 +46,6 @@ public class MainActivity extends Activity
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
|
||||
MagiskManager mm = getMagiskManager();
|
||||
prefs = mm.prefs;
|
||||
|
||||
if (!mm.hasInit) {
|
||||
Intent intent = new Intent(this, SplashActivity.class);
|
||||
@@ -96,16 +90,6 @@ public class MainActivity extends Activity
|
||||
navigate(getIntent().getStringExtra(Const.Key.OPEN_SECTION));
|
||||
|
||||
navigationView.setNavigationItemSelectedListener(this);
|
||||
|
||||
if (mm.prefs.getInt(Const.Key.APP_VER, -1) < BuildConfig.VERSION_CODE) {
|
||||
prefs.edit().putInt(Const.Key.APP_VER, BuildConfig.VERSION_CODE).apply();
|
||||
try {
|
||||
InputStream is = getAssets().open("changelog.md");
|
||||
new MarkDownWindow(this, getString(R.string.app_changelog), is).exec();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -118,7 +102,7 @@ public class MainActivity extends Activity
|
||||
public void onBackPressed() {
|
||||
if (drawer.isDrawerOpen(navigationView)) {
|
||||
drawer.closeDrawer(navigationView);
|
||||
} else if (mDrawerItem != R.id.magisk) {
|
||||
} else if (mDrawerItem != R.id.magisk && !fromShortcut) {
|
||||
navigate(R.id.magisk);
|
||||
} else {
|
||||
finish();
|
||||
@@ -134,7 +118,7 @@ public class MainActivity extends Activity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
public void onTopicPublished(Topic topic) {
|
||||
recreate();
|
||||
}
|
||||
|
||||
@@ -147,8 +131,8 @@ public class MainActivity extends Activity
|
||||
MagiskManager mm = getMagiskManager();
|
||||
Menu menu = navigationView.getMenu();
|
||||
menu.findItem(R.id.magiskhide).setVisible(
|
||||
Shell.rootAccess() && mm.magiskVersionCode >= 1300
|
||||
&& prefs.getBoolean(Const.Key.MAGISKHIDE, false));
|
||||
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)
|
||||
@@ -162,9 +146,6 @@ public class MainActivity extends Activity
|
||||
int itemId = R.id.magisk;
|
||||
if (item != null) {
|
||||
switch (item) {
|
||||
case "magisk":
|
||||
itemId = R.id.magisk;
|
||||
break;
|
||||
case "superuser":
|
||||
itemId = R.id.superuser;
|
||||
break;
|
||||
@@ -197,22 +178,23 @@ public class MainActivity extends Activity
|
||||
navigationView.setCheckedItem(itemId);
|
||||
switch (itemId) {
|
||||
case R.id.magisk:
|
||||
displayFragment(new MagiskFragment(), "magisk", true);
|
||||
fromShortcut = false;
|
||||
displayFragment(new MagiskFragment(), true);
|
||||
break;
|
||||
case R.id.superuser:
|
||||
displayFragment(new SuperuserFragment(), "superuser", true);
|
||||
displayFragment(new SuperuserFragment(), true);
|
||||
break;
|
||||
case R.id.modules:
|
||||
displayFragment(new ModulesFragment(), "modules", true);
|
||||
displayFragment(new ModulesFragment(), true);
|
||||
break;
|
||||
case R.id.downloads:
|
||||
displayFragment(new ReposFragment(), "downloads", true);
|
||||
displayFragment(new ReposFragment(), true);
|
||||
break;
|
||||
case R.id.magiskhide:
|
||||
displayFragment(new MagiskHideFragment(), Const.Key.MAGISKHIDE, true);
|
||||
displayFragment(new MagiskHideFragment(), true);
|
||||
break;
|
||||
case R.id.log:
|
||||
displayFragment(new LogFragment(), "log", false);
|
||||
displayFragment(new LogFragment(), false);
|
||||
break;
|
||||
case R.id.settings:
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
@@ -225,12 +207,13 @@ public class MainActivity extends Activity
|
||||
}
|
||||
}
|
||||
|
||||
private void displayFragment(@NonNull Fragment navFragment, String tag, boolean setElevation) {
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
private void displayFragment(@NonNull Fragment navFragment, boolean setElevation) {
|
||||
supportInvalidateOptionsMenu();
|
||||
transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
transaction.replace(R.id.content_frame, navFragment, tag).commitNow();
|
||||
if (setElevation) toolbar.setElevation(toolbarElevation);
|
||||
else toolbar.setElevation(0);
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
.replace(R.id.content_frame, navFragment)
|
||||
.commitNow();
|
||||
toolbar.setElevation(setElevation ? toolbarElevation : 0);
|
||||
}
|
||||
}
|
@@ -20,9 +20,8 @@ import com.topjohnwu.magisk.asyncs.LoadModules;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -40,7 +39,7 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
@OnClick(R.id.fab)
|
||||
public void selectFile() {
|
||||
Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> {
|
||||
runWithPermission(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, () -> {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("application/zip");
|
||||
startActivityForResult(intent, Const.ID.FETCH_ZIP);
|
||||
@@ -79,7 +78,7 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
public void onTopicPublished(Topic topic) {
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@@ -113,16 +112,16 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.reboot:
|
||||
Shell.su_raw("/system/bin/reboot");
|
||||
Shell.Async.su("/system/bin/reboot");
|
||||
return true;
|
||||
case R.id.reboot_recovery:
|
||||
Shell.su_raw("/system/bin/reboot recovery");
|
||||
Shell.Async.su("/system/bin/reboot recovery");
|
||||
return true;
|
||||
case R.id.reboot_bootloader:
|
||||
Shell.su_raw("/system/bin/reboot bootloader");
|
||||
Shell.Async.su("/system/bin/reboot bootloader");
|
||||
return true;
|
||||
case R.id.reboot_download:
|
||||
Shell.su_raw("/system/bin/reboot download");
|
||||
Shell.Async.su("/system/bin/reboot download");
|
||||
return true;
|
||||
default:
|
||||
return false;
|
26
app/src/full/java/com/topjohnwu/magisk/NoUIActivity.java
Normal file
26
app/src/full/java/com/topjohnwu/magisk/NoUIActivity.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
|
||||
public class NoUIActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
String[] perms = getIntent().getStringArrayExtra(Const.Key.INTENT_PERM);
|
||||
if (perms != null) {
|
||||
ActivityCompat.requestPermissions(this, perms, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
finish();
|
||||
}
|
||||
}
|
@@ -27,6 +27,7 @@ import butterknife.Unbinder;
|
||||
public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
private Unbinder unbinder;
|
||||
private MagiskManager mm;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@@ -44,8 +45,9 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_repos, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
mm = getApplication();
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
mSwipeRefreshLayout.setRefreshing(mm.repoLoadDone.isPending());
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
@@ -60,7 +62,7 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
adapter = new ReposAdapter(getApplication().repoDB, getApplication().moduleMap);
|
||||
adapter = new ReposAdapter(mm.repoDB, mm.moduleMap);
|
||||
recyclerView.setAdapter(adapter);
|
||||
super.onResume();
|
||||
}
|
||||
@@ -72,7 +74,7 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
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);
|
||||
@@ -80,7 +82,7 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { getApplication().repoLoadDone };
|
||||
return new Topic[] { mm.repoLoadDone };
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,7 +105,6 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
MagiskManager mm = getApplication();
|
||||
if (item.getItemId() == R.id.repo_sort) {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.sorting_order)
|
@@ -1,32 +1,38 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.support.v14.preference.SwitchPreference;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceCategory;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.support.v7.preference.PreferenceScreen;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.asyncs.HideManager;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
@@ -38,7 +44,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_Transparent_Dark;
|
||||
return R.style.AppTheme_StatusBar_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,13 +66,13 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
setFloating();
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
getFragmentManager().beginTransaction().add(R.id.container, new SettingsFragment()).commit();
|
||||
getSupportFragmentManager().beginTransaction().add(R.id.container, new SettingsFragment()).commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
public void onTopicPublished(Topic topic) {
|
||||
recreate();
|
||||
}
|
||||
|
||||
@@ -75,7 +81,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
return new Topic[] { getMagiskManager().reloadActivity };
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragment
|
||||
public static class SettingsFragment extends PreferenceFragmentCompat
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener, Topic.Subscriber {
|
||||
|
||||
private SharedPreferences prefs;
|
||||
@@ -87,9 +93,8 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
private PreferenceCategory generalCatagory;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.app_settings);
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.app_settings, rootKey);
|
||||
mm = Utils.getMagiskManager(getActivity());
|
||||
prefs = mm.prefs;
|
||||
prefScreen = getPreferenceScreen();
|
||||
@@ -98,6 +103,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
|
||||
PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
|
||||
Preference hideManager = findPreference("hide");
|
||||
Preference restoreManager = findPreference("restore");
|
||||
findPreference("clear").setOnPreferenceClickListener((pref) -> {
|
||||
prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
||||
mm.repoDB.clearRepo();
|
||||
@@ -122,13 +128,13 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
EditText url = v.findViewById(R.id.custom_url);
|
||||
url.setText(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.settings_update_custom)
|
||||
.setView(v)
|
||||
.setPositiveButton(R.string.ok, (d, i) ->
|
||||
prefs.edit().putString(Const.Key.CUSTOM_CHANNEL,
|
||||
url.getText().toString()).apply())
|
||||
.setNegativeButton(R.string.close, null)
|
||||
.show();
|
||||
.setTitle(R.string.settings_update_custom)
|
||||
.setView(v)
|
||||
.setPositiveButton(R.string.ok, (d, i) ->
|
||||
prefs.edit().putString(Const.Key.CUSTOM_CHANNEL,
|
||||
url.getText().toString()).apply())
|
||||
.setNegativeButton(R.string.close, null)
|
||||
.show();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -146,19 +152,43 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
reauth.setSummary(R.string.android_o_not_support);
|
||||
}
|
||||
|
||||
// Remove fingerprint option if not possible
|
||||
// Disable fingerprint option if not possible
|
||||
if (!FingerprintHelper.canUseFingerprint()) {
|
||||
suCategory.removePreference(fingerprint);
|
||||
fingerprint.setEnabled(false);
|
||||
fingerprint.setSummary(R.string.disable_fingerprint);
|
||||
}
|
||||
|
||||
if (mm.getPackageName().equals(Const.ORIG_PKG_NAME) && mm.magiskVersionCode >= 1440) {
|
||||
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||
Utils.runWithPermission(getActivity(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
() -> new HideManager(getActivity()).exec());
|
||||
return true;
|
||||
});
|
||||
if (mm.magiskVersionCode >= Const.MAGISK_VER.MANAGER_HIDE) {
|
||||
if (mm.getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||
new HideManager(getActivity()).exec();
|
||||
return true;
|
||||
});
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
} else {
|
||||
if (Utils.checkNetworkStatus()) {
|
||||
restoreManager.setOnPreferenceClickListener((pref) -> {
|
||||
Utils.dlAndReceive(
|
||||
getActivity(), new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
mm.dumpPrefs();
|
||||
if (ShellUtils.fastCmdResult("pm install " + uri.getPath()))
|
||||
RootUtils.uninstallPkg(context.getPackageName());
|
||||
}
|
||||
},
|
||||
mm.managerLink,
|
||||
Utils.fmt("MagiskManager-v%s.apk", mm.remoteManagerVersionString)
|
||||
);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
}
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
|
||||
@@ -170,19 +200,15 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
if (!Shell.rootAccess()) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
} else if (mm.magiskVersionCode < 1300) {
|
||||
} else if (mm.magiskVersionCode < Const.MAGISK_VER.UNIFIED) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
}
|
||||
}
|
||||
|
||||
private void setLocalePreference(ListPreference lp) {
|
||||
boolean isNew = lp == null;
|
||||
if (isNew) {
|
||||
lp = new ListPreference(getActivity());
|
||||
}
|
||||
CharSequence[] entries = new CharSequence[mm.locales.size() + 1];
|
||||
CharSequence[] entryValues = new CharSequence[mm.locales.size() + 1];
|
||||
entries[0] = getString(R.string.system_default);
|
||||
entries[0] = Utils.getLocaleString(MagiskManager.defaultLocale, R.string.system_default);
|
||||
entryValues[0] = "";
|
||||
int i = 1;
|
||||
for (Locale locale : mm.locales) {
|
||||
@@ -191,26 +217,21 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
}
|
||||
lp.setEntries(entries);
|
||||
lp.setEntryValues(entryValues);
|
||||
lp.setTitle(R.string.language);
|
||||
lp.setKey(Const.Key.LOCALE);
|
||||
lp.setSummary(MagiskManager.locale.getDisplayName(MagiskManager.locale));
|
||||
if (isNew) {
|
||||
generalCatagory.addPreference(lp);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
subscribeTopics();
|
||||
return super.onCreateView(inflater, container, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
public void onDestroyView() {
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
unsubscribeTopics();
|
||||
super.onPause();
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -220,37 +241,39 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
case Const.Key.DARK_THEME:
|
||||
mm.isDarkTheme = prefs.getBoolean(key, false);
|
||||
mm.reloadActivity.publish(false);
|
||||
break;
|
||||
return;
|
||||
case Const.Key.COREONLY:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Utils.createFile(Const.MAGISK_DISABLE_FILE);
|
||||
try {
|
||||
Const.MAGISK_DISABLE_FILE.createNewFile();
|
||||
} catch (IOException ignored) {}
|
||||
} else {
|
||||
Utils.removeItem(Const.MAGISK_DISABLE_FILE);
|
||||
Const.MAGISK_DISABLE_FILE.delete();
|
||||
}
|
||||
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case Const.Key.MAGISKHIDE:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.su_raw("magiskhide --enable");
|
||||
Shell.Async.su("magiskhide --enable");
|
||||
} else {
|
||||
Shell.su_raw("magiskhide --disable");
|
||||
Shell.Async.su("magiskhide --disable");
|
||||
}
|
||||
break;
|
||||
case Const.Key.HOSTS:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.su_raw(
|
||||
"cp -af /system/etc/hosts " + Const.MAGISK_HOST_FILE(),
|
||||
"mount -o bind " + Const.MAGISK_HOST_FILE() + " /system/etc/hosts");
|
||||
Shell.Async.su(
|
||||
"cp -af /system/etc/hosts " + Const.MAGISK_HOST_FILE,
|
||||
"mount -o bind " + Const.MAGISK_HOST_FILE + " /system/etc/hosts");
|
||||
} else {
|
||||
Shell.su_raw(
|
||||
Shell.Async.su(
|
||||
"umount -l /system/etc/hosts",
|
||||
"rm -f " + Const.MAGISK_HOST_FILE());
|
||||
"rm -f " + Const.MAGISK_HOST_FILE);
|
||||
}
|
||||
break;
|
||||
case Const.Key.ROOT_ACCESS:
|
||||
case Const.Key.SU_MULTIUSER_MODE:
|
||||
case Const.Key.SU_MNT_NS:
|
||||
mm.suDB.setSettings(key, Utils.getPrefsInt(prefs, key));
|
||||
mm.mDB.setSettings(key, Utils.getPrefsInt(prefs, key));
|
||||
break;
|
||||
case Const.Key.LOCALE:
|
||||
mm.setLocale();
|
||||
@@ -259,6 +282,9 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
case Const.Key.UPDATE_CHANNEL:
|
||||
new CheckUpdates().exec();
|
||||
break;
|
||||
case Const.Key.CHECK_UPDATES:
|
||||
mm.setupUpdateCheck();
|
||||
break;
|
||||
}
|
||||
mm.loadConfig();
|
||||
setSummary();
|
||||
@@ -282,7 +308,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic, Object result) {
|
||||
public void onTopicPublished(Topic topic) {
|
||||
setLocalePreference((ListPreference) findPreference(Const.Key.LOCALE));
|
||||
}
|
||||
|
@@ -2,10 +2,6 @@ package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -15,27 +11,26 @@ import com.topjohnwu.magisk.asyncs.LoadModules;
|
||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.receivers.ShortcutReceiver;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class SplashActivity extends Activity {
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
RootUtils.init();
|
||||
MagiskManager mm = getMagiskManager();
|
||||
|
||||
mm.repoDB = new RepoDatabaseHelper(this);
|
||||
mm.loadMagiskInfo();
|
||||
mm.getDefaultInstallFlags();
|
||||
Utils.loadPrefs();
|
||||
mm.loadPrefs();
|
||||
|
||||
// Dynamic detect all locales
|
||||
new LoadLocale().exec();
|
||||
@@ -47,36 +42,24 @@ public class SplashActivity extends Activity {
|
||||
getSystemService(NotificationManager.class).createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
// Setup shortcuts
|
||||
sendBroadcast(new Intent(this, ShortcutReceiver.class));
|
||||
|
||||
LoadModules loadModuleTask = new LoadModules();
|
||||
|
||||
if (Utils.checkNetworkStatus()) {
|
||||
|
||||
// Fire update check
|
||||
new CheckUpdates().exec();
|
||||
|
||||
// Add repo update check
|
||||
loadModuleTask.setCallBack(() -> new UpdateRepos(false).exec());
|
||||
}
|
||||
|
||||
// Magisk working as expected
|
||||
if (Shell.rootAccess() && mm.magiskVersionCode > 0) {
|
||||
|
||||
// Add update checking service
|
||||
if (Const.UPDATE_SERVICE_VER > mm.prefs.getInt(Const.Key.UPDATE_SERVICE_VER, -1)) {
|
||||
ComponentName service = new ComponentName(this, UpdateCheckService.class);
|
||||
JobInfo info = new JobInfo.Builder(Const.ID.UPDATE_SERVICE_ID, service)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
.setPersisted(true)
|
||||
.setPeriodic(8 * 60 * 60 * 1000)
|
||||
.build();
|
||||
((JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(info);
|
||||
}
|
||||
|
||||
// Update check service
|
||||
mm.setupUpdateCheck();
|
||||
// Fire asynctasks
|
||||
loadModuleTask.exec();
|
||||
|
||||
// Check dtbo status
|
||||
Utils.patchDTBO();
|
||||
}
|
||||
|
||||
// Write back default values
|
@@ -46,7 +46,7 @@ public class SuLogFragment extends Fragment {
|
||||
View v = inflater.inflate(R.layout.fragment_su_log, container, false);
|
||||
unbinder = ButterKnife.bind(this, v);
|
||||
mm = getApplication();
|
||||
adapter = new SuLogAdapter(mm.suDB);
|
||||
adapter = new SuLogAdapter(mm.mDB);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
updateList();
|
||||
@@ -73,7 +73,7 @@ public class SuLogFragment extends Fragment {
|
||||
updateList();
|
||||
return true;
|
||||
case R.id.menu_clear:
|
||||
mm.suDB.clearLogs();
|
||||
mm.mDB.clearLogs();
|
||||
updateList();
|
||||
return true;
|
||||
default:
|
@@ -34,13 +34,13 @@ public class SuperuserFragment extends Fragment {
|
||||
PackageManager pm = getActivity().getPackageManager();
|
||||
MagiskManager mm = getApplication();
|
||||
|
||||
List<Policy> policyList = mm.suDB.getPolicyList(pm);
|
||||
List<Policy> policyList = mm.mDB.getPolicyList(pm);
|
||||
|
||||
if (policyList.size() == 0) {
|
||||
emptyRv.setVisibility(View.VISIBLE);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
} else {
|
||||
recyclerView.setAdapter(new PolicyAdapter(policyList, mm.suDB, pm));
|
||||
recyclerView.setAdapter(new PolicyAdapter(policyList, mm.mDB, pm));
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
}
|
@@ -1,9 +1,7 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -14,13 +12,11 @@ import android.widget.Filter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -32,25 +28,19 @@ import butterknife.ButterKnife;
|
||||
|
||||
public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> {
|
||||
|
||||
private List<ApplicationInfo> mOriginalList, mList;
|
||||
private List<String> mHideList;
|
||||
private List<ApplicationInfo> fullList, showList;
|
||||
private List<String> hideList;
|
||||
private PackageManager pm;
|
||||
private ApplicationFilter filter;
|
||||
private Topic magiskHideDone;
|
||||
|
||||
public ApplicationAdapter(Context context) {
|
||||
mOriginalList = mList = Collections.emptyList();
|
||||
mHideList = Collections.emptyList();
|
||||
public ApplicationAdapter() {
|
||||
fullList = showList = Collections.emptyList();
|
||||
hideList = Collections.emptyList();
|
||||
filter = new ApplicationFilter();
|
||||
pm = context.getPackageManager();
|
||||
magiskHideDone = Utils.getMagiskManager(context).magiskHideDone;
|
||||
pm = MagiskManager.get().getPackageManager();
|
||||
new LoadApps().exec();
|
||||
}
|
||||
|
||||
private boolean lowercaseContains(CharSequence string, CharSequence nonNullLowercaseSearch) {
|
||||
return !TextUtils.isEmpty(string) && string.toString().toLowerCase().contains(nonNullLowercaseSearch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_app, parent, false);
|
||||
@@ -59,41 +49,28 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
||||
ApplicationInfo info = mList.get(position);
|
||||
ApplicationInfo info = showList.get(position);
|
||||
|
||||
holder.appIcon.setImageDrawable(info.loadIcon(pm));
|
||||
holder.appName.setText(info.loadLabel(pm));
|
||||
holder.appPackage.setText(info.packageName);
|
||||
|
||||
// Remove all listeners
|
||||
holder.itemView.setOnClickListener(null);
|
||||
holder.checkBox.setOnCheckedChangeListener(null);
|
||||
|
||||
if (Const.SN_DEFAULTLIST.contains(info.packageName)) {
|
||||
holder.checkBox.setChecked(true);
|
||||
holder.checkBox.setEnabled(false);
|
||||
holder.itemView.setOnClickListener(v ->
|
||||
SnackbarMaker.make(holder.itemView,
|
||||
R.string.safetyNet_hide_notice, Snackbar.LENGTH_LONG).show()
|
||||
);
|
||||
} else {
|
||||
holder.checkBox.setEnabled(true);
|
||||
holder.checkBox.setChecked(mHideList.contains(info.packageName));
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if (isChecked) {
|
||||
Shell.su_raw("magiskhide --add " + info.packageName);
|
||||
mHideList.add(info.packageName);
|
||||
} else {
|
||||
Shell.su_raw("magiskhide --rm " + info.packageName);
|
||||
mHideList.remove(info.packageName);
|
||||
}
|
||||
});
|
||||
}
|
||||
holder.checkBox.setChecked(hideList.contains(info.packageName));
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if (isChecked) {
|
||||
Shell.Async.su("magiskhide --add " + info.packageName);
|
||||
hideList.add(info.packageName);
|
||||
} else {
|
||||
Shell.Async.su("magiskhide --rm " + info.packageName);
|
||||
hideList.remove(info.packageName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mList.size();
|
||||
return showList.size();
|
||||
}
|
||||
|
||||
public void filter(String constraint) {
|
||||
@@ -119,17 +96,21 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
|
||||
private class ApplicationFilter extends Filter {
|
||||
|
||||
private boolean lowercaseContains(String s, CharSequence filter) {
|
||||
return !TextUtils.isEmpty(s) && s.toLowerCase().contains(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
if (constraint == null || constraint.length() == 0) {
|
||||
mList = mOriginalList;
|
||||
showList = fullList;
|
||||
} else {
|
||||
mList = new ArrayList<>();
|
||||
showList = new ArrayList<>();
|
||||
String filter = constraint.toString().toLowerCase();
|
||||
for (ApplicationInfo info : mOriginalList) {
|
||||
if (lowercaseContains(info.loadLabel(pm), filter)
|
||||
for (ApplicationInfo info : fullList) {
|
||||
if (lowercaseContains(info.loadLabel(pm).toString(), filter)
|
||||
|| lowercaseContains(info.packageName, filter)) {
|
||||
mList.add(info);
|
||||
showList.add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,22 +127,32 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
mOriginalList = pm.getInstalledApplications(0);
|
||||
for (Iterator<ApplicationInfo> i = mOriginalList.iterator(); i.hasNext(); ) {
|
||||
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(mOriginalList, (a, b) -> a.loadLabel(pm).toString().toLowerCase()
|
||||
.compareTo(b.loadLabel(pm).toString().toLowerCase()));
|
||||
mHideList = Shell.su("magiskhide --ls");
|
||||
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) {
|
||||
magiskHideDone.publish(false);
|
||||
MagiskManager.get().magiskHideDone.publish(false);
|
||||
}
|
||||
}
|
||||
}
|
@@ -14,7 +14,7 @@ import android.widget.TextView;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.List;
|
||||
|
@@ -16,7 +16,7 @@ import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||
import com.topjohnwu.magisk.database.MagiskDatabaseHelper;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -28,11 +28,11 @@ import butterknife.ButterKnife;
|
||||
public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder> {
|
||||
|
||||
private List<Policy> policyList;
|
||||
private SuDatabaseHelper dbHelper;
|
||||
private MagiskDatabaseHelper dbHelper;
|
||||
private PackageManager pm;
|
||||
private Set<Policy> expandList = new HashSet<>();
|
||||
|
||||
public PolicyAdapter(List<Policy> list, SuDatabaseHelper db, PackageManager pm) {
|
||||
public PolicyAdapter(List<Policy> list, MagiskDatabaseHelper db, PackageManager pm) {
|
||||
policyList = list;
|
||||
dbHelper = db;
|
||||
this.pm = pm;
|
@@ -13,7 +13,7 @@ import android.widget.TextView;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||
import com.topjohnwu.magisk.database.MagiskDatabaseHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@@ -27,10 +27,10 @@ public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, S
|
||||
|
||||
private List<List<Integer>> logEntryList;
|
||||
private Set<Integer> itemExpanded, sectionExpanded;
|
||||
private SuDatabaseHelper suDB;
|
||||
private MagiskDatabaseHelper suDB;
|
||||
private Cursor suLogCursor = null;
|
||||
|
||||
public SuLogAdapter(SuDatabaseHelper db) {
|
||||
public SuLogAdapter(MagiskDatabaseHelper db) {
|
||||
suDB = db;
|
||||
logEntryList = Collections.emptyList();
|
||||
sectionExpanded = new HashSet<>();
|
@@ -0,0 +1,84 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.ISafetyNetHelper;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import dalvik.system.DexClassLoader;
|
||||
|
||||
public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
|
||||
public static final File dexPath =
|
||||
new File(MagiskManager.get().getFilesDir().getParent() + "/snet", "snet.apk");
|
||||
private ISafetyNetHelper helper;
|
||||
|
||||
public CheckSafetyNet(Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
private void dlSnet() throws Exception {
|
||||
Shell.Sync.sh("rm -rf " + dexPath.getParent());
|
||||
dexPath.getParentFile().mkdir();
|
||||
HttpURLConnection conn = WebService.request(Const.Url.SNET_URL, null);
|
||||
try (
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(dexPath));
|
||||
InputStream in = new BufferedInputStream(conn.getInputStream())) {
|
||||
ShellUtils.pump(in, out);
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Exception doInBackground(Void... voids) {
|
||||
try {
|
||||
try {
|
||||
dyload();
|
||||
} catch (Exception e) {
|
||||
// If dynamic load failed, try re-downloading and reload
|
||||
dlSnet();
|
||||
dyload();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@@ -42,11 +42,14 @@ public class CheckUpdates extends ParallelTask<Void, Void, Void> {
|
||||
mm.remoteMagiskVersionString = magisk.getString("version");
|
||||
mm.remoteMagiskVersionCode = magisk.getInt("versionCode");
|
||||
mm.magiskLink = magisk.getString("link");
|
||||
mm.releaseNoteLink = magisk.getString("note");
|
||||
mm.magiskNoteLink = magisk.getString("note");
|
||||
JSONObject manager = json.getJSONObject("app");
|
||||
mm.remoteManagerVersionString = manager.getString("version");
|
||||
mm.remoteManagerVersionCode = manager.getInt("versionCode");
|
||||
mm.managerLink = manager.getString("link");
|
||||
mm.managerNoteLink = manager.getString("note");
|
||||
JSONObject uninstaller = json.getJSONObject("uninstaller");
|
||||
mm.uninstallerLink = uninstaller.getString("link");
|
||||
} catch (JSONException ignored) {}
|
||||
return null;
|
||||
}
|
||||
@@ -54,7 +57,7 @@ public class CheckUpdates extends ParallelTask<Void, Void, Void> {
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
if (showNotification && mm.prefs.getBoolean(Const.Key.UPDATE_NOTIFICATION, true)) {
|
||||
if (showNotification) {
|
||||
if (BuildConfig.VERSION_CODE < mm.remoteManagerVersionCode) {
|
||||
ShowUI.managerUpdateNotification();
|
||||
} else if (mm.magiskVersionCode < mm.remoteMagiskVersionCode) {
|
@@ -7,10 +7,12 @@ import android.view.View;
|
||||
|
||||
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.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
@@ -38,8 +40,7 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
|
||||
private boolean unzipAndCheck() throws Exception {
|
||||
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android", true);
|
||||
List<String> ret = Utils.readFile(new File(mCachedFile.getParentFile(), "updater-script"));
|
||||
return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK");
|
||||
return ShellUtils.fastCmdResult("grep -q '#MAGISK' " + new File(mCachedFile.getParentFile(), "updater-script"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,7 +56,7 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
) {
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
InputStream buf= new BufferedInputStream(in);
|
||||
Utils.inToOut(buf, out);
|
||||
ShellUtils.pump(buf, out);
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
throw e;
|
||||
@@ -65,7 +66,7 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
}
|
||||
if (!unzipAndCheck()) return 0;
|
||||
console.add("- Installing " + Utils.getNameFromUri(mm, mUri));
|
||||
Shell.getShell().run(console, logs,
|
||||
Shell.Sync.su(console, logs,
|
||||
"cd " + mCachedFile.getParent(),
|
||||
"BOOTMODE=true sh update-binary dummy 1 " + mCachedFile + " || echo 'Failed!'"
|
||||
);
|
||||
@@ -85,14 +86,14 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
FlashActivity activity = (FlashActivity) getActivity();
|
||||
Shell.su_raw(
|
||||
Shell.Async.su(
|
||||
"rm -rf " + mCachedFile.getParent(),
|
||||
"rm -rf " + Const.TMP_FOLDER_PATH
|
||||
);
|
||||
switch (result) {
|
||||
case -1:
|
||||
console.add("! Installation failed");
|
||||
Utils.showUriSnack(getActivity(), mUri);
|
||||
SnackbarMaker.showUri(getActivity(), mUri);
|
||||
break;
|
||||
case 0:
|
||||
console.add("! This zip is not a Magisk Module!");
|
@@ -0,0 +1,95 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.PatchAPK;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class HideManager extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
private ProgressDialog dialog;
|
||||
|
||||
public HideManager(Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
private 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
dialog = ProgressDialog.show(getActivity(),
|
||||
getActivity().getString(R.string.hide_manager_toast),
|
||||
getActivity().getString(R.string.hide_manager_toast2));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
|
||||
// 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 {
|
||||
if (!PatchAPK.patchPackageID(
|
||||
mm.getPackageCodePath(),
|
||||
new SuFileOutputStream(repack),
|
||||
Const.ORIG_PKG_NAME, pkg))
|
||||
return false;
|
||||
} catch (FileNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Install the application
|
||||
if (!ShellUtils.fastCmdResult(Shell.getShell(), "pm install " + repack))
|
||||
return false;
|
||||
|
||||
repack.delete();
|
||||
|
||||
mm.mDB.setStrings(Const.Key.SU_MANAGER, pkg);
|
||||
mm.dumpPrefs();
|
||||
RootUtils.uninstallPkg(Const.ORIG_PKG_NAME);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean b) {
|
||||
dialog.dismiss();
|
||||
if (!b) {
|
||||
MagiskManager.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG);
|
||||
}
|
||||
super.onPostExecute(b);
|
||||
}
|
||||
}
|
327
app/src/full/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java
Normal file
327
app/src/full/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java
Normal file
@@ -0,0 +1,327 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
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.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
import com.topjohnwu.utils.SignBoot;
|
||||
|
||||
import org.kamranzafar.jtar.TarInputStream;
|
||||
import org.kamranzafar.jtar.TarOutputStream;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.AbstractList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
private static final int PATCH_MODE = 0;
|
||||
public static final int DIRECT_MODE = 1;
|
||||
private static final int FIX_ENV_MODE = 2;
|
||||
public static final int SECOND_SLOT_MODE = 3;
|
||||
|
||||
private Uri bootUri, mZip;
|
||||
private List<String> console, logs;
|
||||
private String mBoot;
|
||||
private int mode;
|
||||
private File installDir;
|
||||
private ProgressDialog dialog;
|
||||
private MagiskManager mm;
|
||||
|
||||
public InstallMagisk(Activity context, Uri zip) {
|
||||
super(context);
|
||||
mZip = zip;
|
||||
mm = MagiskManager.get();
|
||||
mode = FIX_ENV_MODE;
|
||||
}
|
||||
|
||||
public InstallMagisk(Activity context, List<String> console, List<String> logs, Uri zip, int mode) {
|
||||
this(context, zip);
|
||||
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);
|
||||
bootUri = boot;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
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<>();
|
||||
}
|
||||
}
|
||||
|
||||
private void extractFiles(String arch) throws IOException {
|
||||
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;
|
||||
} 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));
|
||||
}
|
||||
|
||||
private boolean dumpBoot() {
|
||||
console.add("- Copying image locally");
|
||||
// Copy boot image to local
|
||||
try (InputStream in = mm.getContentResolver().openInputStream(bootUri);
|
||||
OutputStream out = new FileOutputStream(mBoot)
|
||||
) {
|
||||
if (in == null)
|
||||
throw new FileNotFoundException();
|
||||
|
||||
InputStream src;
|
||||
if (Utils.getNameFromUri(mm, bootUri).endsWith(".tar")) {
|
||||
// Extract boot.img from tar
|
||||
TarInputStream tar = new TarInputStream(new BufferedInputStream(in));
|
||||
org.kamranzafar.jtar.TarEntry entry;
|
||||
while ((entry = tar.getNextEntry()) != null) {
|
||||
if (entry.getName().equals("boot.img"))
|
||||
break;
|
||||
}
|
||||
src = tar;
|
||||
} else {
|
||||
// Direct copy raw image
|
||||
src = new BufferedInputStream(in);
|
||||
}
|
||||
ShellUtils.pump(src, out);
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
console.add("! Copy failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private File patchBoot() throws IOException {
|
||||
boolean isSigned;
|
||||
try (InputStream in = new SuFileInputStream(mBoot)) {
|
||||
isSigned = SignBoot.verifySignature(in, null);
|
||||
if (isSigned) {
|
||||
console.add("- Boot image is signed with AVB 1.0");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
console.add("! Unable to check signature");
|
||||
throw e;
|
||||
}
|
||||
|
||||
// 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!"))
|
||||
return null;
|
||||
|
||||
Shell.Sync.sh("mv bin/busybox busybox",
|
||||
"rm -rf magisk.apk bin boot.img update-binary",
|
||||
"cd /");
|
||||
|
||||
File patched = new File(installDir, "new-boot.img");
|
||||
if (isSigned) {
|
||||
console.add("- Signing boot image with test keys");
|
||||
File signed = new File(installDir, "signed.img");
|
||||
try (InputStream in = new SuFileInputStream(patched);
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(signed))
|
||||
) {
|
||||
SignBoot.doSignature("/boot", in, out, null, null);
|
||||
}
|
||||
Shell.Sync.su("mv -f " + signed + " " + patched);
|
||||
}
|
||||
return patched;
|
||||
}
|
||||
|
||||
private void outputBoot(File patched) throws IOException {
|
||||
switch (mode) {
|
||||
case PATCH_MODE:
|
||||
File dest = new File(Const.EXTERNAL_PATH, "patched_boot" + mm.bootFormat);
|
||||
dest.getParentFile().mkdirs();
|
||||
OutputStream out;
|
||||
switch (mm.bootFormat) {
|
||||
case ".img.tar":
|
||||
out = new TarOutputStream(new BufferedOutputStream(new FileOutputStream(dest)));
|
||||
((TarOutputStream) out).putNextEntry(new TarEntry(patched, "boot.img"));
|
||||
break;
|
||||
default:
|
||||
case ".img":
|
||||
out = new BufferedOutputStream(new FileOutputStream(dest));
|
||||
break;
|
||||
}
|
||||
try (InputStream in = new SuFileInputStream(patched)) {
|
||||
ShellUtils.pump(in, out);
|
||||
out.close();
|
||||
}
|
||||
Shell.Sync.su("rm -f " + patched);
|
||||
console.add("");
|
||||
console.add("****************************");
|
||||
console.add(" Patched image is placed in ");
|
||||
console.add(" " + dest + " ");
|
||||
console.add("****************************");
|
||||
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");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@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/*");
|
||||
} else {
|
||||
installDir = new File(
|
||||
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
|
||||
mm.createDeviceProtectedStorageContext() : mm)
|
||||
.getFilesDir().getParent()
|
||||
, "install");
|
||||
Shell.Sync.sh("rm -rf " + installDir);
|
||||
installDir.mkdirs();
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case PATCH_MODE:
|
||||
mBoot = new File(installDir, "boot.img").getAbsolutePath();
|
||||
if (!dumpBoot())
|
||||
return false;
|
||||
break;
|
||||
case DIRECT_MODE:
|
||||
console.add("- Detecting target image");
|
||||
mBoot = ShellUtils.fastCmd("find_boot_image", "echo \"$BOOTIMAGE\"");
|
||||
break;
|
||||
case SECOND_SLOT_MODE:
|
||||
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");
|
||||
break;
|
||||
case FIX_ENV_MODE:
|
||||
mBoot = "";
|
||||
break;
|
||||
}
|
||||
if (mBoot == null) {
|
||||
console.add("! Unable to detect target image");
|
||||
return false;
|
||||
}
|
||||
|
||||
console.add("- Target image: " + mBoot);
|
||||
|
||||
List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
|
||||
String arch;
|
||||
|
||||
if (mm.remoteMagiskVersionCode >= Const.MAGISK_VER.SEPOL_REFACTOR) {
|
||||
// 32-bit only
|
||||
if (abis.contains("x86")) arch = "x86";
|
||||
else arch = "arm";
|
||||
} else {
|
||||
if (abis.contains("x86_64")) arch = "x64";
|
||||
else if (abis.contains("arm64-v8a")) arch = "arm64";
|
||||
else if (abis.contains("x86")) arch = "x86";
|
||||
else arch = "arm";
|
||||
}
|
||||
|
||||
console.add("- Device platform: " + Build.SUPPORTED_ABIS[0]);
|
||||
|
||||
try {
|
||||
extractFiles(arch);
|
||||
if (mode == FIX_ENV_MODE) {
|
||||
Shell.Sync.sh("fix_env");
|
||||
} else {
|
||||
File patched = patchBoot();
|
||||
if (patched == null)
|
||||
return false;
|
||||
outputBoot(patched);
|
||||
console.add("- All done!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
} else {
|
||||
// Running in FlashActivity
|
||||
FlashActivity activity = (FlashActivity) getActivity();
|
||||
if (!result) {
|
||||
Shell.Async.sh("rm -rf " + installDir);
|
||||
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,15 +4,15 @@ import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.container.ValueSortedMap;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LoadModules extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
private List<String> getModList() {
|
||||
String command = "ls -d " + Const.MAGISK_PATH() + "/* | grep -v lost+found";
|
||||
return Shell.su(command);
|
||||
String command = "ls -d " + Const.MAGISK_PATH + "/* | grep -v lost+found";
|
||||
return Shell.Sync.su(command);
|
||||
}
|
||||
|
||||
@Override
|
@@ -6,8 +6,8 @@ import android.webkit.WebView;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
@@ -44,7 +44,7 @@ public class MarkDownWindow extends ParallelTask<Void, Void, String> {
|
||||
md = WebService.getString(mUrl);
|
||||
} else {
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
Utils.inToOut(is, out);
|
||||
ShellUtils.pump(is, out);
|
||||
md = out.toString();
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
@@ -54,11 +54,13 @@ public class MarkDownWindow extends ParallelTask<Void, Void, String> {
|
||||
}
|
||||
String css;
|
||||
try (
|
||||
InputStream in = mm.getAssets().open(mm.isDarkTheme ? "dark.css" : "light.css");
|
||||
InputStream in = mm.getResources().openRawResource(
|
||||
mm.isDarkTheme ? R.raw.dark : R.raw.light);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream()
|
||||
) {
|
||||
Utils.inToOut(in, out);
|
||||
ShellUtils.pump(in, out);
|
||||
css = out.toString();
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
@@ -12,11 +12,12 @@ import android.widget.Toast;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
@@ -68,7 +69,7 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
continue;
|
||||
}
|
||||
out.putNextEntry(new JarEntry(path));
|
||||
Utils.inToOut(in, out);
|
||||
ShellUtils.pump(in, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,7 +90,6 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
HttpURLConnection conn;
|
||||
do {
|
||||
conn = WebService.request(mLink, null);
|
||||
if (conn == null) return null;
|
||||
total = conn.getContentLength();
|
||||
if (total < 0)
|
||||
conn.disconnect();
|
||||
@@ -107,7 +107,7 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
InputStream in = new BufferedInputStream(new ProgressInputStream(conn.getInputStream()));
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(temp1))
|
||||
) {
|
||||
Utils.inToOut(in, out);
|
||||
ShellUtils.pump(in, out);
|
||||
in.close();
|
||||
}
|
||||
conn.disconnect();
|
||||
@@ -120,14 +120,8 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
// First remove top folder in Github source zip, temp1 -> temp2
|
||||
removeTopFolder(temp1, temp2);
|
||||
|
||||
// Then sign the zip for the first time, temp2 -> temp1
|
||||
ZipUtils.signZip(temp2, temp1, false);
|
||||
|
||||
// Adjust the zip to prevent unzip issues, temp1 -> temp2
|
||||
ZipUtils.zipAdjust(temp1.getPath(), temp2.getPath());
|
||||
|
||||
// Finally, sign the whole zip file again, temp2 -> target
|
||||
ZipUtils.signZip(temp2, mFile, true);
|
||||
// Then sign the zip
|
||||
ZipUtils.signZip(temp2, mFile);
|
||||
|
||||
// Delete temp files
|
||||
temp1.delete();
|
||||
@@ -152,7 +146,7 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
intent.setData(uri).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||
activity.startActivity(intent);
|
||||
} else {
|
||||
Utils.showUriSnack(activity, uri);
|
||||
SnackbarMaker.showUri(activity, uri);
|
||||
}
|
||||
} else {
|
||||
MagiskManager.toast(R.string.process_error, Toast.LENGTH_LONG);
|
||||
@@ -162,7 +156,8 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
|
||||
@Override
|
||||
public ParallelTask<Void, Object, Boolean> exec(Void... voids) {
|
||||
Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
com.topjohnwu.magisk.components.Activity.runWithPermission(
|
||||
getActivity(), new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
|
||||
() -> super.exec(voids));
|
||||
return this;
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
public class RestoreImages extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
private ProgressDialog dialog;
|
||||
|
||||
public RestoreImages(Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
Activity a = getActivity();
|
||||
dialog = ProgressDialog.show(a, a.getString(R.string.restore_img), a.getString(R.string.restore_img_msg));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
return ShellUtils.fastCmdResult("restore_imgs");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
dialog.cancel();
|
||||
if (result) {
|
||||
MagiskManager.toast(R.string.restore_done, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
MagiskManager.toast(R.string.restore_fail, Toast.LENGTH_LONG);
|
||||
}
|
||||
}
|
||||
}
|
210
app/src/full/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java
Normal file
210
app/src/full/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java
Normal file
@@ -0,0 +1,210 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.ReposFragment;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
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.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class UpdateRepos extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
private static final int CHECK_ETAG = 0;
|
||||
private static final int LOAD_NEXT = 1;
|
||||
private static final int LOAD_PREV = 2;
|
||||
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
|
||||
private MagiskManager mm;
|
||||
private List<String> etags, newEtags = new LinkedList<>();
|
||||
private Set<String> cached;
|
||||
private boolean forceUpdate;
|
||||
private AtomicInteger taskCount = new AtomicInteger(0);
|
||||
final private Object allDone = new Object();
|
||||
|
||||
public UpdateRepos(boolean force) {
|
||||
mm = MagiskManager.get();
|
||||
mm.repoLoadDone.reset();
|
||||
forceUpdate = force;
|
||||
}
|
||||
|
||||
private void queueTask(Runnable task) {
|
||||
// Thread pool's queue has an upper bound, batch it with 64 tasks
|
||||
while (taskCount.get() >= 64) {
|
||||
waitTasks();
|
||||
}
|
||||
taskCount.incrementAndGet();
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
task.run();
|
||||
if (taskCount.decrementAndGet() == 0) {
|
||||
synchronized (allDone) {
|
||||
allDone.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void waitTasks() {
|
||||
if (taskCount.get() == 0)
|
||||
return;
|
||||
synchronized (allDone) {
|
||||
try {
|
||||
allDone.wait();
|
||||
} catch (InterruptedException e) {
|
||||
// Wait again
|
||||
waitTasks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
queueTask(() -> {
|
||||
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);
|
||||
publishProgress();
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.debug(e.getMessage());
|
||||
mm.repoDB.removeRepo(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean loadPage(int page, int mode) {
|
||||
Map<String, String> header = new HashMap<>();
|
||||
if (mode == CHECK_ETAG && page < etags.size())
|
||||
header.put(Const.Key.IF_NONE_MATCH, etags.get(page));
|
||||
String url = Utils.fmt(Const.Url.REPO_URL, page + 1);
|
||||
|
||||
try {
|
||||
HttpURLConnection conn = WebService.request(url, header);
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
// Current page is not updated, check the next page
|
||||
return loadPage(page + 1, CHECK_ETAG);
|
||||
}
|
||||
if (!loadJSON(WebService.getString(conn)))
|
||||
return mode != CHECK_ETAG;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If one page is updated, we force update all pages */
|
||||
|
||||
// Update ETAG
|
||||
String etag = header.get(Const.Key.ETAG_KEY);
|
||||
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
|
||||
if (mode == LOAD_PREV) {
|
||||
// We are loading a previous page, push the new tag to the front
|
||||
newEtags.add(0, etag);
|
||||
} else {
|
||||
newEtags.add(etag);
|
||||
}
|
||||
|
||||
String links = header.get(Const.Key.LINK_KEY);
|
||||
if (links != null) {
|
||||
for (String s : links.split(", ")) {
|
||||
if (mode != LOAD_PREV && s.contains("next")) {
|
||||
// Force load all next pages
|
||||
loadPage(page + 1, LOAD_NEXT);
|
||||
}
|
||||
if (mode != LOAD_NEXT && s.contains("prev")) {
|
||||
// Back propagation
|
||||
loadPage(page - 1, LOAD_PREV);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Void... values) {
|
||||
if (ReposFragment.adapter != null)
|
||||
ReposFragment.adapter.notifyDBChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
mm.repoLoadDone.setPending();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
etags = Arrays.asList(mm.prefs.getString(Const.Key.ETAG_KEY, "").split(","));
|
||||
cached = mm.repoDB.getRepoIDSet();
|
||||
|
||||
if (loadPage(0, CHECK_ETAG)) {
|
||||
waitTasks();
|
||||
|
||||
// The leftover cached means they are removed from online repo
|
||||
mm.repoDB.removeRepo(cached);
|
||||
|
||||
// Update ETag
|
||||
mm.prefs.edit().putString(Const.Key.ETAG_KEY, TextUtils.join(",", newEtags)).apply();
|
||||
} else if (forceUpdate) {
|
||||
Cursor c = mm.repoDB.getRawCursor();
|
||||
while (c.moveToNext()) {
|
||||
Repo repo = new Repo(c);
|
||||
queueTask(() -> {
|
||||
try {
|
||||
repo.update();
|
||||
mm.repoDB.addRepo(repo);
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.debug(e.getMessage());
|
||||
mm.repoDB.removeRepo(repo);
|
||||
}
|
||||
});
|
||||
}
|
||||
waitTasks();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
mm.repoLoadDone.publish();
|
||||
super.onPostExecute(v);
|
||||
}
|
||||
}
|
@@ -0,0 +1,108 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Keep;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StyleRes;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
public abstract class FlavorActivity extends AppCompatActivity {
|
||||
|
||||
private AssetManager swappedAssetManager = null;
|
||||
private Resources swappedResources = null;
|
||||
private Resources.Theme backupTheme = null;
|
||||
|
||||
@StyleRes
|
||||
public int getDarkTheme() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public MagiskManager getMagiskManager() {
|
||||
return (MagiskManager) super.getApplication();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (this instanceof Topic.Subscriber) {
|
||||
((Topic.Subscriber) this).subscribeTopics();
|
||||
}
|
||||
|
||||
if (getMagiskManager().isDarkTheme && getDarkTheme() != -1) {
|
||||
setTheme(getDarkTheme());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (this instanceof Topic.Subscriber) {
|
||||
((Topic.Subscriber) this).unsubscribeTopics();
|
||||
}
|
||||
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
|
||||
public Resources.Theme getTheme() {
|
||||
return backupTheme == null ? super.getTheme() : backupTheme;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetManager getAssets() {
|
||||
return swappedAssetManager == null ? super.getAssets() : swappedAssetManager;
|
||||
}
|
||||
|
||||
private AssetManager getAssets(String apk) {
|
||||
try {
|
||||
AssetManager asset = AssetManager.class.newInstance();
|
||||
AssetManager.class.getMethod("addAssetPath", String.class).invoke(asset, apk);
|
||||
return asset;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resources getResources() {
|
||||
return swappedResources == null ? super.getResources() : swappedResources;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void swapResources(String dexPath) {
|
||||
AssetManager asset = getAssets(dexPath);
|
||||
if (asset != null) {
|
||||
backupTheme = super.getTheme();
|
||||
Resources res = super.getResources();
|
||||
swappedResources = new Resources(asset, res.getDisplayMetrics(), res.getConfiguration());
|
||||
swappedAssetManager = asset;
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void restoreResources() {
|
||||
swappedAssetManager = null;
|
||||
swappedResources = null;
|
||||
backupTheme = null;
|
||||
}
|
||||
}
|
@@ -36,4 +36,8 @@ public class Fragment extends android.support.v4.app.Fragment {
|
||||
public void startActivityForResult(Intent intent, int requestCode, Activity.ActivityResultListener listener) {
|
||||
((Activity) getActivity()).startActivityForResult(intent, requestCode, listener);
|
||||
}
|
||||
|
||||
public void runWithPermission(String[] permissions, Runnable callback) {
|
||||
Activity.runWithPermission(getActivity(), permissions, callback);
|
||||
}
|
||||
}
|
@@ -1,11 +1,15 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
public class SnackbarMaker {
|
||||
|
||||
public static Snackbar make(Activity activity, CharSequence text, int duration) {
|
||||
@@ -34,4 +38,10 @@ public class SnackbarMaker {
|
||||
text.setMaxLines(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
public static void showUri(Activity activity, Uri uri) {
|
||||
make(activity, activity.getString(R.string.internal_storage,
|
||||
"/MagiskManager/" + Utils.getNameFromUri(activity, uri)),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.ok, (v)->{}).show();
|
||||
}
|
||||
}
|
@@ -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;
|
@@ -1,21 +1,22 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
public class Module extends BaseModule {
|
||||
|
||||
private String mRemoveFile, mDisableFile, mUpdateFile;
|
||||
private SuFile mRemoveFile, mDisableFile, mUpdateFile;
|
||||
private boolean mEnable, mRemove, mUpdated;
|
||||
|
||||
public Module(String path) {
|
||||
|
||||
try {
|
||||
parseProps(Utils.readFile(path + "/module.prop"));
|
||||
parseProps(Shell.Sync.su("dos2unix < " + path + "/module.prop"));
|
||||
} catch (NumberFormatException ignored) {}
|
||||
|
||||
mRemoveFile = path + "/remove";
|
||||
mDisableFile = path + "/disable";
|
||||
mUpdateFile = path + "/update";
|
||||
mRemoveFile = new SuFile(path + "/remove");
|
||||
mDisableFile = new SuFile(path + "/disable");
|
||||
mUpdateFile = new SuFile(path + "/update");
|
||||
|
||||
if (getId() == null) {
|
||||
int sep = path.lastIndexOf('/');
|
||||
@@ -26,19 +27,19 @@ public class Module extends BaseModule {
|
||||
setName(getId());
|
||||
}
|
||||
|
||||
mEnable = !Utils.itemExist(mDisableFile);
|
||||
mRemove = Utils.itemExist(mRemoveFile);
|
||||
mUpdated = Utils.itemExist(mUpdateFile);
|
||||
mEnable = !mDisableFile.exists();
|
||||
mRemove = mRemoveFile.exists();
|
||||
mUpdated = mUpdateFile.exists();
|
||||
}
|
||||
|
||||
public void createDisableFile() {
|
||||
mEnable = false;
|
||||
Utils.createFile(mDisableFile);
|
||||
mDisableFile.createNewFile();
|
||||
}
|
||||
|
||||
public void removeDisableFile() {
|
||||
mEnable = true;
|
||||
Utils.removeItem(mDisableFile);
|
||||
mDisableFile.delete();
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
@@ -47,12 +48,12 @@ public class Module extends BaseModule {
|
||||
|
||||
public void createRemoveFile() {
|
||||
mRemove = true;
|
||||
Utils.createFile(mRemoveFile);
|
||||
mRemoveFile.createNewFile();
|
||||
}
|
||||
|
||||
public void deleteRemoveFile() {
|
||||
mRemove = false;
|
||||
Utils.removeItem(mRemoveFile);
|
||||
mRemoveFile.delete();
|
||||
}
|
||||
|
||||
public boolean willBeRemoved() {
|
@@ -20,7 +20,8 @@ public class Policy implements Comparable<Policy>{
|
||||
|
||||
public Policy(int uid, PackageManager pm) throws PackageManager.NameNotFoundException {
|
||||
String[] pkgs = pm.getPackagesForUid(uid);
|
||||
if (pkgs == null || pkgs.length == 0) throw new PackageManager.NameNotFoundException();
|
||||
if (pkgs == null || pkgs.length == 0)
|
||||
throw new PackageManager.NameNotFoundException();
|
||||
this.uid = uid;
|
||||
packageName = pkgs[0];
|
||||
info = pm.getApplicationInfo(packageName, 0);
|
||||
@@ -35,6 +36,8 @@ public class Policy implements Comparable<Policy>{
|
||||
logging = c.getInt(c.getColumnIndex("logging")) != 0;
|
||||
notification = c.getInt(c.getColumnIndex("notification")) != 0;
|
||||
info = pm.getApplicationInfo(packageName, 0);
|
||||
if (info.uid != uid)
|
||||
throw new PackageManager.NameNotFoundException();
|
||||
appName = info.loadLabel(pm).toString();
|
||||
}
|
||||
|
@@ -5,6 +5,8 @@ import android.database.Cursor;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import java.text.DateFormat;
|
||||
@@ -15,10 +17,8 @@ public class Repo extends BaseModule {
|
||||
private String repoName;
|
||||
private Date mLastUpdate;
|
||||
|
||||
public Repo(String name, Date lastUpdate) throws IllegalRepoException {
|
||||
mLastUpdate = lastUpdate;
|
||||
public Repo(String name) {
|
||||
repoName = name;
|
||||
update();
|
||||
}
|
||||
|
||||
public Repo(Cursor c) {
|
||||
@@ -28,10 +28,9 @@ public class Repo extends BaseModule {
|
||||
}
|
||||
|
||||
public void update() throws IllegalRepoException {
|
||||
String props = WebService.getString(getManifestUrl());
|
||||
String lines[] = props.split("\\n");
|
||||
String props[] = Utils.dos2unix(WebService.getString(getManifestUrl())).split("\\n");
|
||||
try {
|
||||
parseProps(lines);
|
||||
parseProps(props);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] parse error: " + e.getMessage());
|
||||
}
|
||||
@@ -42,18 +41,14 @@ public class Repo extends BaseModule {
|
||||
if (getVersionCode() < 0) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] does not contain versionCode");
|
||||
}
|
||||
if (getMinMagiskVersion() < Const.MIN_MODULE_VER) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] is outdated");
|
||||
if (getMinMagiskVersion() < Const.MIN_MODULE_VER()) {
|
||||
Logger.debug("Repo [" + repoName + "] is outdated");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean update(Date lastUpdate) throws IllegalRepoException {
|
||||
if (lastUpdate.after(mLastUpdate)) {
|
||||
mLastUpdate = lastUpdate;
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
public void update(Date lastUpdate) throws IllegalRepoException {
|
||||
mLastUpdate = lastUpdate;
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
@@ -0,0 +1,301 @@
|
||||
package com.topjohnwu.magisk.database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
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 com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class MagiskDatabaseHelper {
|
||||
|
||||
private static final int DATABASE_VER = 5;
|
||||
private static final String POLICY_TABLE = "policies";
|
||||
private static final String LOG_TABLE = "logs";
|
||||
private static final String SETTINGS_TABLE = "settings";
|
||||
private static final String STRINGS_TABLE = "strings";
|
||||
|
||||
private PackageManager pm;
|
||||
private SQLiteDatabase db;
|
||||
|
||||
@NonNull
|
||||
public static MagiskDatabaseHelper getInstance(MagiskManager mm) {
|
||||
try {
|
||||
return new MagiskDatabaseHelper(mm);
|
||||
} catch (Exception e) {
|
||||
// Let's cleanup everything and try again
|
||||
Shell.Sync.su("db_clean '*'");
|
||||
return new MagiskDatabaseHelper(mm);
|
||||
}
|
||||
}
|
||||
|
||||
private MagiskDatabaseHelper(MagiskManager mm) {
|
||||
pm = mm.getPackageManager();
|
||||
db = openDatabase(mm);
|
||||
db.disableWriteAheadLogging();
|
||||
int version = db.getVersion();
|
||||
if (version < DATABASE_VER) {
|
||||
onUpgrade(db, version);
|
||||
} else if (version > DATABASE_VER) {
|
||||
onDowngrade(db);
|
||||
}
|
||||
db.setVersion(DATABASE_VER);
|
||||
clearOutdated();
|
||||
}
|
||||
|
||||
private SQLiteDatabase openDatabase(MagiskManager mm) {
|
||||
final File DB_FILE = new File(Utils.fmt("/sbin/.core/db-%d/magisk.db", Const.USER_ID));
|
||||
Context de = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
|
||||
? mm.createDeviceProtectedStorageContext() : mm;
|
||||
if (!DB_FILE.canWrite()) {
|
||||
if (!Shell.rootAccess()) {
|
||||
// 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) {
|
||||
// Super old legacy mode
|
||||
return mm.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
|
||||
} else if (mm.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");
|
||||
}
|
||||
return de.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
|
||||
} else {
|
||||
// Global database
|
||||
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) {
|
||||
// We need some additional policies on old versions
|
||||
Shell.Sync.su("db_sepatch");
|
||||
}
|
||||
if (!GLOBAL_DB.exists()) {
|
||||
Shell.Sync.su("db_init");
|
||||
SQLiteDatabase.openOrCreateDatabase(GLOBAL_DB, null).close();
|
||||
Shell.Sync.su("db_restore");
|
||||
}
|
||||
}
|
||||
Shell.Sync.su("db_setup " + Process.myUid());
|
||||
}
|
||||
// Not using legacy mode, open the mounted global DB
|
||||
return SQLiteDatabase.openOrCreateDatabase(DB_FILE, null);
|
||||
}
|
||||
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion) {
|
||||
if (oldVersion == 0) {
|
||||
createTables(db);
|
||||
oldVersion = 3;
|
||||
}
|
||||
if (oldVersion == 1) {
|
||||
// We're dropping column app_name, rename and re-construct table
|
||||
db.execSQL(Utils.fmt("ALTER TABLE %s RENAME TO %s_old", POLICY_TABLE));
|
||||
|
||||
// Create the new tables
|
||||
createTables(db);
|
||||
|
||||
// Migrate old data to new tables
|
||||
db.execSQL(Utils.fmt("INSERT INTO %s SELECT " +
|
||||
"uid, package_name, policy, until, logging, notification FROM %s_old",
|
||||
POLICY_TABLE, POLICY_TABLE));
|
||||
db.execSQL(Utils.fmt("DROP TABLE %s_old", POLICY_TABLE));
|
||||
|
||||
MagiskManager.get().deleteDatabase("sulog.db");
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 2) {
|
||||
db.execSQL(Utils.fmt("UPDATE %s SET time=time*1000", LOG_TABLE));
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 3) {
|
||||
db.execSQL(Utils.fmt("CREATE TABLE IF NOT EXISTS %s (key TEXT, value TEXT, PRIMARY KEY(key))", STRINGS_TABLE));
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 4) {
|
||||
db.execSQL(Utils.fmt("UPDATE %s SET uid=uid%%100000", POLICY_TABLE));
|
||||
++oldVersion;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove everything, we do not support downgrade
|
||||
public void onDowngrade(SQLiteDatabase db) {
|
||||
MagiskManager.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);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + STRINGS_TABLE);
|
||||
onUpgrade(db, 0);
|
||||
}
|
||||
|
||||
private void createTables(SQLiteDatabase db) {
|
||||
// Policies
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + POLICY_TABLE + " " +
|
||||
"(uid INT, package_name TEXT, policy INT, " +
|
||||
"until INT, logging INT, notification INT, " +
|
||||
"PRIMARY KEY(uid))");
|
||||
|
||||
// Logs
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + LOG_TABLE + " " +
|
||||
"(from_uid INT, package_name TEXT, app_name TEXT, from_pid INT, " +
|
||||
"to_uid INT, action INT, time INT, command TEXT)");
|
||||
|
||||
// Settings
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + SETTINGS_TABLE + " " +
|
||||
"(key TEXT, value INT, PRIMARY KEY(key))");
|
||||
}
|
||||
|
||||
public void clearOutdated() {
|
||||
// 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);
|
||||
}
|
||||
|
||||
public void deletePolicy(Policy policy) {
|
||||
deletePolicy(policy.uid);
|
||||
}
|
||||
|
||||
public void deletePolicy(String pkg) {
|
||||
db.delete(POLICY_TABLE, "package_name=?", new String[] { pkg });
|
||||
}
|
||||
|
||||
public void deletePolicy(int uid) {
|
||||
db.delete(POLICY_TABLE, Utils.fmt("uid=%d", uid), null);
|
||||
}
|
||||
|
||||
public Policy getPolicy(int uid) {
|
||||
Policy policy = null;
|
||||
try (Cursor c = db.query(POLICY_TABLE, null, Utils.fmt("uid=%d", uid), null, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
policy = new Policy(c, pm);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
deletePolicy(uid);
|
||||
return null;
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
|
||||
public void addPolicy(Policy policy) {
|
||||
db.replace(POLICY_TABLE, null, policy.getContentValues());
|
||||
}
|
||||
|
||||
public void updatePolicy(Policy policy) {
|
||||
db.update(POLICY_TABLE, policy.getContentValues(), Utils.fmt("uid=%d", policy.uid), null);
|
||||
}
|
||||
|
||||
public List<Policy> getPolicyList(PackageManager pm) {
|
||||
try (Cursor c = db.query(POLICY_TABLE, null, Utils.fmt("uid/100000=%d", Const.USER_ID),
|
||||
null, null, null, null)) {
|
||||
List<Policy> ret = new ArrayList<>(c.getCount());
|
||||
while (c.moveToNext()) {
|
||||
try {
|
||||
Policy policy = new Policy(c, pm);
|
||||
ret.add(policy);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// The app no longer exist, remove from DB
|
||||
deletePolicy(c.getInt(c.getColumnIndex("uid")));
|
||||
}
|
||||
}
|
||||
Collections.sort(ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public List<List<Integer>> getLogStructure() {
|
||||
try (Cursor c = db.query(LOG_TABLE, new String[] { "time" }, Utils.fmt("from_uid/100000=%d", Const.USER_ID),
|
||||
null, null, null, "time DESC")) {
|
||||
List<List<Integer>> ret = new ArrayList<>();
|
||||
List<Integer> list = null;
|
||||
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);
|
||||
if (!TextUtils.equals(dateString, newString)) {
|
||||
dateString = newString;
|
||||
list = new ArrayList<>();
|
||||
ret.add(list);
|
||||
}
|
||||
list.add(c.getPosition());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public Cursor getLogCursor() {
|
||||
return db.query(LOG_TABLE, null, Utils.fmt("from_uid/100000=%d", Const.USER_ID),
|
||||
null, null, null, "time DESC");
|
||||
}
|
||||
|
||||
public void addLog(SuLogEntry log) {
|
||||
db.insert(LOG_TABLE, null, log.getContentValues());
|
||||
}
|
||||
|
||||
public void clearLogs() {
|
||||
db.delete(LOG_TABLE, null, null);
|
||||
}
|
||||
|
||||
public void setSettings(String key, int value) {
|
||||
ContentValues data = new ContentValues();
|
||||
data.put("key", key);
|
||||
data.put("value", value);
|
||||
db.replace(SETTINGS_TABLE, null, data);
|
||||
}
|
||||
|
||||
public int getSettings(String key, int defaultValue) {
|
||||
int value = defaultValue;
|
||||
try (Cursor c = db.query(SETTINGS_TABLE, null, "key=?",new String[] { key }, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
value = c.getInt(c.getColumnIndex("value"));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setStrings(String key, String value) {
|
||||
if (value == null) {
|
||||
db.delete(STRINGS_TABLE, "key=?", new String[] { key });
|
||||
} else {
|
||||
ContentValues data = new ContentValues();
|
||||
data.put("key", key);
|
||||
data.put("value", value);
|
||||
db.replace(STRINGS_TABLE, null, data);
|
||||
}
|
||||
}
|
||||
|
||||
public String getStrings(String key, String defaultValue) {
|
||||
String value = defaultValue;
|
||||
try (Cursor c = db.query(STRINGS_TABLE, null, "key=?",new String[] { key }, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
value = c.getString(c.getColumnIndex("value"));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
@@ -10,8 +10,8 @@ import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
@@ -26,9 +26,9 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
mm = Utils.getMagiskManager(context);
|
||||
mDb = getWritableDatabase();
|
||||
|
||||
// Clear bad repos
|
||||
// Remove outdated repos
|
||||
mDb.delete(TABLE_NAME, "minMagisk<?",
|
||||
new String[] { String.valueOf(Const.MIN_MODULE_VER) });
|
||||
new String[] { String.valueOf(Const.MIN_MODULE_VER()) });
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,7 +74,7 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
mDb.delete(TABLE_NAME, "repo_name=?", new String[] { repo.getRepoName() });
|
||||
}
|
||||
|
||||
public void removeRepo(List<String> list) {
|
||||
public void removeRepo(Iterable<String> list) {
|
||||
for (String id : list) {
|
||||
if (id == null) continue;
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||
@@ -94,6 +94,10 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Cursor getRawCursor() {
|
||||
return mDb.query(TABLE_NAME, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public Cursor getRepoCursor() {
|
||||
String orderBy = null;
|
||||
switch (mm.repoOrder) {
|
||||
@@ -103,18 +107,18 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
case Const.Value.ORDER_DATE:
|
||||
orderBy = "last_update DESC";
|
||||
}
|
||||
return mDb.query(TABLE_NAME, null, "minMagisk<=?",
|
||||
new String[] { String.valueOf(mm.magiskVersionCode) },
|
||||
return mDb.query(TABLE_NAME, null, "minMagisk<=? AND minMagisk>=?",
|
||||
new String[] { String.valueOf(mm.magiskVersionCode), String.valueOf(Const.MIN_MODULE_VER()) },
|
||||
null, null, orderBy);
|
||||
}
|
||||
|
||||
public List<String> getRepoIDList() {
|
||||
LinkedList<String> ret = new LinkedList<>();
|
||||
public Set<String> getRepoIDSet() {
|
||||
HashSet<String> set = new HashSet<>(300);
|
||||
try (Cursor c = mDb.query(TABLE_NAME, null, null, null, null, null, null)) {
|
||||
while (c.moveToNext()) {
|
||||
ret.add(c.getString(c.getColumnIndex("id")));
|
||||
set.add(c.getString(c.getColumnIndex("id")));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
return set;
|
||||
}
|
||||
}
|
@@ -9,7 +9,8 @@ import com.topjohnwu.magisk.services.OnBootIntentService;
|
||||
|
||||
public class BootReceiver extends BroadcastReceiver {
|
||||
|
||||
private void startIntentService(Context context) {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(new Intent(context, OnBootIntentService.class));
|
||||
} else {
|
||||
@@ -17,9 +18,4 @@ public class BootReceiver extends BroadcastReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
startIntentService(context);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
package com.topjohnwu.magisk.receivers;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
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 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(
|
||||
context, new PatchedInstall(),
|
||||
intent.getStringExtra(Const.Key.INTENT_SET_LINK),
|
||||
intent.getStringExtra(Const.Key.INTENT_SET_FILENAME)
|
||||
);
|
||||
}
|
||||
|
||||
private static class PatchedInstall extends ManagerInstall {
|
||||
@Override
|
||||
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";
|
||||
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)));
|
||||
});
|
||||
} else {
|
||||
super.onDownloadDone(context, uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,8 +6,8 @@ import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class PackageReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
@@ -20,12 +20,12 @@ public class PackageReceiver extends BroadcastReceiver {
|
||||
case Intent.ACTION_PACKAGE_REPLACED:
|
||||
// This will only work pre-O
|
||||
if (mm.prefs.getBoolean(Const.Key.SU_REAUTH, false)) {
|
||||
mm.suDB.deletePolicy(pkg);
|
||||
mm.mDB.deletePolicy(pkg);
|
||||
}
|
||||
break;
|
||||
case Intent.ACTION_PACKAGE_FULLY_REMOVED:
|
||||
mm.suDB.deletePolicy(pkg);
|
||||
Shell.su_raw("magiskhide --rm " + pkg);
|
||||
mm.mDB.deletePolicy(pkg);
|
||||
Shell.Async.su("magiskhide --rm " + pkg);
|
||||
break;
|
||||
}
|
||||
}
|
@@ -4,11 +4,11 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class RebootReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Shell.su_raw("/system/bin/reboot");
|
||||
Shell.Async.su("/system/bin/reboot");
|
||||
}
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
package com.topjohnwu.magisk.receivers;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
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.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;
|
||||
|
||||
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);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@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)) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(mm, "superuser")
|
||||
.setShortLabel(mm.getString(R.string.superuser))
|
||||
.setIntent(new Intent(mm, SplashActivity.class)
|
||||
.putExtra(Const.Key.OPEN_SECTION, "superuser")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(mm, R.drawable.sc_superuser))
|
||||
.setRank(0)
|
||||
.build());
|
||||
}
|
||||
if (Shell.rootAccess() && mm.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))
|
||||
.setIntent(new Intent(mm, SplashActivity.class)
|
||||
.putExtra(Const.Key.OPEN_SECTION, "magiskhide")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(mm, R.drawable.sc_magiskhide))
|
||||
.setRank(1)
|
||||
.build());
|
||||
}
|
||||
if (!mm.prefs.getBoolean(Const.Key.COREONLY, false) &&
|
||||
Shell.rootAccess() && mm.magiskVersionCode >= 0) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(mm, "modules")
|
||||
.setShortLabel(mm.getString(R.string.modules))
|
||||
.setIntent(new Intent(mm, SplashActivity.class)
|
||||
.putExtra(Const.Key.OPEN_SECTION, "modules")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(mm, R.drawable.sc_extension))
|
||||
.setRank(3)
|
||||
.build());
|
||||
shortCuts.add(new ShortcutInfo.Builder(mm, "downloads")
|
||||
.setShortLabel(mm.getString(R.string.download))
|
||||
.setIntent(new Intent(mm, SplashActivity.class)
|
||||
.putExtra(Const.Key.OPEN_SECTION, "downloads")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(mm, R.drawable.sc_cloud_download))
|
||||
.setRank(2)
|
||||
.build());
|
||||
}
|
||||
return shortCuts;
|
||||
}
|
||||
}
|
@@ -8,8 +8,7 @@ import android.support.v4.app.NotificationCompat;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
|
||||
public class OnBootIntentService extends IntentService {
|
||||
|
||||
@@ -21,12 +20,12 @@ public class OnBootIntentService extends IntentService {
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationCompat.Builder builder =
|
||||
new NotificationCompat.Builder(this, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk)
|
||||
.setContentTitle("onBoot")
|
||||
.setContentText("Running onBoot operations...");
|
||||
startForeground(Const.ID.ONBOOT_NOTIFICATION_ID, builder.build());
|
||||
startForeground(Const.ID.ONBOOT_NOTIFICATION_ID,
|
||||
new NotificationCompat.Builder(this, Const.ID.NOTIFICATION_CHANNEL)
|
||||
.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle("Startup Operations")
|
||||
.setContentText("Running startup operations...")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,11 +38,7 @@ public class OnBootIntentService extends IntentService {
|
||||
* Check for dtbo status every boot time, and prompt user
|
||||
* to reboot if dtbo wasn't patched and patched by Magisk Manager.
|
||||
* */
|
||||
MagiskManager mm = Utils.getMagiskManager(this);
|
||||
mm.loadMagiskInfo();
|
||||
mm.getDefaultInstallFlags();
|
||||
if (Shell.rootAccess()) {
|
||||
Utils.patchDTBO();
|
||||
}
|
||||
MagiskManager.get().loadMagiskInfo();
|
||||
RootUtils.patchDTBO();
|
||||
}
|
||||
}
|
@@ -68,7 +68,7 @@ public class RequestActivity extends Activity {
|
||||
|
||||
pm = getPackageManager();
|
||||
mm = Utils.getMagiskManager(this);
|
||||
mm.suDB.cleanup();
|
||||
mm.mDB.clearOutdated();
|
||||
|
||||
Intent intent = getIntent();
|
||||
socketPath = intent.getStringExtra("socket");
|
||||
@@ -233,7 +233,7 @@ public class RequestActivity extends Activity {
|
||||
policy.policy = action;
|
||||
if (time >= 0) {
|
||||
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
|
||||
mm.suDB.addPolicy(policy);
|
||||
mm.mDB.addPolicy(policy);
|
||||
}
|
||||
handleAction();
|
||||
}
|
||||
@@ -273,10 +273,14 @@ public class RequestActivity extends Activity {
|
||||
}
|
||||
|
||||
int uid = payload.getAsInteger("uid");
|
||||
policy = mm.suDB.getPolicy(uid);
|
||||
policy = mm.mDB.getPolicy(uid);
|
||||
if (policy == null) {
|
||||
policy = new Policy(uid, pm);
|
||||
}
|
||||
|
||||
/* Never allow com.topjohnwu.magisk (could be malware) */
|
||||
if (TextUtils.equals(policy.packageName, Const.ORIG_PKG_NAME))
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
@@ -43,7 +43,7 @@ public class SuReceiver extends BroadcastReceiver {
|
||||
action = intent.getStringExtra("action");
|
||||
if (action == null) return;
|
||||
|
||||
policy = mm.suDB.getPolicy(fromUid);
|
||||
policy = mm.mDB.getPolicy(fromUid);
|
||||
if (policy == null) {
|
||||
try {
|
||||
policy = new Policy(fromUid, context.getPackageManager());
|
||||
@@ -84,7 +84,7 @@ public class SuReceiver extends BroadcastReceiver {
|
||||
log.fromPid = pid;
|
||||
log.command = command;
|
||||
log.date = new Date();
|
||||
mm.suDB.addLog(log);
|
||||
mm.mDB.addLog(log);
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,12 +2,10 @@ package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.support.annotation.Keep;
|
||||
|
||||
import com.topjohnwu.crypto.SignBoot;
|
||||
import com.topjohnwu.utils.SignBoot;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
public class BootSigner {
|
||||
|
||||
@@ -15,30 +13,23 @@ public class BootSigner {
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length > 0 && "-verify".equals(args[0])) {
|
||||
String certPath = "";
|
||||
if (args.length >= 3 && "-certificate".equals(args[1])) {
|
||||
/* args[2] is the path to a public key certificate */
|
||||
certPath = args[2];
|
||||
if (args.length >= 2) {
|
||||
/* args[1] is the path to a public key certificate */
|
||||
certPath = args[1];
|
||||
}
|
||||
/* args[1] is the path to a signed boot image */
|
||||
boolean signed = SignBoot.verifySignature(System.in,
|
||||
certPath.isEmpty() ? null : new FileInputStream(certPath));
|
||||
System.exit(signed ? 0 : 1);
|
||||
} else if (args.length > 0 && "-sign".equals(args[0])) {
|
||||
InputStream keyIn, certIn;
|
||||
if (args.length >= 3) {
|
||||
keyIn = new FileInputStream(args[1]);
|
||||
certIn = new FileInputStream(args[2]);
|
||||
} else {
|
||||
/* Use internal test keys */
|
||||
JarFile apk = new JarFile(System.getProperty("java.class.path"));
|
||||
JarEntry keyEntry = apk.getJarEntry("assets/" + Const.PRIVATE_KEY_NAME);
|
||||
JarEntry sigEntry = apk.getJarEntry("assets/" + Const.PUBLIC_KEY_NAME);
|
||||
InputStream cert = null;
|
||||
InputStream key = null;
|
||||
|
||||
keyIn = apk.getInputStream(keyEntry);
|
||||
certIn = apk.getInputStream(sigEntry);
|
||||
if (args.length >= 3) {
|
||||
cert = new FileInputStream(args[1]);
|
||||
key = new FileInputStream(args[2]);
|
||||
}
|
||||
|
||||
boolean success = SignBoot.doSignature("/boot", System.in, System.out, keyIn, certIn);
|
||||
boolean success = SignBoot.doSignature("/boot", System.in, System.out, cert, key);
|
||||
System.exit(success ? 0 : 1);
|
||||
} else {
|
||||
System.err.println(
|
||||
@@ -48,8 +39,8 @@ public class BootSigner {
|
||||
"Actions:\n" +
|
||||
" -verify [x509.pem]\n" +
|
||||
" verify image, cert is optional\n" +
|
||||
" -sign [pk8] [x509.pem]\n" +
|
||||
" sign image, key and cert are optional\n"
|
||||
" -sign [x509.pem] [pk8]\n" +
|
||||
" sign image, cert and key pair is optional\n"
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
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 BASIC_PASS = 0x10;
|
||||
int CTS_PASS = 0x20;
|
||||
|
||||
void attest();
|
||||
int getVersion();
|
||||
|
||||
interface Callback {
|
||||
@Keep
|
||||
void onResponse(int responseCode);
|
||||
}
|
||||
}
|
27
app/src/full/java/com/topjohnwu/magisk/utils/Logger.java
Normal file
27
app/src/full/java/com/topjohnwu/magisk/utils/Logger.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class Logger {
|
||||
|
||||
public static void debug(String line) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(Const.DEBUG_TAG, "DEBUG: " + line);
|
||||
}
|
||||
|
||||
public static void debug(String fmt, Object... args) {
|
||||
debug(Utils.fmt(fmt, args));
|
||||
}
|
||||
|
||||
public static void error(String line) {
|
||||
Log.e(Const.DEBUG_TAG, "ERROR: " + line);
|
||||
}
|
||||
|
||||
public static void error(String fmt, Object... args) {
|
||||
error(Utils.fmt(fmt, args));
|
||||
}
|
||||
}
|
77
app/src/full/java/com/topjohnwu/magisk/utils/PatchAPK.java
Normal file
77
app/src/full/java/com/topjohnwu/magisk/utils/PatchAPK.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import com.topjohnwu.utils.JarMap;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
public class PatchAPK {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public static boolean patchPackageID(String fileName, OutputStream out, String from, String to) {
|
||||
try {
|
||||
JarMap apk = new JarMap(fileName);
|
||||
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);
|
||||
|
||||
// Sign the APK
|
||||
ZipUtils.signZip(apk, out);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
38
app/src/full/java/com/topjohnwu/magisk/utils/RootUtils.java
Normal file
38
app/src/full/java/com/topjohnwu/magisk/utils/RootUtils.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
public class RootUtils {
|
||||
|
||||
public static void init() {
|
||||
if (Shell.rootAccess()) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
public static void uninstallPkg(String pkg) {
|
||||
Shell.Sync.su("db_clean " + Const.USER_ID, "pm uninstall " + pkg);
|
||||
}
|
||||
|
||||
public static void patchDTBO() {
|
||||
if (Shell.rootAccess()) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
if (mm.magiskVersionCode >= Const.MAGISK_VER.DTBO_SUPPORT) {
|
||||
if (Boolean.parseBoolean(ShellUtils.fastCmd("mm_patch_dtbo")))
|
||||
ShowUI.dtboPatchedNotification();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.superuser.BusyBox;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class ShellInitializer extends Shell.Initializer {
|
||||
|
||||
static {
|
||||
BusyBox.BB_PATH = new File(Const.BUSYBOX_PATH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onRootShellInit(Context context, @NonNull Shell shell) throws Exception {
|
||||
try (InputStream magiskUtils = context.getResources().openRawResource(R.raw.util_functions);
|
||||
InputStream managerUtils = context.getResources().openRawResource(R.raw.utils)
|
||||
) {
|
||||
shell.execTask((in, err, out) -> {
|
||||
ShellUtils.pump(magiskUtils, in);
|
||||
ShellUtils.pump(managerUtils, in);
|
||||
});
|
||||
}
|
||||
shell.run(null, null,
|
||||
"mount_partitions",
|
||||
"get_flags",
|
||||
"run_migrations");
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -7,25 +7,26 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
import com.topjohnwu.magisk.asyncs.InstallMagisk;
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.asyncs.RestoreImages;
|
||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
import com.topjohnwu.magisk.receivers.RebootReceiver;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -43,7 +44,7 @@ public class ShowUI {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk)
|
||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle(mm.getString(R.string.magisk_update_title))
|
||||
.setContentText(mm.getString(R.string.magisk_update_available, mm.remoteMagiskVersionString))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
@@ -57,15 +58,17 @@ public class ShowUI {
|
||||
|
||||
public static void managerUpdateNotification() {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
String filename = Utils.fmt("MagiskManager-v%s(%d).apk",
|
||||
mm.remoteManagerVersionString, mm.remoteManagerVersionCode);
|
||||
|
||||
Intent intent = new Intent(mm, ManagerUpdate.class);
|
||||
intent.putExtra(Const.Key.INTENT_SET_LINK, mm.managerLink);
|
||||
intent.putExtra(Const.Key.INTENT_SET_VERSION, mm.remoteManagerVersionString);
|
||||
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)
|
||||
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})
|
||||
@@ -85,7 +88,7 @@ public class ShowUI {
|
||||
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)
|
||||
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})
|
||||
@@ -96,10 +99,31 @@ public class ShowUI {
|
||||
notificationManager.notify(Const.ID.DTBO_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void envFixDialog(Activity activity) {
|
||||
MagiskManager mm = Utils.getMagiskManager(activity);
|
||||
String filename = Utils.fmt("Magisk-v%s(%d).zip",
|
||||
mm.remoteMagiskVersionString, mm.remoteMagiskVersionCode);
|
||||
new AlertDialogBuilder(activity)
|
||||
.setTitle(R.string.env_fix_title)
|
||||
.setMessage(R.string.env_fix_msg)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.yes, (d, i) -> {
|
||||
Utils.dlAndReceive(activity, new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
new InstallMagisk(activity, uri).exec();
|
||||
}
|
||||
}, mm.magiskLink, filename);
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
public static void magiskInstallDialog(Activity activity) {
|
||||
MagiskManager mm = Utils.getMagiskManager(activity);
|
||||
String filename = Utils.fmt("Magisk-v%s.zip", mm.remoteMagiskVersionString);
|
||||
new AlertDialogBuilder(activity)
|
||||
String filename = Utils.fmt("Magisk-v%s(%d).zip",
|
||||
mm.remoteMagiskVersionString, mm.remoteMagiskVersionCode);
|
||||
AlertDialog.Builder b = new AlertDialogBuilder(activity)
|
||||
.setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.magisk)))
|
||||
.setMessage(mm.getString(R.string.repo_install_msg, filename))
|
||||
.setCancelable(true)
|
||||
@@ -110,17 +134,11 @@ public class ShowUI {
|
||||
if (Shell.rootAccess()) {
|
||||
options.add(mm.getString(R.string.direct_install));
|
||||
}
|
||||
List<String> res = Shell.su("echo $SLOT");
|
||||
if (Utils.isValidShellResponse(res)) {
|
||||
options.add(mm.getString(R.string.install_second_slot));
|
||||
}
|
||||
char[] slot = Utils.isValidShellResponse(res) ? res.get(0).toCharArray() : null;
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.select_method)
|
||||
.setItems(
|
||||
options.toArray(new String [0]),
|
||||
(dialog, idx) -> {
|
||||
String boot;
|
||||
DownloadReceiver receiver = null;
|
||||
switch (idx) {
|
||||
case 1:
|
||||
@@ -140,7 +158,7 @@ public class ShowUI {
|
||||
activity,
|
||||
new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Uri uri) {
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
Intent intent = new Intent(mm, FlashActivity.class);
|
||||
intent.setData(uri)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
@@ -158,128 +176,98 @@ public class ShowUI {
|
||||
case 0:
|
||||
receiver = new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Uri uri) {
|
||||
Utils.showUriSnack(activity, uri);
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
SnackbarMaker.showUri(activity, uri);
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 2:
|
||||
boot = mm.bootBlock;
|
||||
if (boot == null)
|
||||
return;
|
||||
receiver = new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Uri uri) {
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
Intent intent = new Intent(mm, FlashActivity.class);
|
||||
intent.setData(uri)
|
||||
.putExtra(Const.Key.FLASH_SET_BOOT, boot)
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_MAGISK);
|
||||
intent.setData(uri).putExtra(Const.Key.FLASH_ACTION,
|
||||
Const.Value.FLASH_MAGISK);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 3:
|
||||
assert (slot != null);
|
||||
// Choose the other slot
|
||||
if (slot[1] == 'a') slot[1] = 'b';
|
||||
else slot[1] = 'a';
|
||||
// Then find the boot image again
|
||||
List<String> ret = Shell.su(
|
||||
"SLOT=" + String.valueOf(slot),
|
||||
"find_boot_image",
|
||||
"echo \"$BOOTIMAGE\""
|
||||
);
|
||||
boot = Utils.isValidShellResponse(ret) ? ret.get(ret.size() - 1) : null;
|
||||
Shell.su_raw("mount_partitions");
|
||||
if (boot == null)
|
||||
return;
|
||||
receiver = new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Uri uri) {
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
Intent intent = new Intent(mm, FlashActivity.class);
|
||||
intent.setData(uri)
|
||||
.putExtra(Const.Key.FLASH_SET_BOOT, boot)
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_MAGISK);
|
||||
intent.setData(uri).putExtra(Const.Key.FLASH_ACTION,
|
||||
Const.Value.FLASH_SECOND_SLOT);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
};
|
||||
default:
|
||||
}
|
||||
Utils.dlAndReceive(
|
||||
activity,
|
||||
receiver,
|
||||
mm.magiskLink,
|
||||
filename
|
||||
);
|
||||
Utils.dlAndReceive(activity, receiver, mm.magiskLink, filename);
|
||||
}
|
||||
).show();
|
||||
})
|
||||
.setNeutralButton(R.string.release_notes, (d, i) -> {
|
||||
if (mm.releaseNoteLink != null) {
|
||||
Intent openLink = new Intent(Intent.ACTION_VIEW, Uri.parse(mm.releaseNoteLink));
|
||||
.setNegativeButton(R.string.no_thanks, null);
|
||||
if (!TextUtils.isEmpty(mm.magiskNoteLink)) {
|
||||
b.setNeutralButton(R.string.release_notes, (d, i) -> {
|
||||
if (mm.magiskNoteLink.contains("forum.xda-developers")) {
|
||||
// Open forum links in browser
|
||||
Intent openLink = new Intent(Intent.ACTION_VIEW, Uri.parse(mm.magiskNoteLink));
|
||||
openLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
mm.startActivity(openLink);
|
||||
} else {
|
||||
new MarkDownWindow(activity, null, mm.magiskNoteLink).exec();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
b.show();
|
||||
}
|
||||
|
||||
public static void managerInstallDialog(Activity activity) {
|
||||
MagiskManager mm = Utils.getMagiskManager(activity);
|
||||
new AlertDialogBuilder(activity)
|
||||
String filename = Utils.fmt("MagiskManager-v%s(%d).apk",
|
||||
mm.remoteManagerVersionString, mm.remoteManagerVersionCode);
|
||||
AlertDialog.Builder b = new AlertDialogBuilder(activity)
|
||||
.setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.app_name)))
|
||||
.setMessage(mm.getString(R.string.repo_install_msg,
|
||||
Utils.fmt("MagiskManager-v%s.apk", mm.remoteManagerVersionString)))
|
||||
.setMessage(mm.getString(R.string.repo_install_msg, filename))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.install, (d, i) -> {
|
||||
Utils.runWithPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> {
|
||||
com.topjohnwu.magisk.components.Activity.runWithPermission(activity,
|
||||
new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, () -> {
|
||||
Intent intent = new Intent(mm, ManagerUpdate.class);
|
||||
intent.putExtra(Const.Key.INTENT_SET_LINK, mm.managerLink);
|
||||
intent.putExtra(Const.Key.INTENT_SET_VERSION, mm.remoteManagerVersionString);
|
||||
intent.putExtra(Const.Key.INTENT_SET_FILENAME, filename);
|
||||
mm.sendBroadcast(intent);
|
||||
});
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
.setNegativeButton(R.string.no_thanks, null);
|
||||
if (!TextUtils.isEmpty(mm.managerNoteLink)) {
|
||||
b.setNeutralButton(R.string.app_changelog, (d, i) ->
|
||||
new MarkDownWindow(activity, null, mm.managerNoteLink).exec());
|
||||
}
|
||||
b.show();
|
||||
}
|
||||
|
||||
public static void uninstallDialog(Activity activity) {
|
||||
MagiskManager mm = Utils.getMagiskManager(activity);
|
||||
new AlertDialogBuilder(activity)
|
||||
AlertDialog.Builder b = new AlertDialogBuilder(activity)
|
||||
.setTitle(R.string.uninstall_magisk_title)
|
||||
.setMessage(R.string.uninstall_magisk_msg)
|
||||
.setPositiveButton(R.string.complete_uninstall, (d, i) -> {
|
||||
ByteArrayOutputStream uninstaller = new ByteArrayOutputStream();
|
||||
try (InputStream in = mm.getAssets().open(Const.UNINSTALLER)) {
|
||||
Utils.inToOut(in, uninstaller);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
ByteArrayOutputStream utils = new ByteArrayOutputStream();
|
||||
try (InputStream in = mm.getAssets().open(Const.UTIL_FUNCTIONS)) {
|
||||
Utils.inToOut(in, utils);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Shell.su(
|
||||
Utils.fmt("echo '%s' > /cache/%s", uninstaller.toString().replace("'", "'\\''"), Const.UNINSTALLER),
|
||||
Utils.fmt("echo '%s' > %s/%s", utils.toString().replace("'", "'\\''"),
|
||||
mm.magiskVersionCode >= 1464 ? "/data/adb/magisk" : "/data/magisk", Const.UTIL_FUNCTIONS)
|
||||
);
|
||||
try {
|
||||
uninstaller.close();
|
||||
utils.close();
|
||||
} catch (IOException ignored) {}
|
||||
|
||||
MagiskManager.toast(R.string.uninstall_toast, Toast.LENGTH_LONG);
|
||||
new Handler().postDelayed(() -> Utils.uninstallPkg(mm.getPackageName()), 5000);
|
||||
})
|
||||
.setNeutralButton(R.string.restore_img, (d, i) -> new RestoreImages().exec())
|
||||
.setNegativeButton(R.string.uninstall_app, (d, i) -> Utils.uninstallPkg(mm.getPackageName()))
|
||||
.show();
|
||||
.setNeutralButton(R.string.restore_img, (d, i) -> new RestoreImages(activity).exec());
|
||||
if (!TextUtils.isEmpty(mm.uninstallerLink)) {
|
||||
b.setPositiveButton(R.string.complete_uninstall, (d, i) ->
|
||||
Utils.dlAndReceive(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);
|
||||
}
|
||||
}, mm.uninstallerLink, "magisk-uninstaller.zip"));
|
||||
}
|
||||
b.show();
|
||||
}
|
||||
}
|
99
app/src/full/java/com/topjohnwu/magisk/utils/Topic.java
Normal file
99
app/src/full/java/com/topjohnwu/magisk/utils/Topic.java
Normal file
@@ -0,0 +1,99 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Topic {
|
||||
|
||||
private static final int NON_INIT = 0;
|
||||
private static final int PENDING = 1;
|
||||
private static final int PUBLISHED = 2;
|
||||
|
||||
private int state = NON_INIT;
|
||||
private List<WeakReference<Subscriber>> subscribers;
|
||||
private Object[] results;
|
||||
|
||||
public Topic() {
|
||||
subscribers = new SyncArrayList<>();
|
||||
}
|
||||
|
||||
public synchronized void subscribe(Subscriber sub) {
|
||||
subscribers.add(new WeakReference<>(sub));
|
||||
}
|
||||
|
||||
public synchronized void unsubscribe() {
|
||||
subscribers = new SyncArrayList<>();
|
||||
}
|
||||
|
||||
public synchronized void unsubscribe(Subscriber sub) {
|
||||
List<WeakReference<Subscriber>> subs = subscribers;
|
||||
subscribers = new ArrayList<>();
|
||||
for (WeakReference<Subscriber> subscriber : subs) {
|
||||
if (subscriber.get() != null && subscriber.get() != sub)
|
||||
subscribers.add(subscriber);
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
state = NON_INIT;
|
||||
results = null;
|
||||
}
|
||||
|
||||
public boolean isPublished() {
|
||||
return state == PUBLISHED;
|
||||
}
|
||||
|
||||
public void publish() {
|
||||
publish(true);
|
||||
}
|
||||
|
||||
public void publish(boolean record, Object... results) {
|
||||
if (record)
|
||||
state = PUBLISHED;
|
||||
this.results = results;
|
||||
// Snapshot
|
||||
List<WeakReference<Subscriber>> subs = subscribers;
|
||||
for (WeakReference<Subscriber> subscriber : subs) {
|
||||
if (subscriber != null && subscriber.get() != null)
|
||||
subscriber.get().onTopicPublished(this);
|
||||
}
|
||||
}
|
||||
|
||||
public Object[] getResults() {
|
||||
return results;
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return state == PENDING;
|
||||
}
|
||||
|
||||
public void setPending() {
|
||||
state = PENDING;
|
||||
}
|
||||
|
||||
public interface Subscriber {
|
||||
default void subscribeTopics() {
|
||||
for (Topic topic : getSubscription()) {
|
||||
if (topic.isPublished()) {
|
||||
onTopicPublished(topic);
|
||||
}
|
||||
topic.subscribe(this);
|
||||
}
|
||||
}
|
||||
default void unsubscribeTopics() {
|
||||
for (Topic event : getSubscription()) {
|
||||
event.unsubscribe(this);
|
||||
}
|
||||
}
|
||||
void onTopicPublished(Topic topic);
|
||||
Topic[] getSubscription();
|
||||
}
|
||||
|
||||
private static class SyncArrayList<E> extends ArrayList<E> {
|
||||
@Override
|
||||
public synchronized boolean add(E e) {
|
||||
return super.add(e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,34 +1,31 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
import com.topjohnwu.crypto.JarMap;
|
||||
import com.topjohnwu.crypto.SignAPK;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
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.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class ZipUtils {
|
||||
|
||||
static {
|
||||
System.loadLibrary("zipadjust");
|
||||
}
|
||||
|
||||
public native static void zipAdjust(String filenameIn, String filenameOut);
|
||||
|
||||
public static void unzip(File zip, File folder, String path, boolean junkPath) throws Exception {
|
||||
public static void unzip(File zip, File folder, String path, boolean junkPath) throws IOException {
|
||||
InputStream in = new BufferedInputStream(new FileInputStream(zip));
|
||||
unzip(in, folder, path, junkPath);
|
||||
in.close();
|
||||
}
|
||||
|
||||
public static void unzip(InputStream zip, File folder, String path, boolean junkPath) throws Exception {
|
||||
public static void unzip(InputStream zip, File folder, String path, boolean junkPath) throws IOException {
|
||||
try {
|
||||
ZipInputStream zipfile = new ZipInputStream(zip);
|
||||
ZipEntry entry;
|
||||
@@ -44,29 +41,29 @@ public class ZipUtils {
|
||||
name = entry.getName();
|
||||
}
|
||||
File dest = new File(folder, name);
|
||||
dest.getParentFile().mkdirs();
|
||||
try (FileOutputStream out = new FileOutputStream(dest)) {
|
||||
Utils.inToOut(zipfile, out);
|
||||
if (!dest.getParentFile().exists() && !dest.getParentFile().mkdirs()) {
|
||||
dest = new SuFile(folder, name);
|
||||
dest.getParentFile().mkdirs();
|
||||
}
|
||||
try (OutputStream out = new SuFileOutputStream(dest)) {
|
||||
ShellUtils.pump(zipfile, out);
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
}
|
||||
catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static void signZip(InputStream is, File output, boolean minSign) throws Exception {
|
||||
signZip(new JarMap(is, false), output, minSign);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public static void signZip(File input, File output, boolean minSign) throws Exception {
|
||||
signZip(new JarMap(input, false), output, minSign);
|
||||
public static void signZip(JarMap input, OutputStream output) throws Exception {
|
||||
SignAPK.signZip(null, null, input, output);
|
||||
}
|
||||
|
||||
public static void signZip(JarMap input, File output, boolean minSign) throws Exception {
|
||||
AssetManager assets = MagiskManager.get().getAssets();
|
||||
SignAPK.signZip(
|
||||
assets.open(Const.PUBLIC_KEY_NAME), assets.open(Const.PRIVATE_KEY_NAME),
|
||||
input, output, minSign);
|
||||
}
|
||||
}
|
||||
}
|
BIN
app/src/full/res/drawable-nodpi/logo.png
Normal file
BIN
app/src/full/res/drawable-nodpi/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
9
app/src/full/res/drawable-v26/sc_cloud_download.xml
Normal file
9
app/src/full/res/drawable-v26/sc_cloud_download.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/su_request_background" />
|
||||
<foreground>
|
||||
<inset
|
||||
android:drawable="@drawable/ic_cloud_download"
|
||||
android:inset="30%" />
|
||||
</foreground>
|
||||
</adaptive-icon>
|
9
app/src/full/res/drawable-v26/sc_extension.xml
Normal file
9
app/src/full/res/drawable-v26/sc_extension.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/su_request_background" />
|
||||
<foreground>
|
||||
<inset
|
||||
android:drawable="@drawable/ic_extension"
|
||||
android:inset="30%" />
|
||||
</foreground>
|
||||
</adaptive-icon>
|
9
app/src/full/res/drawable-v26/sc_magiskhide.xml
Normal file
9
app/src/full/res/drawable-v26/sc_magiskhide.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/su_request_background" />
|
||||
<foreground>
|
||||
<inset
|
||||
android:drawable="@drawable/ic_magiskhide"
|
||||
android:inset="30%" />
|
||||
</foreground>
|
||||
</adaptive-icon>
|
9
app/src/full/res/drawable-v26/sc_superuser.xml
Normal file
9
app/src/full/res/drawable-v26/sc_superuser.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/su_request_background" />
|
||||
<foreground>
|
||||
<inset
|
||||
android:drawable="@drawable/ic_superuser"
|
||||
android:inset="30%" />
|
||||
</foreground>
|
||||
</adaptive-icon>
|
@@ -4,6 +4,6 @@
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:fillColor="@color/primary_dark"
|
||||
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z"/>
|
||||
</vector>
|
@@ -4,6 +4,6 @@
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:fillColor="@color/primary_dark"
|
||||
android:pathData="M20.5,11H19V7c0,-1.1 -0.9,-2 -2,-2h-4V3.5C13,2.12 11.88,1 10.5,1S8,2.12 8,3.5V5H4c-1.1,0 -1.99,0.9 -1.99,2v3.8H3.5c1.49,0 2.7,1.21 2.7,2.7s-1.21,2.7 -2.7,2.7H2V20c0,1.1 0.9,2 2,2h3.8v-1.5c0,-1.49 1.21,-2.7 2.7,-2.7 1.49,0 2.7,1.21 2.7,2.7V22H17c1.1,0 2,-0.9 2,-2v-4h1.5c1.38,0 2.5,-1.12 2.5,-2.5S21.88,11 20.5,11z"/>
|
||||
</vector>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user