mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-06 04:41:23 -08:00
Compare commits
1705 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfe32f1a70 | ||
|
|
d877f5d5c6 | ||
|
|
0ab6ffefb4 | ||
|
|
a292a1d23a | ||
|
|
3f87f6aee3 | ||
|
|
04bcd145d3 | ||
|
|
244e811291 | ||
|
|
ac7467fb59 | ||
|
|
2c0436216f | ||
|
|
017fbf267b | ||
|
|
e6afbf2ec0 | ||
|
|
906b4aad9e | ||
|
|
4cf8d41f6a | ||
|
|
47c860142e | ||
|
|
2fba3f213b | ||
|
|
af7c6f9fce | ||
|
|
78534deab6 | ||
|
|
6710314832 | ||
|
|
0cd4fa6fa0 | ||
|
|
065949496e | ||
|
|
39c82576ae | ||
|
|
37221a508d | ||
|
|
6b43a32a10 | ||
|
|
d7cd1ff142 | ||
|
|
659d947863 | ||
|
|
39be7a6288 | ||
|
|
8ac976c579 | ||
|
|
70fd432c57 | ||
|
|
00135f2f49 | ||
|
|
9b944bc29c | ||
|
|
d520b3d2a0 | ||
|
|
6f41d9855b | ||
|
|
2d7c1da741 | ||
|
|
c0f45b6b1e | ||
|
|
7a0025673c | ||
|
|
ad7ec79903 | ||
|
|
0543239cca | ||
|
|
ff3dad2457 | ||
|
|
298d5e197b | ||
|
|
d73c0a998d | ||
|
|
1b79a3ddbf | ||
|
|
a8478ace18 | ||
|
|
72cf5f3f9f | ||
|
|
6f9d493a18 | ||
|
|
08f7d5ebff | ||
|
|
1fe3675403 | ||
|
|
a0f956d2c1 | ||
|
|
1560f91b4a | ||
|
|
c20f362594 | ||
|
|
7ae8c26e50 | ||
|
|
adfffe6121 | ||
|
|
64601baa76 | ||
|
|
aa374b51f1 | ||
|
|
5c483745ff | ||
|
|
0c247110a0 | ||
|
|
1643638a78 | ||
|
|
4ace228fc2 | ||
|
|
25aa86a0dc | ||
|
|
70d3b24338 | ||
|
|
8664e9d19b | ||
|
|
50d9877446 | ||
|
|
fe06352089 | ||
|
|
7b599419b5 | ||
|
|
491adf072e | ||
|
|
f6aae2b048 | ||
|
|
d2d5c94633 | ||
|
|
10581f9ef2 | ||
|
|
c7e0e1c038 | ||
|
|
a914d701eb | ||
|
|
0f9dee6e9c | ||
|
|
aa383e2190 | ||
|
|
9bbfcf326c | ||
|
|
3948e67c8f | ||
|
|
d56e1b2cc5 | ||
|
|
bfac1f1bc2 | ||
|
|
d4a956c355 | ||
|
|
6c71fefa58 | ||
|
|
ad3003c00a | ||
|
|
0ad5dcb258 | ||
|
|
d790309b02 | ||
|
|
1072faf309 | ||
|
|
d2c196896d | ||
|
|
e42b608444 | ||
|
|
89a501a3af | ||
|
|
c19b78180c | ||
|
|
c0b750a09a | ||
|
|
c967e618a1 | ||
|
|
59f78d7dfc | ||
|
|
d8405f0d05 | ||
|
|
0f34f0033c | ||
|
|
190646d50c | ||
|
|
a46c6252c6 | ||
|
|
5c1886c8f5 | ||
|
|
afcb3d8f34 | ||
|
|
9fbffafdbf | ||
|
|
075f0458f7 | ||
|
|
d4568aa0a7 | ||
|
|
97588408a2 | ||
|
|
1def9b301b | ||
|
|
5bac442b18 | ||
|
|
6add682705 | ||
|
|
8b50d84a05 | ||
|
|
d3858b81e2 | ||
|
|
bdff9769be | ||
|
|
c61df75e5e | ||
|
|
a74bf2cc27 | ||
|
|
ada0f93686 | ||
|
|
ff36f2ba17 | ||
|
|
5164cfd399 | ||
|
|
5fa021503e | ||
|
|
7b5d79d313 | ||
|
|
3e3f38500d | ||
|
|
5109b9abfd | ||
|
|
7fb4777c1c | ||
|
|
c38533e0f8 | ||
|
|
51ba99d09e | ||
|
|
9159f86a9e | ||
|
|
e139f4fc13 | ||
|
|
2fbfeacb87 | ||
|
|
ebb7a9fcda | ||
|
|
9e72317302 | ||
|
|
d764c20c08 | ||
|
|
9c17b8a098 | ||
|
|
3084873154 | ||
|
|
32809e56d0 | ||
|
|
9f05b182a2 | ||
|
|
525484e834 | ||
|
|
65a4e69cae | ||
|
|
e973f8bab9 | ||
|
|
92466671ff | ||
|
|
6d61106070 | ||
|
|
ac13749fb8 | ||
|
|
7ec1a9a316 | ||
|
|
cf17e21ad3 | ||
|
|
0e0240c4ab | ||
|
|
d1b290b91a | ||
|
|
a63696836c | ||
|
|
46aad00f16 | ||
|
|
252afe8932 | ||
|
|
9dd467a613 | ||
|
|
4c14df67cc | ||
|
|
20e0fe3ba1 | ||
|
|
6a005135f2 | ||
|
|
82e8375957 | ||
|
|
bb25edc09e | ||
|
|
169c0fe4af | ||
|
|
cd6918e6eb | ||
|
|
5be035fd44 | ||
|
|
f1edc8443c | ||
|
|
d9564bd04c | ||
|
|
35f1c396f2 | ||
|
|
6acb950990 | ||
|
|
27e0d1641a | ||
|
|
9ac71ff8af | ||
|
|
075737a4ec | ||
|
|
6d0e4a6a5e | ||
|
|
a2544768a0 | ||
|
|
8574a14ed2 | ||
|
|
e90c555c18 | ||
|
|
863b9a410f | ||
|
|
23c7bbc7d5 | ||
|
|
f900189f90 | ||
|
|
7c74be2790 | ||
|
|
70dd2d4829 | ||
|
|
914b7ee056 | ||
|
|
e39f83edbf | ||
|
|
52fe0c6abb | ||
|
|
5cb3e5937f | ||
|
|
e0cd224831 | ||
|
|
de225ac64a | ||
|
|
5807808a10 | ||
|
|
362877d18f | ||
|
|
88b8dd0149 | ||
|
|
1552f32e09 | ||
|
|
50b73a6720 | ||
|
|
53e51f1735 | ||
|
|
40b63bfebe | ||
|
|
89861eceef | ||
|
|
b8eaff66fa | ||
|
|
a747fdd27d | ||
|
|
27851bdefa | ||
|
|
3fdeb40ddf | ||
|
|
546c7cebd3 | ||
|
|
473902f5f4 | ||
|
|
41c0721159 | ||
|
|
413d4badfd | ||
|
|
c5d67ebf72 | ||
|
|
91818cfa1a | ||
|
|
6263d684d9 | ||
|
|
07140d33a7 | ||
|
|
4ffc388491 | ||
|
|
0ef026c610 | ||
|
|
153c7fdf20 | ||
|
|
90379eeb35 | ||
|
|
3ae959af95 | ||
|
|
c8cc652b71 | ||
|
|
4b6285e5c2 | ||
|
|
013de7b3ef | ||
|
|
e11e88a9c5 | ||
|
|
7cec8baa55 | ||
|
|
e987db9fb5 | ||
|
|
c603b9084f | ||
|
|
492d6dfcf0 | ||
|
|
a3e0f2dcc3 | ||
|
|
cf211e26f4 | ||
|
|
c5aaaa7c55 | ||
|
|
f86d077e27 | ||
|
|
f8076825cb | ||
|
|
201d8a97d4 | ||
|
|
d08f326990 | ||
|
|
8dc9d3bc78 | ||
|
|
adf95ce3a0 | ||
|
|
3c1aca114f | ||
|
|
18d0fd9d2a | ||
|
|
c2e673f978 | ||
|
|
2bde8a1975 | ||
|
|
bf9927c7dd | ||
|
|
f339a087a2 | ||
|
|
6ccc5f3788 | ||
|
|
1affb91f17 | ||
|
|
7779c3e372 | ||
|
|
49ba7ad22e | ||
|
|
6ad33d60f7 | ||
|
|
0117274061 | ||
|
|
e50192a407 | ||
|
|
c6fc0e587e | ||
|
|
68c448bc34 | ||
|
|
ef62272df7 | ||
|
|
375cd0e42b | ||
|
|
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 | ||
|
|
759e905c3c | ||
|
|
8bf7e42913 | ||
|
|
0dcd073554 | ||
|
|
2fe35d578d | ||
|
|
8d139e156e | ||
|
|
7c2849356a | ||
|
|
0025ffd1c0 | ||
|
|
2ef7146642 | ||
|
|
10bd25be52 | ||
|
|
1b27e69e40 | ||
|
|
65511845d2 | ||
|
|
1c743839ea | ||
|
|
bcae9dec41 | ||
|
|
482c9af41f | ||
|
|
8e7b757efd | ||
|
|
1ab543cea1 | ||
|
|
a3f86903e4 | ||
|
|
c239c305ab | ||
|
|
2bf2e7461f | ||
|
|
7d1082b1cb | ||
|
|
0dbae83aec | ||
|
|
f927c1b997 | ||
|
|
89ec7dad2b | ||
|
|
4fd61345af | ||
|
|
2e02af994e | ||
|
|
66cca24453 | ||
|
|
2f4062a923 | ||
|
|
e733484fab | ||
|
|
e5c3183025 | ||
|
|
836d9afe17 | ||
|
|
007a352742 | ||
|
|
e526e5659e | ||
|
|
4a5227c7bf | ||
|
|
c2c151ec4c | ||
|
|
452096e7e4 | ||
|
|
50c2a9859e | ||
|
|
677b667307 | ||
|
|
930c82316a | ||
|
|
3dc22db265 | ||
|
|
d8c51cb286 | ||
|
|
2f79d0c3b3 | ||
|
|
5c338cd0a7 | ||
|
|
d8bb3af06b | ||
|
|
e139e8777b | ||
|
|
1adf331268 | ||
|
|
349b3e961b | ||
|
|
96650c06f0 | ||
|
|
26038a0a07 | ||
|
|
6a148b5dd9 | ||
|
|
0e109ef979 | ||
|
|
d52d7cfbd9 | ||
|
|
4f74a259e3 | ||
|
|
de2285d5e9 | ||
|
|
b2483ba437 | ||
|
|
a82a5e5a49 | ||
|
|
74da6e1dc0 | ||
|
|
84ffdf0ed5 | ||
|
|
022b18c8ce | ||
|
|
b92b1dcddb | ||
|
|
d161a02e71 | ||
|
|
d2b6a700b1 | ||
|
|
af203cef24 | ||
|
|
673e917e76 | ||
|
|
a3bd41db54 | ||
|
|
1472dbb291 | ||
|
|
d58a8dc868 | ||
|
|
0d9527921a | ||
|
|
e94be0b70e | ||
|
|
f0e4aec0af | ||
|
|
b0d65b5edd | ||
|
|
75532ef591 | ||
|
|
9a6d1bd700 | ||
|
|
a7ed6c15d3 | ||
|
|
5ee49ba065 | ||
|
|
f6ae7e1bf1 | ||
|
|
190d857949 | ||
|
|
f7b4935677 | ||
|
|
a3c49de6a5 | ||
|
|
d34bd47bea | ||
|
|
f17792380b | ||
|
|
c11920110e | ||
|
|
ec5a993fea | ||
|
|
d250c2cc89 | ||
|
|
767e73f40c | ||
|
|
3f699c9d2f | ||
|
|
50dbd9befd | ||
|
|
760e01bf92 | ||
|
|
543f435b1e | ||
|
|
e8dd1b292f | ||
|
|
91337218b3 | ||
|
|
afff3c0a49 | ||
|
|
d21264d01b | ||
|
|
b0567eadfd | ||
|
|
5fc2058336 | ||
|
|
d0567d29d2 | ||
|
|
2b6c271d37 | ||
|
|
b0c1a6f73a | ||
|
|
4db0ad32f0 | ||
|
|
a1871e4bc3 | ||
|
|
d065040321 | ||
|
|
3aa0294cd4 | ||
|
|
17f0fea3fc | ||
|
|
8ca1e43533 | ||
|
|
310b266251 | ||
|
|
21b1b5098e | ||
|
|
a3a4a5d8a5 | ||
|
|
270536f33c | ||
|
|
66bb433cc6 | ||
|
|
bd4ef1a03a | ||
|
|
aa2d9a3bf1 | ||
|
|
bd01c314dc | ||
|
|
257308d5db | ||
|
|
d4620e1654 | ||
|
|
fd6cbb138c | ||
|
|
aa75c8e5e4 | ||
|
|
e404476609 | ||
|
|
942c870981 | ||
|
|
c461fc6daa | ||
|
|
baff9256c5 | ||
|
|
b4c0a255fc | ||
|
|
9f6a27c20d | ||
|
|
742dc137ed | ||
|
|
39a6bd33ce | ||
|
|
4672a5fad6 | ||
|
|
e649b0a2df | ||
|
|
96eaa833f5 | ||
|
|
fd8dbe3eff | ||
|
|
bb97cc594d | ||
|
|
70a322263e | ||
|
|
c6f144d482 | ||
|
|
3709489b3a | ||
|
|
863b13a694 | ||
|
|
e6fea4e6dd | ||
|
|
83bfc13056 | ||
|
|
bc4f09209b | ||
|
|
145ef32e28 | ||
|
|
2212800a23 | ||
|
|
2e25431bb6 | ||
|
|
967ca17238 | ||
|
|
595c72147c | ||
|
|
f3c3b5a649 | ||
|
|
1cd2c5e653 | ||
|
|
b2873dd44b | ||
|
|
bb80ab4026 | ||
|
|
32c8e7522f | ||
|
|
a5e4f3cc6b | ||
|
|
80cabb338b | ||
|
|
2c69e2c151 | ||
|
|
a30777bd9f | ||
|
|
e989195a68 | ||
|
|
c1dd23f5e0 | ||
|
|
f93624a41c | ||
|
|
9f4559a059 | ||
|
|
fd05cad303 | ||
|
|
d58b06e493 | ||
|
|
d7a6127273 | ||
|
|
997d58932e | ||
|
|
b4015f877f | ||
|
|
8ee9984e4e | ||
|
|
d15fff95b9 | ||
|
|
2f0b549027 | ||
|
|
87dbd7e541 | ||
|
|
96e5da36be | ||
|
|
43745edac0 | ||
|
|
687e3b13ea | ||
|
|
8c6bb383b7 | ||
|
|
18bee21cfc | ||
|
|
e5b6121d17 | ||
|
|
f5ceee547c | ||
|
|
b612bce779 | ||
|
|
2e88e5e9c7 | ||
|
|
9a7aa25c90 | ||
|
|
c4420fe932 | ||
|
|
a5260f3a95 | ||
|
|
bc592c1d13 | ||
|
|
968bd8be67 | ||
|
|
47ccf4b1f5 | ||
|
|
a356b21895 | ||
|
|
614a36c888 | ||
|
|
d8b8adb88c | ||
|
|
f42d820891 | ||
|
|
bc21a1fb71 | ||
|
|
b7e717ee8c | ||
|
|
f520fe36bd | ||
|
|
7273a1c34d | ||
|
|
dc45cbce37 | ||
|
|
708d8f75c0 | ||
|
|
bd37d90228 | ||
|
|
b1ad691464 | ||
|
|
f4e7baf31e | ||
|
|
c0e60c41f2 | ||
|
|
c8dad43e00 | ||
|
|
3bc31374ac | ||
|
|
858e7bae2b | ||
|
|
a8f124704d | ||
|
|
eed2816491 | ||
|
|
a6334b3e35 | ||
|
|
334beebfeb | ||
|
|
13dad848bd | ||
|
|
e518f4cef8 | ||
|
|
c8fd5da2da | ||
|
|
3a74729ecc | ||
|
|
49c672ac4d | ||
|
|
b570cb5b77 | ||
|
|
97bf388471 | ||
|
|
1a32aaea6f | ||
|
|
4635883dec | ||
|
|
8c02d120a2 | ||
|
|
3ba6db4a50 | ||
|
|
2f1de25747 | ||
|
|
f60fd42ac0 | ||
|
|
ecc8f9c792 | ||
|
|
e295dfdcf7 | ||
|
|
fc42c25390 | ||
|
|
27d5858e06 | ||
|
|
07e353f4ff | ||
|
|
bb33d9e600 | ||
|
|
68eb0bdec9 | ||
|
|
32ee8e462c | ||
|
|
e1ef732b60 | ||
|
|
9840b95c21 | ||
|
|
a6f8446d81 | ||
|
|
c1c844c830 | ||
|
|
389299afd1 | ||
|
|
826543a291 | ||
|
|
4ac83cfded | ||
|
|
e79aa54b70 | ||
|
|
9a95652034 | ||
|
|
912c188b53 | ||
|
|
e9d0f615ba | ||
|
|
9136573596 | ||
|
|
2487ec94e6 | ||
|
|
811489f157 | ||
|
|
b438cc9335 | ||
|
|
1d3d30fa45 | ||
|
|
72b5985398 | ||
|
|
64c363ce53 | ||
|
|
cca4347bf9 | ||
|
|
3ae3d4926a | ||
|
|
36025d6d9f | ||
|
|
e171362e3e | ||
|
|
3e0bf2ae15 | ||
|
|
07aa9f4b8b | ||
|
|
b2d9f3fc64 | ||
|
|
5fb3e9167e | ||
|
|
2db60e0a6b | ||
|
|
e710848345 | ||
|
|
99c74b31be | ||
|
|
ce5b13824e | ||
|
|
c39170c42e | ||
|
|
9e96824161 | ||
|
|
fd19fbf300 | ||
|
|
166469827f | ||
|
|
a34ed538b6 | ||
|
|
8d6f3c2450 | ||
|
|
f863d127e7 | ||
|
|
5f22d3e055 | ||
|
|
a831110816 | ||
|
|
fdd700f3e5 | ||
|
|
adf930f126 | ||
|
|
e97bdb53f4 | ||
|
|
05f41928cd | ||
|
|
2ee0829871 | ||
|
|
743560825d | ||
|
|
e3d84ac349 | ||
|
|
266c832b30 | ||
|
|
f5374a024e | ||
|
|
fe1439fbac | ||
|
|
4956d826fb | ||
|
|
f5cc2af5d0 | ||
|
|
2bc30e5c22 | ||
|
|
7244c02a0d | ||
|
|
84ca8e1f3e | ||
|
|
5880d4a6ec | ||
|
|
ae05dce958 | ||
|
|
9ebe372a9a | ||
|
|
6c229ffa68 | ||
|
|
e6e04cc5b3 | ||
|
|
12352510fd | ||
|
|
2b3d927937 | ||
|
|
a8890740f5 | ||
|
|
f60d7ee54b | ||
|
|
896ca2ef6b | ||
|
|
c036f6d529 | ||
|
|
6f457c0c59 | ||
|
|
13bf1b27b4 | ||
|
|
f742bb1c47 | ||
|
|
aa0b9e2db2 | ||
|
|
c10076f7ed | ||
|
|
bcd92499f2 | ||
|
|
b2bb0d4f72 | ||
|
|
e140481f14 | ||
|
|
6b7b71b1f8 | ||
|
|
186bd11463 | ||
|
|
a0490d6687 | ||
|
|
beef740ade | ||
|
|
2ac7786a90 | ||
|
|
a3fb5e910f | ||
|
|
319afe86b5 | ||
|
|
762ab66b86 | ||
|
|
0c239a42de | ||
|
|
e9322fba26 | ||
|
|
39b6df27b3 | ||
|
|
b1ee284e7f | ||
|
|
e986332bf2 | ||
|
|
48f9b27381 | ||
|
|
42a6e0dd10 | ||
|
|
d4798b02ac | ||
|
|
963edfe8ab | ||
|
|
53237f3ae0 | ||
|
|
64da9281a4 | ||
|
|
ab7fd9799d | ||
|
|
f6bcc84251 | ||
|
|
35dc3d9df9 | ||
|
|
566714a75d | ||
|
|
c92f30b122 | ||
|
|
294ad094c4 | ||
|
|
c1a0f520f9 | ||
|
|
773c24b7fc | ||
|
|
8f926c7ca9 | ||
|
|
c562cbc2bb | ||
|
|
3fbbb0865a | ||
|
|
7d5f612a48 | ||
|
|
4a5a36440b | ||
|
|
43dd5cfea1 | ||
|
|
7b5fec1842 | ||
|
|
5762ded601 | ||
|
|
a3abb86daa | ||
|
|
4f5c656b05 | ||
|
|
a31cddbe7b | ||
|
|
b4ecd93f1c | ||
|
|
1a702b08b9 | ||
|
|
8c52dfb804 | ||
|
|
0acc23e058 | ||
|
|
cdd5f9b628 | ||
|
|
4c9f5f4655 | ||
|
|
b80ba13cb4 | ||
|
|
8260bdc09c | ||
|
|
24f856e02b | ||
|
|
3aa619b928 | ||
|
|
4cb5e98d94 | ||
|
|
272910575e | ||
|
|
a15a62f4bc | ||
|
|
53cf11db8c | ||
|
|
01052fbe47 | ||
|
|
a5e1e075c7 | ||
|
|
6be32ac688 | ||
|
|
b362c0ef38 | ||
|
|
bba9969e31 | ||
|
|
007ba24809 | ||
|
|
df21539311 | ||
|
|
2592cb6019 | ||
|
|
f7df17a7ed | ||
|
|
62f42b72f8 | ||
|
|
a1ba4fda6f | ||
|
|
1c06b04c45 | ||
|
|
2ee22fd374 | ||
|
|
4c230d9e61 | ||
|
|
727294fbbe | ||
|
|
478c43969b | ||
|
|
79b5303350 | ||
|
|
ce4b742b25 | ||
|
|
a9dc15bda5 | ||
|
|
ba6387ff5c | ||
|
|
8fa98508b7 | ||
|
|
decdbaecf9 | ||
|
|
6d87cf9be0 | ||
|
|
94f434c4a6 | ||
|
|
7ba867c30b | ||
|
|
3424395e10 | ||
|
|
926c7359a2 | ||
|
|
ec0af99a2e | ||
|
|
b4d948886c | ||
|
|
4d8d79372a | ||
|
|
04a589722c | ||
|
|
d4a10e2873 | ||
|
|
4998ad6c7e | ||
|
|
a07ca5ff50 | ||
|
|
f07e7571ab | ||
|
|
834c16485c | ||
|
|
04a4265ef3 | ||
|
|
0ec473195d | ||
|
|
0bf09256b0 | ||
|
|
db8fd2c913 | ||
|
|
dbe6e5b3d7 | ||
|
|
cc81cd446b | ||
|
|
439c7118f1 | ||
|
|
d8154a5815 | ||
|
|
4e3787bc0d | ||
|
|
02e0955924 | ||
|
|
3c6a170138 | ||
|
|
a78950e822 | ||
|
|
1ce1a94a35 | ||
|
|
977b6d9f67 | ||
|
|
b5e6dbd797 | ||
|
|
833e6688f1 | ||
|
|
bc22c9f84f | ||
|
|
2149a7d116 | ||
|
|
29175d2c17 | ||
|
|
803454d5c8 | ||
|
|
36cf32dc42 | ||
|
|
657f4ab303 | ||
|
|
c0c38022ea | ||
|
|
93b66d26ff | ||
|
|
ea6552615d | ||
|
|
4bf3287fce | ||
|
|
832c2034c2 | ||
|
|
b0aa26e1f1 | ||
|
|
e52baeb967 | ||
|
|
8268eb9a83 | ||
|
|
3cc458abd9 | ||
|
|
337b4c4268 | ||
|
|
001f8657f6 | ||
|
|
ea884e7fa1 | ||
|
|
9be2844c82 | ||
|
|
1b1394cf5d | ||
|
|
1eef930dbb | ||
|
|
875c687e3f | ||
|
|
1e175e74ed | ||
|
|
75a46c365e | ||
|
|
8e7b8825f5 | ||
|
|
2ecbca303b | ||
|
|
8195a4d616 | ||
|
|
7ba40f925f | ||
|
|
345cd1795f | ||
|
|
959aaee045 | ||
|
|
53477f0f59 | ||
|
|
5716218f41 | ||
|
|
9df6b9d5c0 | ||
|
|
a0be47ab8b | ||
|
|
ec46031d36 | ||
|
|
55b84d166a | ||
|
|
34ae8bacec | ||
|
|
cb4e5ca0f7 | ||
|
|
0ba45468c4 | ||
|
|
710502784e | ||
|
|
0275a8558d | ||
|
|
58acc75cf6 | ||
|
|
874ababb9f | ||
|
|
3771e6b0cd | ||
|
|
33eaefa966 | ||
|
|
cd7e236d57 | ||
|
|
54c0b7c7d5 | ||
|
|
a2177daec2 | ||
|
|
628386b453 | ||
|
|
b222bfb3e0 | ||
|
|
ab199d883d | ||
|
|
356065d1ee | ||
|
|
76e7c5623d | ||
|
|
085fba050a | ||
|
|
295334d3ac | ||
|
|
36124ddca4 | ||
|
|
bd6585765e | ||
|
|
c325deb4ed | ||
|
|
73bb0b10ee | ||
|
|
72820b162c | ||
|
|
89e5b8d057 | ||
|
|
da4f53ebbb | ||
|
|
8458553b74 | ||
|
|
55ecc41d06 | ||
|
|
28fcdf2cbb | ||
|
|
24087679a8 | ||
|
|
5ac6a8cb4a | ||
|
|
668d85d14e | ||
|
|
c11a3dc95c | ||
|
|
56f57c20a2 | ||
|
|
240d14779a | ||
|
|
3550d1e61c | ||
|
|
6513ad249c | ||
|
|
50297b1880 | ||
|
|
f189b78b9e | ||
|
|
5c0250f495 | ||
|
|
2093f726e9 | ||
|
|
10efe3859d | ||
|
|
6933bcf7bb | ||
|
|
2ea046cd80 | ||
|
|
f4097a372b | ||
|
|
40b6de599c | ||
|
|
87ea2a2bef | ||
|
|
cc14a1c361 | ||
|
|
bcdface60d | ||
|
|
4dc9419d2e | ||
|
|
d2bcac813e | ||
|
|
080c37a7f6 | ||
|
|
c1c6f55f8f | ||
|
|
f9a3838db6 | ||
|
|
1e61db104b | ||
|
|
30a9c7718d | ||
|
|
34b052b5d3 | ||
|
|
aaa12853ad | ||
|
|
b0ab55b0bf | ||
|
|
d2f8496f4e | ||
|
|
4c7e081e15 | ||
|
|
1a69b16d36 | ||
|
|
b5e8673e62 | ||
|
|
264c6a50b6 | ||
|
|
493642eb38 | ||
|
|
28d42b9164 | ||
|
|
42f29062ca | ||
|
|
09392be069 | ||
|
|
5529dab84e | ||
|
|
60ca704a9e | ||
|
|
c4377ed6c2 | ||
|
|
7c4d5cee95 | ||
|
|
7d283ed65f | ||
|
|
bf1f941e50 | ||
|
|
789fef34ba | ||
|
|
1daf5a611c | ||
|
|
6aed1db67e | ||
|
|
cf68854770 | ||
|
|
711392c73b | ||
|
|
9573c32481 | ||
|
|
a15f80f79d | ||
|
|
23e7475f06 | ||
|
|
1eb571b787 | ||
|
|
dd3b716d85 | ||
|
|
28649c07e3 | ||
|
|
961e02be0d | ||
|
|
a161491bfd | ||
|
|
e0b4d1c1e4 | ||
|
|
fd4aaab137 | ||
|
|
42d14d5ca2 | ||
|
|
d3ff482c9b | ||
|
|
c9286624d4 | ||
|
|
f682368eeb | ||
|
|
4a5d033efb | ||
|
|
343161b195 | ||
|
|
bc576a9659 | ||
|
|
19e407fcc4 | ||
|
|
bc7327d004 | ||
|
|
666fa1c797 | ||
|
|
0eda4a7821 | ||
|
|
862058fd2b | ||
|
|
193d160bed | ||
|
|
69e5bcd57d | ||
|
|
efeddda328 | ||
|
|
1ddd746862 | ||
|
|
ff6938280e | ||
|
|
1e4425b30f | ||
|
|
b5d1d8cdad | ||
|
|
029be5ccca | ||
|
|
29c2d785b5 | ||
|
|
abda8cfa32 | ||
|
|
44e7d79d4c | ||
|
|
9a1dc8ee0e | ||
|
|
27879c3f01 | ||
|
|
29096eb5d7 | ||
|
|
a573baea03 | ||
|
|
48ace3de57 | ||
|
|
5af07c4531 | ||
|
|
44e36feb09 | ||
|
|
3395c84560 | ||
|
|
2a7d996881 | ||
|
|
94c2fc80d2 | ||
|
|
738f943a68 | ||
|
|
47e62a5681 | ||
|
|
1ecbfd7590 | ||
|
|
67c139a04b | ||
|
|
31cc008249 | ||
|
|
9cb026439d | ||
|
|
e6f10176c6 | ||
|
|
0917c79470 | ||
|
|
597baa986d | ||
|
|
75cc4b4843 | ||
|
|
aac088d496 | ||
|
|
a822e5bbc5 | ||
|
|
c527249c21 | ||
|
|
9ef798f534 | ||
|
|
e69b99f089 | ||
|
|
55b8079e86 | ||
|
|
e272dbe9af | ||
|
|
962f8354ac | ||
|
|
20e4a960f7 | ||
|
|
371db886b4 | ||
|
|
3904ca38c0 | ||
|
|
16527ceaf6 | ||
|
|
feec3e8255 | ||
|
|
82249cb50a | ||
|
|
fad417e553 | ||
|
|
5ba692f50c | ||
|
|
f799db67eb | ||
|
|
3e106a9dc5 | ||
|
|
907e01e524 | ||
|
|
b8ed23efa7 | ||
|
|
2b3bbf7e67 | ||
|
|
464fe627a3 | ||
|
|
6a9e39c470 | ||
|
|
7fec9a3cc6 | ||
|
|
008f6ef462 | ||
|
|
2440c108ca | ||
|
|
430baad8a4 | ||
|
|
51132e74b4 | ||
|
|
a4f33e106a | ||
|
|
baba3190e0 | ||
|
|
47b13aa5ea | ||
|
|
a0de3fc643 | ||
|
|
9de3c582c0 | ||
|
|
45cff2b51b | ||
|
|
670397a73e | ||
|
|
272eb37e9a | ||
|
|
ca79e58ab9 | ||
|
|
977c049875 | ||
|
|
aefbc1c9bf | ||
|
|
c37a2e61ed | ||
|
|
7f6cd5e469 | ||
|
|
f6d1f1985c | ||
|
|
222c31b306 | ||
|
|
e99185f011 | ||
|
|
5c662f1230 | ||
|
|
a65c7ee2fc | ||
|
|
743c4f554d | ||
|
|
838b2757eb | ||
|
|
a92c9fc226 | ||
|
|
ed052e0b0b | ||
|
|
ae88d3054d | ||
|
|
7bb8b9039c | ||
|
|
3800b4b45c | ||
|
|
cd498711bc | ||
|
|
411b600e14 | ||
|
|
0a0ad9a184 | ||
|
|
234bead59e | ||
|
|
76de310986 | ||
|
|
817f050bcd | ||
|
|
60ae685d1e | ||
|
|
dc9670c439 | ||
|
|
03c8079858 | ||
|
|
0cfc527328 | ||
|
|
f66a820e14 | ||
|
|
4c7bdbb284 | ||
|
|
435251ca41 | ||
|
|
324a0dd38f | ||
|
|
cc77d93918 | ||
|
|
0ea7d8bd8c | ||
|
|
2e6bea23ac | ||
|
|
ca75dd0728 | ||
|
|
849b217143 | ||
|
|
9af6efba59 | ||
|
|
079d6f06ef | ||
|
|
9cf0757689 | ||
|
|
b54c438948 | ||
|
|
c3ff4bfdad | ||
|
|
e103676b65 | ||
|
|
17e395c2a8 | ||
|
|
d50c1f39ab | ||
|
|
ef6b25b3bb | ||
|
|
9f35fa0fa3 | ||
|
|
ff48996bbe | ||
|
|
2fe4d97061 | ||
|
|
eb38393cad | ||
|
|
5d62e066e2 | ||
|
|
e94219c5a3 | ||
|
|
8ed9634adf | ||
|
|
0aefa9599f | ||
|
|
e279cf0575 | ||
|
|
a3f0ef8e77 | ||
|
|
8eba05ed4a | ||
|
|
2f78155723 | ||
|
|
6785221479 | ||
|
|
9bc410dd3d | ||
|
|
2491ab6bf9 | ||
|
|
f615ed40cd | ||
|
|
430f2cafc1 | ||
|
|
0ad049da88 | ||
|
|
2c7691567b | ||
|
|
1d70d0fe94 | ||
|
|
ac44f05811 | ||
|
|
d99252f394 | ||
|
|
b58c7ba7c5 | ||
|
|
8c5acd1a0a | ||
|
|
b9b1ebf18c | ||
|
|
8ca132cef0 | ||
|
|
a03bb90754 | ||
|
|
d1c939f48a | ||
|
|
21b11f1b48 | ||
|
|
23c84a7803 | ||
|
|
f9ab060403 | ||
|
|
df7a5bf149 | ||
|
|
e205969b11 | ||
|
|
6bf19ecc34 | ||
|
|
c4afa069df | ||
|
|
1bfafdb44f | ||
|
|
1ef5bd7076 | ||
|
|
29176fa4f4 | ||
|
|
958c95732b | ||
|
|
44b0d4127c | ||
|
|
1418ec2416 | ||
|
|
b51978f51c | ||
|
|
b07361580a | ||
|
|
6ff45a754d | ||
|
|
d1b5ebad7d | ||
|
|
32d2df0f08 | ||
|
|
f4ce813de9 | ||
|
|
b44ac994d8 | ||
|
|
333948814c | ||
|
|
1a51ad6e01 | ||
|
|
22a5c11f0d | ||
|
|
51b22d1ad4 | ||
|
|
bef5969580 | ||
|
|
c6bf7bb9cd | ||
|
|
2a84d92cbf | ||
|
|
62de36b0da | ||
|
|
03a9aaeff7 | ||
|
|
45765e292d | ||
|
|
6e28a26015 | ||
|
|
9150bf720d | ||
|
|
845864679c | ||
|
|
b3b2149ebb | ||
|
|
0886dca385 | ||
|
|
53198ba4a7 | ||
|
|
a9652ee1fd | ||
|
|
75caf2f01c | ||
|
|
65bab2666e | ||
|
|
6d93ae399a | ||
|
|
7239c2e31a | ||
|
|
f269695d4a | ||
|
|
443af5f760 | ||
|
|
0e35350160 | ||
|
|
10bf497cda | ||
|
|
76eb629fc2 | ||
|
|
91de738563 | ||
|
|
43b7ef8110 | ||
|
|
99ef0b8cb4 | ||
|
|
0cf13f6393 | ||
|
|
4a8acfd123 | ||
|
|
abaffc1908 | ||
|
|
ea61d5c1a5 | ||
|
|
9a14931175 | ||
|
|
165eee102a | ||
|
|
6900c197cd | ||
|
|
fe3c66a7c8 | ||
|
|
0efb4da0ee | ||
|
|
1d728475e3 | ||
|
|
827057b9f1 | ||
|
|
ed7920d61e | ||
|
|
c0379c8e25 | ||
|
|
00a0e64fdd | ||
|
|
0dc60debea | ||
|
|
c44ae5888c | ||
|
|
b9495cd1bb | ||
|
|
bfec381933 | ||
|
|
2dddb8df69 | ||
|
|
d30397e9c0 | ||
|
|
d9597549fd | ||
|
|
13512b4146 | ||
|
|
49e546919a | ||
|
|
586015c2ed | ||
|
|
4a7e067d1a | ||
|
|
9bc0b7f183 | ||
|
|
cd4dfc9861 | ||
|
|
1716452203 | ||
|
|
09bdbc1224 | ||
|
|
978b3a64c5 | ||
|
|
651547ef20 | ||
|
|
b4d95977d0 | ||
|
|
5d8bb897db | ||
|
|
84c8ecb372 | ||
|
|
61abe5b948 | ||
|
|
a5b573eaaa | ||
|
|
cbb32f82eb | ||
|
|
ca9334b2df | ||
|
|
959ed7f866 | ||
|
|
a5c0411be0 | ||
|
|
32e1303742 | ||
|
|
7263b6fe89 | ||
|
|
46a4070f84 | ||
|
|
c3c155a1ed | ||
|
|
b067105660 | ||
|
|
15ca18848e | ||
|
|
67c9e2ead6 | ||
|
|
3681177be4 | ||
|
|
6eb814ef0b | ||
|
|
bcc695234c | ||
|
|
ad16a6fc1b | ||
|
|
478b7eeb65 | ||
|
|
151a153dc9 | ||
|
|
ad131854ca | ||
|
|
0bd0eb9e59 | ||
|
|
54827cacb9 | ||
|
|
e3a4a16507 | ||
|
|
cf16fd0104 | ||
|
|
21b00ac6ca | ||
|
|
57e6f3080c | ||
|
|
89744100ce | ||
|
|
a718f9bbfd | ||
|
|
e81bc4f044 | ||
|
|
4dbacd79ae | ||
|
|
ae74d54451 | ||
|
|
dc316c5669 | ||
|
|
e9f04256c9 | ||
|
|
e1aabd70e8 | ||
|
|
a9dc1b32e0 | ||
|
|
01d847ae4e | ||
|
|
61e2c3444a | ||
|
|
5363b0f810 | ||
|
|
f0e1a8823e | ||
|
|
7be5937aa0 | ||
|
|
8f43055b0e | ||
|
|
953a81b299 | ||
|
|
1d34ae7934 | ||
|
|
2cabb2666b | ||
|
|
0b59bb1a29 | ||
|
|
c1e7d74b96 | ||
|
|
cc262d6595 | ||
|
|
61d43b118b | ||
|
|
989d8181dd | ||
|
|
cffc157d98 | ||
|
|
2a70619577 | ||
|
|
b91919bffa | ||
|
|
fb7a4bf880 | ||
|
|
4b41799a90 | ||
|
|
123f39a21b | ||
|
|
cadab12737 | ||
|
|
742055c43b | ||
|
|
fa73b41fa7 | ||
|
|
a474eafe84 | ||
|
|
442fcf921c | ||
|
|
fb0923f3ab | ||
|
|
5bb943f845 | ||
|
|
a3109953d0 | ||
|
|
ff266c8c79 | ||
|
|
ef2e02098d | ||
|
|
93598d3a51 | ||
|
|
53aebcfb1e | ||
|
|
bb2467d2ac | ||
|
|
05c063b61d | ||
|
|
ef1d1303f4 | ||
|
|
b84ab656d8 | ||
|
|
edd4b477f8 | ||
|
|
04fcb33d7e | ||
|
|
f31d2486c9 | ||
|
|
7dea682713 | ||
|
|
7955ddceb2 | ||
|
|
8a6b254799 | ||
|
|
94562cb5cf | ||
|
|
b064c124e7 | ||
|
|
c7e64f40f9 | ||
|
|
0f254dca13 | ||
|
|
e0f2ff36af | ||
|
|
3546e7b51e | ||
|
|
5e7c3ed46a | ||
|
|
13ec1aafa0 | ||
|
|
f521bce9e6 | ||
|
|
c78209604c | ||
|
|
8fe4cfecb6 | ||
|
|
a5a2df4956 | ||
|
|
2fa5e4679f | ||
|
|
57af984e68 | ||
|
|
442e840a53 | ||
|
|
3c33f7d294 | ||
|
|
42a66ad49e | ||
|
|
2d1d70b3b6 | ||
|
|
c9217a419a | ||
|
|
a180395832 | ||
|
|
3dfcc6b0be | ||
|
|
cb1df5217e | ||
|
|
24ef80351c | ||
|
|
bb878a1ccf | ||
|
|
4daea7d7e6 | ||
|
|
3b20747192 | ||
|
|
403e30feba | ||
|
|
f58c73b7f1 | ||
|
|
2a8477cbda | ||
|
|
f5bee7b691 | ||
|
|
8c077a7373 | ||
|
|
4e07b51460 | ||
|
|
44294e1a88 | ||
|
|
25a0a68cde | ||
|
|
3e259021d0 | ||
|
|
f760a9d0c2 | ||
|
|
f69facc842 | ||
|
|
e17638bc06 | ||
|
|
399c0d337a | ||
|
|
856eb479e4 | ||
|
|
1c7de1d668 | ||
|
|
8a8f24f93e | ||
|
|
e76dba0f84 | ||
|
|
aababe1a87 | ||
|
|
436b0624e7 | ||
|
|
0a37d1c15c | ||
|
|
793269731d | ||
|
|
b69a4fe8b5 | ||
|
|
665d84f40a | ||
|
|
4734b390a5 | ||
|
|
50d0721c39 | ||
|
|
9079f15f52 | ||
|
|
60b460d594 | ||
|
|
98f42d9b3b | ||
|
|
23adcb544b | ||
|
|
e6b24d2e3c | ||
|
|
ea3e736a14 | ||
|
|
a5c39b829a | ||
|
|
1ec333ee5a | ||
|
|
bbae93aa16 | ||
|
|
be1dcb7264 | ||
|
|
4a1e6dcc32 | ||
|
|
f644a4ea78 | ||
|
|
85b7405963 | ||
|
|
c854f436bf | ||
|
|
e5be8b7f67 | ||
|
|
906ae730e9 | ||
|
|
92df7747b2 | ||
|
|
0ee8f5efe3 | ||
|
|
4b5b0b065d | ||
|
|
15cf8d2a6d | ||
|
|
ef0ba9483f | ||
|
|
70500cf21e | ||
|
|
a7da6cf172 | ||
|
|
ae76ae4025 | ||
|
|
9614ec4c6a | ||
|
|
c4e90b810d | ||
|
|
887ce3377e | ||
|
|
6ef47249ab | ||
|
|
3a0df56605 | ||
|
|
98cdee7f03 | ||
|
|
b3e2a6a860 | ||
|
|
55410f026b | ||
|
|
f2611f64ac | ||
|
|
d788bd8323 | ||
|
|
9eb108f13e | ||
|
|
eebd64bedb | ||
|
|
21504f1329 | ||
|
|
ff6bae936d | ||
|
|
62523c815e | ||
|
|
0f5465c5da | ||
|
|
e4cba70008 | ||
|
|
692b993eee | ||
|
|
35e3a479cd | ||
|
|
bb7ff27d04 | ||
|
|
0acc5e33b3 | ||
|
|
cb5187fd8d | ||
|
|
160c6e6554 | ||
|
|
a173179b03 | ||
|
|
e73497e4b7 | ||
|
|
835ef01a70 | ||
|
|
a1335aecfb | ||
|
|
c553312fd5 | ||
|
|
3adc7ca22a | ||
|
|
441e603bc0 | ||
|
|
7511df61b3 | ||
|
|
91d3d2ad1f | ||
|
|
6692b618ea | ||
|
|
2052149dc1 | ||
|
|
7b8237afae | ||
|
|
859a984ec8 | ||
|
|
89932b325d | ||
|
|
dac85757b3 | ||
|
|
3b0cec9db6 | ||
|
|
17749bb14a | ||
|
|
c56dd4172e | ||
|
|
d2335485f2 | ||
|
|
cf69dd644a | ||
|
|
8df6af62d7 | ||
|
|
3c3bb70b01 | ||
|
|
d8a4eaf026 | ||
|
|
2402010d24 | ||
|
|
16c804106a | ||
|
|
b1ef9361f3 | ||
|
|
766a26128d | ||
|
|
5b2dce6cf6 | ||
|
|
bee9be534c | ||
|
|
4b49331d97 | ||
|
|
f9513ca802 | ||
|
|
3de13a4d9e | ||
|
|
8a7df954e5 | ||
|
|
3706b53e65 | ||
|
|
8a8aaf3297 | ||
|
|
41a5639711 | ||
|
|
5d8f9f1a5a | ||
|
|
5124cd4b77 | ||
|
|
0cbf66996f | ||
|
|
e922fdc5d0 | ||
|
|
0addbaa9a8 | ||
|
|
8176fb7bad | ||
|
|
baae3592d3 | ||
|
|
6a40e18193 | ||
|
|
2cdb6b811f | ||
|
|
8a8aa1337b | ||
|
|
3fe5647a15 | ||
|
|
fec1245811 | ||
|
|
ccab6eb7c4 | ||
|
|
c9f6e2e257 | ||
|
|
f0d3a4e4b7 | ||
|
|
41295e0c4d | ||
|
|
2abd0265c8 | ||
|
|
1e09ccb4d9 | ||
|
|
11e1d04dd1 | ||
|
|
f140f5f14b | ||
|
|
5898534c23 | ||
|
|
7836336689 | ||
|
|
f96865c2cb | ||
|
|
e475893fd7 | ||
|
|
75a37adcd1 | ||
|
|
c3b1070b83 | ||
|
|
339ca7accf | ||
|
|
0b02e8116c | ||
|
|
8f973661f4 | ||
|
|
c5a73a5c19 | ||
|
|
6a90340b14 | ||
|
|
46abbfe224 | ||
|
|
145d4e4bd5 | ||
|
|
b3ba79a3ba | ||
|
|
c69db035ee | ||
|
|
60a7eaf2bb | ||
|
|
3f43567c8f | ||
|
|
e690f6d487 | ||
|
|
3d4b4e04c5 | ||
|
|
62dd8f35c0 | ||
|
|
1468dfd6b6 | ||
|
|
40e92721c1 | ||
|
|
204e940dcb | ||
|
|
98aa9bd3fe | ||
|
|
041531e96d | ||
|
|
c2a188f7fe | ||
|
|
1a1d37a2d0 | ||
|
|
214649ec20 | ||
|
|
e3866eeb29 | ||
|
|
20db216275 | ||
|
|
f404fe0570 | ||
|
|
bef4361736 | ||
|
|
aa991b62f4 | ||
|
|
8dfe0f4373 | ||
|
|
ffedb79670 | ||
|
|
0e23935455 | ||
|
|
4f62320e7b | ||
|
|
aee3bd3a80 | ||
|
|
c992b89b2f | ||
|
|
fc5c9647d8 | ||
|
|
3a238e9d4b | ||
|
|
9d9fea49ca | ||
|
|
e21131d67e | ||
|
|
1f02d0f6d0 | ||
|
|
830fde8007 | ||
|
|
c44ce77e95 | ||
|
|
ab318ef99e | ||
|
|
c86c2661af | ||
|
|
dabb222511 | ||
|
|
ef13b3a36c | ||
|
|
6fb9081394 | ||
|
|
1ba38b3902 | ||
|
|
dc06a132bc | ||
|
|
644b4f88ac | ||
|
|
c97197b61a | ||
|
|
3e97d29bcf | ||
|
|
a5ea214553 | ||
|
|
91c6ae229e | ||
|
|
e18f4c843a | ||
|
|
0f103d5853 | ||
|
|
56f10e238b | ||
|
|
5baa2e9069 | ||
|
|
7bf83371d5 | ||
|
|
36c575023e | ||
|
|
7eadc74f6c | ||
|
|
3ad06c406c | ||
|
|
c68e37a8c4 | ||
|
|
e66496eae7 | ||
|
|
e6b951c62a | ||
|
|
5279226f36 | ||
|
|
31b552ab51 | ||
|
|
f5e53cd60f | ||
|
|
4a48f59d27 | ||
|
|
bc2c63bf1f | ||
|
|
b56a757f2e | ||
|
|
4692ed4b4a | ||
|
|
615bbcae74 | ||
|
|
7737c6aee1 | ||
|
|
f7c0499158 | ||
|
|
9ebcefee00 | ||
|
|
b18b5c4f43 | ||
|
|
4752b0772f | ||
|
|
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
.gitignore
vendored
18
.gitignore
vendored
@@ -1,8 +1,20 @@
|
||||
obj/
|
||||
libs/
|
||||
out
|
||||
*.zip
|
||||
*.jks
|
||||
*.apk
|
||||
config.prop
|
||||
|
||||
# Manually dumped jars
|
||||
snet/libs
|
||||
|
||||
# Built binaries
|
||||
ziptools/zipadjust
|
||||
native/out
|
||||
|
||||
# Android Studio / Gradle
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
|
||||
36
.gitmodules
vendored
36
.gitmodules
vendored
@@ -1,21 +1,21 @@
|
||||
[submodule "jni/selinux"]
|
||||
path = jni/external/selinux
|
||||
[submodule "selinux"]
|
||||
path = native/jni/external/selinux
|
||||
url = https://github.com/topjohnwu/selinux.git
|
||||
[submodule "jni/su"]
|
||||
path = jni/su
|
||||
url = https://github.com/topjohnwu/MagiskSU.git
|
||||
[submodule "jni/ndk-compression"]
|
||||
path = jni/external/ndk-compression
|
||||
url = https://github.com/topjohnwu/ndk-compression.git
|
||||
[submodule "jni/magiskpolicy"]
|
||||
path = jni/magiskpolicy
|
||||
url = https://github.com/topjohnwu/magiskpolicy.git
|
||||
[submodule "MagiskManager"]
|
||||
path = java
|
||||
url = https://github.com/topjohnwu/MagiskManager.git
|
||||
[submodule "jni/busybox"]
|
||||
path = jni/external/busybox
|
||||
[submodule "busybox"]
|
||||
path = native/jni/external/busybox
|
||||
url = https://github.com/topjohnwu/ndk-busybox.git
|
||||
[submodule "jni/external/dtc"]
|
||||
path = jni/external/dtc
|
||||
[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
|
||||
|
||||
92
README.MD
92
README.MD
@@ -1,35 +1,32 @@
|
||||
# Magisk
|
||||
[XDA Announcement Thread](https://forum.xda-developers.com/apps/magisk/official-magisk-v7-universal-systemless-t3473445)
|
||||
|
||||
## How to build 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 has been tested on 3 major platforms:
|
||||
## Building Notes and Instructions
|
||||
1. Clone sources with submodules: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`
|
||||
2. Building is supported on macOS, Linux, and Windows. Official releases are built and tested with [FrankeNDK](https://github.com/topjohnwu/FrankeNDK); point `ANDROID_NDK_HOME` to FrankeNDK if you want to use it for compiling.
|
||||
3. Set configurations in `config.prop`. A sample file `config.prop.sample` is provided as an example.
|
||||
4. 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`
|
||||
5. By default, `build.py` build binaries and Magisk Manager in debug mode. If you want to build Magisk Manager in release mode (via the `-r, --release` flag), you need a Java Keystore file `release-key.jks` (only `JKS` format is supported) to sign APKs and zips. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually).
|
||||
|
||||
**macOS 10.12**
|
||||
**Ubuntu 17.04 x64**
|
||||
**Windows 10 x64**
|
||||
## Bug Reports
|
||||
**Make sure to install the latest [Canary Build](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337) before reporting any bugs!** Please **DO NOT** report bugs that is already fixed upstream.
|
||||
|
||||
#### Environment Requirements
|
||||
|
||||
1. A 64-bit machine: `cmake` for Android is only available in 64-bit
|
||||
2. Python 3.5+: to run the build script
|
||||
3. Java Development Kit (JDK) 8: To compile Magisk Manager and sign zips
|
||||
4. C compiler (Unix only): To build `zipadjust`. Windows users can use the pre-built `zipadjust.exe`
|
||||
5. Android SDK: `ANDROID_HOME` environment variable should point to the Android SDK folder
|
||||
6. Android NDK: Install NDK via `sdkmanager`, or via Android SDK Manager in Android Studio
|
||||
|
||||
#### Instructions and Notes
|
||||
|
||||
1. The easiest way to setup a working environment is to open Magisk Manager with Android Studio. The IDE will download required components and construct the environment for you. Don't forget to set `ANDROID_HOME` environment variable to the SDK path.
|
||||
2. Windows users: while installing Python 3 on Windows, allow the installer to add Python to `PATH`, or you'll have to add it manually afterwards. By default, the Python executable is setup as `python`, not `python3` like most Unix environment. If you have both Python 2 and Python 3 installed, you'll have to deal with the executable name and `PATH` yourself. To double check the Python version, call `python --version`.
|
||||
3. To run the script, on Windows call `python build.py [args...]`; on Unix call `python3 build.py [args...]`, or simply `./build.py [args...]`. To see the built-in help message, call the script with `-h` as an argument. The `-h` option also works for each supported actions to see the help message for the specific action.
|
||||
4. By default, the script will build binaries and Magisk Manager in debug mode, which will enable verbose debugging messages. If you want to build Magisk Manager in release mode (through the flag `--release`), you will need to place your Java Keystore file in `release_signature.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 python build script uses ANSI color codes to change the color of the terminal output. For Windows, this **only** works on Windows 10, as previous Windows console do not support them. If you use an older Windows version, a quick Google search should provide many workarounds.
|
||||
Follow the instructions in the [Canary Channel XDA Thread](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337), and report a bug either by opening an issue on GitHub or directly in the thread.
|
||||
|
||||
## Documentation
|
||||
[Link to Documentation](docs/README.MD)
|
||||
|
||||
## License
|
||||
|
||||
```
|
||||
Magisk, including all subprojects (git submodule) is free software:
|
||||
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.
|
||||
@@ -42,54 +39,3 @@ 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** (`java`)
|
||||
|
||||
* Copyright 2016-2017, John Wu (@topjohnwu)
|
||||
* All contributors and translators
|
||||
|
||||
**MagiskSU** (`jni/su`)
|
||||
|
||||
* Copyright 2016-2017, 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** (`jni/magiskpolicy`)
|
||||
|
||||
* Copyright 2016-2017, John Wu (@topjohnwu)
|
||||
* Copyright 2015, Pierre-Hugues Husson (phh@phh.me)
|
||||
* Copyright 2015, Joshua Brindle (@joshua_brindle)
|
||||
|
||||
**MagiskHide** (`jni/magiskhide`)
|
||||
|
||||
* Copyright 2016-2017, John Wu (@topjohnwu)
|
||||
* Copyright 2016, Pierre-Hugues Husson (phh@phh.me) (original hidesu)
|
||||
|
||||
**resetprop** (`jni/resetprop`)
|
||||
|
||||
* Copyright 2016-2017 John Wu (@topjohnwu)
|
||||
* Copyright 2016 nkk71 (nkk71x@gmail.com)
|
||||
|
||||
**SELinux** (`jni/selinux`)
|
||||
|
||||
* Makefile for NDK: Copyright 2016-2017, John Wu (@topjohnwu)
|
||||
* Maintained by many developers in SELinux project
|
||||
|
||||
**ndk-compression** (`jni/ndk-compression`)
|
||||
|
||||
* Makefile for NDK: Copyright 2017, John Wu (@topjohnwu)
|
||||
* Each library has its own copyright message in corresponding directories
|
||||
|
||||
**ndk-busybox** (`jni/busybox`)
|
||||
|
||||
* Makefile for NDK, generated by [ndk-busybox-kitchen](https://github.com/topjohnwu/ndk-busybox-kitchen): Copyright 2017, John Wu (@topjohnwu)
|
||||
* Patches for NDK: Many contributors along the way, all placed in [osm0sis/android-busybox-ndk](https://github.com/osm0sis/android-busybox-ndk)
|
||||
* The copyright message for busybox should be included in its own directory
|
||||
|
||||
**Others Not Mentioned**
|
||||
|
||||
* Copyright 2016-2017, John Wu (@topjohnwu)
|
||||
|
||||
12
app/.gitignore
vendored
Normal file
12
app/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
.idea/
|
||||
/build
|
||||
app/release
|
||||
*.hprof
|
||||
.externalNativeBuild/
|
||||
src/full/res/raw/util_functions.sh
|
||||
public.certificate.x509.pem
|
||||
private.key.pk8
|
||||
*.apk
|
||||
7
app/README.md
Normal file
7
app/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Magisk Manager
|
||||
This repo is no longer an independent component. It is merged into the [Magisk Project](https://github.com/topjohnwu/Magisk).
|
||||
|
||||
# Translations
|
||||
The default (English) strings are mainly in `src/full/res/values/strings.xml`; some are scattered in `src/main/res/values/strings.xml` and `src/stub/res/values/strings.xml`.
|
||||
Translations are highly appreciated via pull requests here on Github.
|
||||
Place translated XMLs in the corresponding locale folder.
|
||||
82
app/build.gradle
Normal file
82
app/build.gradle
Normal file
@@ -0,0 +1,82 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
def configProps = new Properties()
|
||||
def configPath = project.hasProperty('configPath') ? project.configPath : rootProject.file('config.prop')
|
||||
configProps.load(new FileInputStream(configPath))
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.topjohnwu.magisk"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion rootProject.ext.compileSdkVersion
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
config {
|
||||
storeFile rootProject.file('release-key.jks')
|
||||
storePassword configProps['keyStorePass']
|
||||
keyAlias configProps['keyAlias']
|
||||
keyPassword configProps['keyPass']
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
// If keystore exists, sign the APK with custom signature
|
||||
if (signingConfigs.config.storeFile.exists())
|
||||
signingConfig signingConfigs.config
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.config
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "mode"
|
||||
|
||||
productFlavors {
|
||||
full {
|
||||
versionName configProps['appVersion']
|
||||
versionCode configProps['appVersionCode'] as Integer
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
argument('butterknife.debuggable', 'false')
|
||||
}
|
||||
}
|
||||
}
|
||||
stub {
|
||||
versionCode 1
|
||||
versionName "stub"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
dexOptions {
|
||||
preDexLibraries true
|
||||
javaMaxHeapSize "2g"
|
||||
}
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
fullImplementation project(':utils')
|
||||
implementation "androidx.core:core:${rootProject.ext.androidXVersion}"
|
||||
fullImplementation "androidx.preference:preference:${rootProject.ext.androidXVersion}"
|
||||
fullImplementation "androidx.recyclerview:recyclerview:${rootProject.ext.androidXVersion}"
|
||||
fullImplementation "androidx.cardview:cardview:${rootProject.ext.androidXVersion}"
|
||||
fullImplementation "com.google.android.material:material:${rootProject.ext.androidXVersion}"
|
||||
fullImplementation 'com.github.topjohnwu:libsu:2.0.2'
|
||||
fullImplementation 'com.atlassian.commonmark:commonmark:0.11.0'
|
||||
fullImplementation 'org.kamranzafar:jtar:2.3'
|
||||
}
|
||||
35
app/proguard-rules.pro
vendored
Normal file
35
app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /Users/topjohnwu/Library/Android/sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# BouncyCastle
|
||||
-keep class org.bouncycastle.jcajce.provider.asymmetric.rsa.**SHA1** { *; }
|
||||
-keep class org.bouncycastle.jcajce.provider.asymmetric.RSA** { *; }
|
||||
-keep class org.bouncycastle.jcajce.provider.digest.SHA1** { *; }
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
# Snet extention
|
||||
-keepclassmembers class com.topjohnwu.magisk.utils.ISafetyNetHelper { *; }
|
||||
|
||||
# Strip logging
|
||||
-assumenosideeffects class com.topjohnwu.magisk.utils.Logger {
|
||||
public *** debug(...);
|
||||
}
|
||||
|
||||
# Excessive obfuscation
|
||||
-repackageclasses 'a'
|
||||
-allowaccessmodification
|
||||
105
app/src/full/AndroidManifest.xml
Normal file
105
app/src/full/AndroidManifest.xml
Normal file
@@ -0,0 +1,105 @@
|
||||
<?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.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:name="a.q"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
|
||||
<!-- Activities -->
|
||||
|
||||
<activity
|
||||
android:name="a.b"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name="a.c"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:exported="true"
|
||||
android:theme="@style/SplashTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="a.d"
|
||||
android:theme="@style/AppTheme.StatusBar" />
|
||||
<activity
|
||||
android:name="a.e"
|
||||
android:theme="@style/AppTheme.StatusBar"/>
|
||||
<activity
|
||||
android:name="a.f"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:screenOrientation="nosensor"
|
||||
android:theme="@style/AppTheme.StatusBar" />
|
||||
<activity
|
||||
android:name="a.g"
|
||||
android:theme="@style/AppTheme.Translucent" />
|
||||
|
||||
<!-- Superuser -->
|
||||
|
||||
<activity
|
||||
android:name="a.p"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="internal.superuser"
|
||||
android:theme="@style/SuRequest" />
|
||||
|
||||
<activity
|
||||
android:name=".superuser.RequestActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="internal.superuser"
|
||||
android:theme="@style/AppTheme.Translucent" />
|
||||
|
||||
<receiver android:name=".superuser.SuReceiver" />
|
||||
|
||||
<!-- Receiver -->
|
||||
|
||||
<receiver android:name="a.h">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name="a.i">
|
||||
<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="a.j" />
|
||||
<receiver android:name="a.k" />
|
||||
<receiver android:name="a.l">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- Service -->
|
||||
|
||||
<service
|
||||
android:name="a.m"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
<service
|
||||
android:name="a.n"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
|
||||
<!-- Hardcode GMS version -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="12451000" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
10
app/src/full/java/a/a.java
Normal file
10
app/src/full/java/a/a.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.utils.BootSigner;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public class a extends BootSigner {
|
||||
/* stub */
|
||||
}
|
||||
7
app/src/full/java/a/b.java
Normal file
7
app/src/full/java/a/b.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.MainActivity;
|
||||
|
||||
public class b extends MainActivity {
|
||||
/* stub */
|
||||
}
|
||||
7
app/src/full/java/a/c.java
Normal file
7
app/src/full/java/a/c.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
|
||||
public class c extends SplashActivity {
|
||||
/* stub */
|
||||
}
|
||||
7
app/src/full/java/a/d.java
Normal file
7
app/src/full/java/a/d.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.AboutActivity;
|
||||
|
||||
public class d extends AboutActivity {
|
||||
/* stub */
|
||||
}
|
||||
7
app/src/full/java/a/e.java
Normal file
7
app/src/full/java/a/e.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.DonationActivity;
|
||||
|
||||
public class e extends DonationActivity {
|
||||
/* stub */
|
||||
}
|
||||
7
app/src/full/java/a/f.java
Normal file
7
app/src/full/java/a/f.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
|
||||
public class f extends FlashActivity {
|
||||
/* stub */
|
||||
}
|
||||
7
app/src/full/java/a/h.java
Normal file
7
app/src/full/java/a/h.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.receivers.BootReceiver;
|
||||
|
||||
public class h extends BootReceiver {
|
||||
/* stub */
|
||||
}
|
||||
7
app/src/full/java/a/i.java
Normal file
7
app/src/full/java/a/i.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.receivers.PackageReceiver;
|
||||
|
||||
public class i extends PackageReceiver {
|
||||
/* stub */
|
||||
}
|
||||
7
app/src/full/java/a/j.java
Normal file
7
app/src/full/java/a/j.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
|
||||
public class j extends ManagerUpdate {
|
||||
/* stub */
|
||||
}
|
||||
7
app/src/full/java/a/k.java
Normal file
7
app/src/full/java/a/k.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.receivers.RebootReceiver;
|
||||
|
||||
public class k extends RebootReceiver {
|
||||
/* stub */
|
||||
}
|
||||
7
app/src/full/java/a/l.java
Normal file
7
app/src/full/java/a/l.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.receivers.ShortcutReceiver;
|
||||
|
||||
public class l extends ShortcutReceiver {
|
||||
/* stub */
|
||||
}
|
||||
7
app/src/full/java/a/m.java
Normal file
7
app/src/full/java/a/m.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.services.OnBootService;
|
||||
|
||||
public class m extends OnBootService {
|
||||
/* stub */
|
||||
}
|
||||
7
app/src/full/java/a/n.java
Normal file
7
app/src/full/java/a/n.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
|
||||
public class n extends UpdateCheckService {
|
||||
/* stub */
|
||||
}
|
||||
22
app/src/full/java/a/o.java
Normal file
22
app/src/full/java/a/o.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package a;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.topjohnwu.magisk.components.AboutCardRow;
|
||||
|
||||
public class o extends AboutCardRow {
|
||||
/* stub */
|
||||
|
||||
public o(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public o(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public o(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
}
|
||||
7
app/src/full/java/a/p.java
Normal file
7
app/src/full/java/a/p.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.SuRequestActivity;
|
||||
|
||||
public class p extends SuRequestActivity {
|
||||
/* stub */
|
||||
}
|
||||
7
app/src/full/java/a/q.java
Normal file
7
app/src/full/java/a/q.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
public class q extends MagiskManager {
|
||||
/* stub */
|
||||
}
|
||||
71
app/src/full/java/com/topjohnwu/magisk/AboutActivity.java
Normal file
71
app/src/full/java/com/topjohnwu/magisk/AboutActivity.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.components.AboutCardRow;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
public class AboutActivity extends BaseActivity {
|
||||
|
||||
Toolbar toolbar;
|
||||
AboutCardRow appVersionInfo;
|
||||
AboutCardRow appChangelog;
|
||||
AboutCardRow appTranslators;
|
||||
AboutCardRow appSourceCode;
|
||||
AboutCardRow supportThread;
|
||||
AboutCardRow twitter;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_StatusBar_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_about);
|
||||
ViewBinder.bind(this);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
toolbar.setNavigationOnClickListener(view -> finish());
|
||||
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setTitle(R.string.about);
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
appVersionInfo.setSummary(String.format(Locale.US, "%s (%d) (%s)",
|
||||
BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, getPackageName()));
|
||||
|
||||
appChangelog.setOnClickListener(v -> {
|
||||
new MarkDownWindow(this, getString(R.string.app_changelog),
|
||||
getResources().openRawResource(R.raw.changelog)).exec();
|
||||
});
|
||||
|
||||
String translators = getString(R.string.translators);
|
||||
if (TextUtils.isEmpty(translators)) {
|
||||
appTranslators.setVisibility(View.GONE);
|
||||
} else {
|
||||
appTranslators.setSummary(translators);
|
||||
}
|
||||
|
||||
appSourceCode.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.SOURCE_CODE_URL)));
|
||||
supportThread.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.XDA_THREAD)));
|
||||
twitter.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.TWITTER_URL)));
|
||||
|
||||
setFloating();
|
||||
}
|
||||
|
||||
}
|
||||
156
app/src/full/java/com/topjohnwu/magisk/Const.java
Normal file
156
app/src/full/java/com/topjohnwu/magisk/Const.java
Normal file
@@ -0,0 +1,156 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.os.Process;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class Const {
|
||||
|
||||
public static final String DEBUG_TAG = "MagiskManager";
|
||||
public static final String ORIG_PKG_NAME = BuildConfig.APPLICATION_ID;
|
||||
public static final String MAGISKHIDE_PROP = "persist.magisk.hide";
|
||||
|
||||
// APK content
|
||||
public static final String ANDROID_MANIFEST = "AndroidManifest.xml";
|
||||
|
||||
public static final String SU_KEYSTORE_KEY = "su_key";
|
||||
|
||||
// Paths
|
||||
public static File MAGISK_PATH;
|
||||
public static File MAGISK_DISABLE_FILE;
|
||||
public static File MAGISK_HOST_FILE;
|
||||
|
||||
static {
|
||||
/* Prevent crashing on unrooted devices */
|
||||
MAGISK_PATH = MAGISK_DISABLE_FILE = MAGISK_HOST_FILE = new File("xxx");
|
||||
}
|
||||
|
||||
public static final String BUSYBOX_PATH = "/sbin/.core/busybox";
|
||||
public static final String TMP_FOLDER_PATH = "/dev/tmp";
|
||||
public static final String MAGISK_LOG = "/cache/magisk.log";
|
||||
public static final String MANAGER_CONFIGS = ".tmp.magisk.config";
|
||||
|
||||
// Versions
|
||||
public static final int UPDATE_SERVICE_VER = 1;
|
||||
|
||||
public static int MIN_MODULE_VER() {
|
||||
return Data.magiskVersionCode >= MAGISK_VER.REMOVE_LEGACY_LINK ? 1500 : 1400;
|
||||
}
|
||||
|
||||
/* A list of apps that should not be shown as hide-able */
|
||||
public static final List<String> HIDE_BLACKLIST = Arrays.asList(
|
||||
"android",
|
||||
Data.MM().getPackageName(),
|
||||
"com.google.android.gms"
|
||||
);
|
||||
|
||||
public static final int USER_ID = Process.myUid() / 100000;
|
||||
|
||||
public static final class MAGISK_VER {
|
||||
public static final int UNIFIED = 1300;
|
||||
public static final int FBE_AWARE = 1410;
|
||||
public static final int RESETPROP_PERSIST = 1436;
|
||||
public static final int MANAGER_HIDE = 1440;
|
||||
public static final int HIDDEN_PATH = 1460;
|
||||
public static final int REMOVE_LEGACY_LINK = 1630;
|
||||
public static final int SEPOL_REFACTOR = 1640;
|
||||
public static final int FIX_ENV = 1650;
|
||||
public static final int DBVER_SIX = 17000;
|
||||
}
|
||||
|
||||
public static class ID {
|
||||
public static final int UPDATE_SERVICE_ID = 1;
|
||||
public static final int FETCH_ZIP = 2;
|
||||
public static final int SELECT_BOOT = 3;
|
||||
public static final int ONBOOT_SERVICE_ID = 6;
|
||||
|
||||
// notifications
|
||||
public static final int MAGISK_UPDATE_NOTIFICATION_ID = 4;
|
||||
public static final int APK_UPDATE_NOTIFICATION_ID = 5;
|
||||
public static final int DTBO_NOTIFICATION_ID = 7;
|
||||
public static final String NOTIFICATION_CHANNEL = "magisk_notification";
|
||||
}
|
||||
|
||||
public static class Url {
|
||||
public static final String STABLE_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/stable.json";
|
||||
public static final String BETA_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/beta.json";
|
||||
public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed&page=%d";
|
||||
public static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s";
|
||||
public static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
|
||||
public static final String PAYPAL_URL = "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=CC7FZ7526MNGG";
|
||||
public static final String PATREON_URL = "https://www.patreon.com/topjohnwu";
|
||||
public static final String TWITTER_URL = "https://twitter.com/topjohnwu";
|
||||
public static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382";
|
||||
public static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk";
|
||||
}
|
||||
|
||||
|
||||
public static class Key {
|
||||
// su
|
||||
public static final String ROOT_ACCESS = "root_access";
|
||||
public static final String SU_MULTIUSER_MODE = "multiuser_mode";
|
||||
public static final String SU_MNT_NS = "mnt_ns";
|
||||
public static final String SU_MANAGER = "requester";
|
||||
public static final String SU_REQUEST_TIMEOUT = "su_request_timeout";
|
||||
public static final String SU_AUTO_RESPONSE = "su_auto_response";
|
||||
public static final String SU_NOTIFICATION = "su_notification";
|
||||
public static final String SU_REAUTH = "su_reauth";
|
||||
public static final String SU_FINGERPRINT = "su_fingerprint";
|
||||
|
||||
// intents
|
||||
public static final String OPEN_SECTION = "section";
|
||||
public static final String INTENT_SET_FILENAME = "filename";
|
||||
public static final String INTENT_SET_LINK = "link";
|
||||
public static final String FLASH_ACTION = "action";
|
||||
public static final String FLASH_SET_BOOT = "boot";
|
||||
|
||||
// others
|
||||
public static final String CHECK_UPDATES = "check_update";
|
||||
public static final String UPDATE_CHANNEL = "update_channel";
|
||||
public static final String CUSTOM_CHANNEL = "custom_channel";
|
||||
public static final String BOOT_FORMAT = "boot_format";
|
||||
public static final String UPDATE_SERVICE_VER = "update_service_version";
|
||||
public static final String APP_VER = "app_version";
|
||||
public static final String MAGISKHIDE = "magiskhide";
|
||||
public static final String HOSTS = "hosts";
|
||||
public static final String COREONLY = "disable";
|
||||
public static final String LOCALE = "locale";
|
||||
public static final String DARK_THEME = "dark_theme";
|
||||
public static final String ETAG_KEY = "ETag";
|
||||
public static final String LINK_KEY = "Link";
|
||||
public static final String IF_NONE_MATCH = "If-None-Match";
|
||||
public static final String REPO_ORDER = "repo_order";
|
||||
}
|
||||
|
||||
|
||||
public static class Value {
|
||||
public static final int STABLE_CHANNEL = 0;
|
||||
public static final int BETA_CHANNEL = 1;
|
||||
public static final int CUSTOM_CHANNEL = 2;
|
||||
public static final int ROOT_ACCESS_DISABLED = 0;
|
||||
public static final int ROOT_ACCESS_APPS_ONLY = 1;
|
||||
public static final int ROOT_ACCESS_ADB_ONLY = 2;
|
||||
public static final int ROOT_ACCESS_APPS_AND_ADB = 3;
|
||||
public static final int MULTIUSER_MODE_OWNER_ONLY = 0;
|
||||
public static final int MULTIUSER_MODE_OWNER_MANAGED = 1;
|
||||
public static final int MULTIUSER_MODE_USER = 2;
|
||||
public static final int NAMESPACE_MODE_GLOBAL = 0;
|
||||
public static final int NAMESPACE_MODE_REQUESTER = 1;
|
||||
public static final int NAMESPACE_MODE_ISOLATE = 2;
|
||||
public static final int NO_NOTIFICATION = 0;
|
||||
public static final int NOTIFICATION_TOAST = 1;
|
||||
public static final int SU_PROMPT = 0;
|
||||
public static final int SU_AUTO_DENY = 1;
|
||||
public static final int SU_AUTO_ALLOW = 2;
|
||||
public static final String FLASH_ZIP = "flash";
|
||||
public static final String PATCH_BOOT = "patch";
|
||||
public static final String FLASH_MAGISK = "magisk";
|
||||
public static final String FLASH_INACTIVE_SLOT = "slot";
|
||||
public static final String UNINSTALL = "uninstall";
|
||||
public static final int[] timeoutList = {0, -1, 10, 20, 30, 60};
|
||||
public static final int ORDER_NAME = 0;
|
||||
public static final int ORDER_DATE = 1;
|
||||
}
|
||||
}
|
||||
218
app/src/full/java/com/topjohnwu/magisk/Data.java
Normal file
218
app/src/full/java/com/topjohnwu/magisk/Data.java
Normal file
@@ -0,0 +1,218 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.topjohnwu.magisk.components.AboutCardRow;
|
||||
import com.topjohnwu.magisk.receivers.BootReceiver;
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
import com.topjohnwu.magisk.receivers.PackageReceiver;
|
||||
import com.topjohnwu.magisk.receivers.RebootReceiver;
|
||||
import com.topjohnwu.magisk.receivers.ShortcutReceiver;
|
||||
import com.topjohnwu.magisk.services.OnBootService;
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Data {
|
||||
// Global app instance
|
||||
public static WeakReference<MagiskManager> weakApp;
|
||||
public static Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
public static Map<Class, Class> classMap = new HashMap<>();
|
||||
|
||||
// Current status
|
||||
public static String magiskVersionString;
|
||||
public static int magiskVersionCode = -1;
|
||||
public static boolean magiskHide;
|
||||
|
||||
// Update Info
|
||||
public static String remoteMagiskVersionString;
|
||||
public static int remoteMagiskVersionCode = -1;
|
||||
public static String magiskLink;
|
||||
public static String magiskNoteLink;
|
||||
public static String magiskMD5;
|
||||
public static String remoteManagerVersionString;
|
||||
public static int remoteManagerVersionCode = -1;
|
||||
public static String managerLink;
|
||||
public static String managerNoteLink;
|
||||
public static String uninstallerLink;
|
||||
public static int snetVersionCode;
|
||||
public static String snetLink;
|
||||
|
||||
// Install flags
|
||||
public static boolean keepVerity = false;
|
||||
public static boolean keepEnc = false;
|
||||
|
||||
// Configs
|
||||
public static boolean isDarkTheme;
|
||||
public static int suRequestTimeout;
|
||||
public static int suLogTimeout = 14;
|
||||
public static int suAccessState;
|
||||
public static boolean suFingerprint;
|
||||
public static int multiuserMode;
|
||||
public static int suResponseType;
|
||||
public static int suNotificationType;
|
||||
public static int suNamespaceMode;
|
||||
public static int updateChannel;
|
||||
public static int repoOrder;
|
||||
|
||||
static {
|
||||
classMap.put(MagiskManager.class, a.q.class);
|
||||
classMap.put(MainActivity.class, a.b.class);
|
||||
classMap.put(SplashActivity.class, a.c.class);
|
||||
classMap.put(AboutActivity.class, a.d.class);
|
||||
classMap.put(DonationActivity.class, a.e.class);
|
||||
classMap.put(FlashActivity.class, a.f.class);
|
||||
classMap.put(NoUIActivity.class, a.g.class);
|
||||
classMap.put(BootReceiver.class, a.h.class);
|
||||
classMap.put(PackageReceiver.class, a.i.class);
|
||||
classMap.put(ManagerUpdate.class, a.j.class);
|
||||
classMap.put(RebootReceiver.class, a.k.class);
|
||||
classMap.put(ShortcutReceiver.class, a.l.class);
|
||||
classMap.put(OnBootService.class, a.m.class);
|
||||
classMap.put(UpdateCheckService.class, a.n.class);
|
||||
classMap.put(AboutCardRow.class, a.o.class);
|
||||
classMap.put(SuRequestActivity.class, a.p.class);
|
||||
|
||||
}
|
||||
|
||||
public static void loadMagiskInfo() {
|
||||
try {
|
||||
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
|
||||
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
|
||||
String s = ShellUtils.fastCmd((magiskVersionCode >= Const.MAGISK_VER.RESETPROP_PERSIST ?
|
||||
"resetprop -p " : "getprop ") + Const.MAGISKHIDE_PROP);
|
||||
magiskHide = s.isEmpty() || Integer.parseInt(s) != 0;
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
public static MagiskManager MM() {
|
||||
return weakApp.get();
|
||||
}
|
||||
|
||||
public static void exportPrefs() {
|
||||
// Flush prefs to disk
|
||||
MagiskManager mm = MM();
|
||||
mm.prefs.edit().commit();
|
||||
File xml = new File(mm.getFilesDir().getParent() + "/shared_prefs",
|
||||
mm.getPackageName() + "_preferences.xml");
|
||||
Shell.su(Utils.fmt("for usr in /data/user/*; do cat %s > ${usr}/%s; done", xml, Const.MANAGER_CONFIGS)).exec();
|
||||
}
|
||||
|
||||
public static void importPrefs() {
|
||||
MagiskManager mm = MM();
|
||||
SuFile config = new SuFile(Utils.fmt("/data/user/%d/%s", Const.USER_ID, Const.MANAGER_CONFIGS));
|
||||
if (config.exists()) {
|
||||
SharedPreferences.Editor editor = mm.prefs.edit();
|
||||
try {
|
||||
SuFileInputStream is = new SuFileInputStream(config);
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
|
||||
parser.setInput(is, "UTF-8");
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.START_TAG, null, "map");
|
||||
while (parser.next() != XmlPullParser.END_TAG) {
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG)
|
||||
continue;
|
||||
String key = parser.getAttributeValue(null, "name");
|
||||
String value = parser.getAttributeValue(null, "value");
|
||||
switch (parser.getName()) {
|
||||
case "string":
|
||||
parser.require(XmlPullParser.START_TAG, null, "string");
|
||||
editor.putString(key, parser.nextText());
|
||||
parser.require(XmlPullParser.END_TAG, null, "string");
|
||||
break;
|
||||
case "boolean":
|
||||
parser.require(XmlPullParser.START_TAG, null, "boolean");
|
||||
editor.putBoolean(key, Boolean.parseBoolean(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "boolean");
|
||||
break;
|
||||
case "int":
|
||||
parser.require(XmlPullParser.START_TAG, null, "int");
|
||||
editor.putInt(key, Integer.parseInt(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "int");
|
||||
break;
|
||||
case "long":
|
||||
parser.require(XmlPullParser.START_TAG, null, "long");
|
||||
editor.putLong(key, Long.parseLong(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "long");
|
||||
break;
|
||||
case "float":
|
||||
parser.require(XmlPullParser.START_TAG, null, "int");
|
||||
editor.putFloat(key, Float.parseFloat(value));
|
||||
parser.nextTag();
|
||||
parser.require(XmlPullParser.END_TAG, null, "int");
|
||||
break;
|
||||
default:
|
||||
parser.next();
|
||||
}
|
||||
}
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
editor.remove(Const.Key.ETAG_KEY);
|
||||
editor.apply();
|
||||
loadConfig();
|
||||
config.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadConfig() {
|
||||
MagiskManager mm = MM();
|
||||
// su
|
||||
suRequestTimeout = Utils.getPrefsInt(mm.prefs, Const.Key.SU_REQUEST_TIMEOUT, Const.Value.timeoutList[2]);
|
||||
suResponseType = Utils.getPrefsInt(mm.prefs, Const.Key.SU_AUTO_RESPONSE, Const.Value.SU_PROMPT);
|
||||
suNotificationType = Utils.getPrefsInt(mm.prefs, Const.Key.SU_NOTIFICATION, Const.Value.NOTIFICATION_TOAST);
|
||||
suAccessState = mm.mDB.getSettings(Const.Key.ROOT_ACCESS, Const.Value.ROOT_ACCESS_APPS_AND_ADB);
|
||||
multiuserMode = mm.mDB.getSettings(Const.Key.SU_MULTIUSER_MODE, Const.Value.MULTIUSER_MODE_OWNER_ONLY);
|
||||
suNamespaceMode = mm.mDB.getSettings(Const.Key.SU_MNT_NS, Const.Value.NAMESPACE_MODE_REQUESTER);
|
||||
suFingerprint = mm.mDB.getSettings(Const.Key.SU_FINGERPRINT, 0) != 0;
|
||||
if (suFingerprint && !FingerprintHelper.canUseFingerprint()) {
|
||||
// User revoked the fingerprint
|
||||
mm.mDB.setSettings(Const.Key.SU_FINGERPRINT, 0);
|
||||
suFingerprint = false;
|
||||
}
|
||||
|
||||
// config
|
||||
isDarkTheme = mm.prefs.getBoolean(Const.Key.DARK_THEME, false);
|
||||
updateChannel = Utils.getPrefsInt(mm.prefs, Const.Key.UPDATE_CHANNEL, Const.Value.STABLE_CHANNEL);
|
||||
repoOrder = mm.prefs.getInt(Const.Key.REPO_ORDER, Const.Value.ORDER_DATE);
|
||||
}
|
||||
|
||||
public static void writeConfig() {
|
||||
MM().prefs.edit()
|
||||
.putBoolean(Const.Key.DARK_THEME, isDarkTheme)
|
||||
.putBoolean(Const.Key.MAGISKHIDE, magiskHide)
|
||||
.putBoolean(Const.Key.HOSTS, Const.MAGISK_HOST_FILE.exists())
|
||||
.putBoolean(Const.Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
|
||||
.putBoolean(Const.Key.SU_FINGERPRINT, suFingerprint)
|
||||
.putString(Const.Key.SU_REQUEST_TIMEOUT, String.valueOf(suRequestTimeout))
|
||||
.putString(Const.Key.SU_AUTO_RESPONSE, String.valueOf(suResponseType))
|
||||
.putString(Const.Key.SU_NOTIFICATION, String.valueOf(suNotificationType))
|
||||
.putString(Const.Key.ROOT_ACCESS, String.valueOf(suAccessState))
|
||||
.putString(Const.Key.SU_MULTIUSER_MODE, String.valueOf(multiuserMode))
|
||||
.putString(Const.Key.SU_MNT_NS, String.valueOf(suNamespaceMode))
|
||||
.putString(Const.Key.UPDATE_CHANNEL, String.valueOf(updateChannel))
|
||||
.putInt(Const.Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
|
||||
.putInt(Const.Key.REPO_ORDER, repoOrder)
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
43
app/src/full/java/com/topjohnwu/magisk/DonationActivity.java
Normal file
43
app/src/full/java/com/topjohnwu/magisk/DonationActivity.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.topjohnwu.magisk.components.AboutCardRow;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
public class DonationActivity extends BaseActivity {
|
||||
|
||||
Toolbar toolbar;
|
||||
AboutCardRow paypal;
|
||||
AboutCardRow patreon;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_StatusBar_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_donation);
|
||||
ViewBinder.bind(this);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
toolbar.setNavigationOnClickListener(view -> finish());
|
||||
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setTitle(R.string.donation);
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
paypal.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.PAYPAL_URL)));
|
||||
patreon.setOnClickListener(v -> Utils.openLink(this, Uri.parse(Const.Url.PATREON_URL)));
|
||||
}
|
||||
}
|
||||
164
app/src/full/java/com/topjohnwu/magisk/FlashActivity.java
Normal file
164
app/src/full/java/com/topjohnwu/magisk/FlashActivity.java
Normal file
@@ -0,0 +1,164 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.FlashZip;
|
||||
import com.topjohnwu.magisk.asyncs.InstallMagisk;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.CallbackList;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
public class FlashActivity extends BaseActivity {
|
||||
|
||||
Toolbar toolbar;
|
||||
TextView flashLogs;
|
||||
public LinearLayout buttonPanel;
|
||||
public Button reboot;
|
||||
ScrollView sv;
|
||||
|
||||
private List<String> logs;
|
||||
|
||||
void dismiss() {
|
||||
finish();
|
||||
}
|
||||
|
||||
void reboot() {
|
||||
Shell.su("/system/bin/reboot").submit();
|
||||
}
|
||||
|
||||
void saveLogs() {
|
||||
runWithPermission(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, () -> {
|
||||
Calendar now = Calendar.getInstance();
|
||||
String filename = String.format(Locale.US,
|
||||
"magisk_install_log_%04d%02d%02d_%02d%02d%02d.log",
|
||||
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
|
||||
|
||||
File logFile = new File(Download.EXTERNAL_PATH, filename);
|
||||
try (FileWriter writer = new FileWriter(logFile)) {
|
||||
for (String s : logs) {
|
||||
writer.write(s);
|
||||
writer.write('\n');
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
Utils.toast(logFile.getPath(), Toast.LENGTH_LONG);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_StatusBar_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_flash);
|
||||
ViewBinder.bind(this);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setTitle(R.string.flashing);
|
||||
}
|
||||
setFloating();
|
||||
setFinishOnTouchOutside(false);
|
||||
if (!Shell.rootAccess())
|
||||
reboot.setVisibility(View.GONE);
|
||||
|
||||
logs = new ArrayList<>();
|
||||
CallbackList<String> console = new CallbackList<String>(new ArrayList<>()) {
|
||||
|
||||
private void updateUI() {
|
||||
flashLogs.setText(TextUtils.join("\n", this));
|
||||
sv.postDelayed(() -> sv.fullScroll(ScrollView.FOCUS_DOWN), 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddElement(String s) {
|
||||
logs.add(s);
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String set(int i, String s) {
|
||||
String ret = super.set(i, s);
|
||||
Data.mainHandler.post(this::updateUI);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
// We must receive a Uri of the target zip
|
||||
Intent intent = getIntent();
|
||||
Uri uri = intent.getData();
|
||||
|
||||
switch (intent.getStringExtra(Const.Key.FLASH_ACTION)) {
|
||||
case Const.Value.FLASH_ZIP:
|
||||
new FlashZip(this, uri, console, logs).exec();
|
||||
break;
|
||||
case Const.Value.UNINSTALL:
|
||||
new UninstallMagisk(this, uri, console, logs).exec();
|
||||
break;
|
||||
case Const.Value.FLASH_MAGISK:
|
||||
new InstallMagisk(this, console, logs, InstallMagisk.DIRECT_MODE).exec();
|
||||
break;
|
||||
case Const.Value.FLASH_INACTIVE_SLOT:
|
||||
new InstallMagisk(this, console, logs, InstallMagisk.SECOND_SLOT_MODE).exec();
|
||||
break;
|
||||
case Const.Value.PATCH_BOOT:
|
||||
runWithPermission(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
() -> new InstallMagisk(this, console, logs,
|
||||
intent.getParcelableExtra(Const.Key.FLASH_SET_BOOT)).exec());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Prevent user accidentally press back button
|
||||
}
|
||||
|
||||
private static class UninstallMagisk extends FlashZip {
|
||||
|
||||
private UninstallMagisk(BaseActivity context, Uri uri, List<String> console, List<String> logs) {
|
||||
super(context, uri, console, logs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
if (result == 1) {
|
||||
Data.mainHandler.postDelayed(() ->
|
||||
RootUtils.uninstallPkg(getActivity().getPackageName()), 3000);
|
||||
} else {
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
app/src/full/java/com/topjohnwu/magisk/MagiskManager.java
Normal file
65
app/src/full/java/com/topjohnwu/magisk/MagiskManager.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.database.MagiskDatabaseHelper;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.superuser.ContainerApp;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class MagiskManager extends ContainerApp {
|
||||
|
||||
// Info
|
||||
public boolean hasInit = false;
|
||||
|
||||
// Global resources
|
||||
public SharedPreferences prefs;
|
||||
public MagiskDatabaseHelper mDB;
|
||||
public RepoDatabaseHelper repoDB;
|
||||
|
||||
public MagiskManager() {
|
||||
Data.weakApp = new WeakReference<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER);
|
||||
Shell.Config.verboseLogging(BuildConfig.DEBUG);
|
||||
Shell.Config.setInitializer(RootUtils.class);
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
mDB = MagiskDatabaseHelper.getInstance(this);
|
||||
|
||||
String pkg = mDB.getStrings(Const.Key.SU_MANAGER, null);
|
||||
if (pkg != null && getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
mDB.setStrings(Const.Key.SU_MANAGER, null);
|
||||
Shell.su("pm uninstall " + pkg).exec();
|
||||
}
|
||||
if (TextUtils.equals(pkg, getPackageName())) {
|
||||
try {
|
||||
// We are the manager, remove com.topjohnwu.magisk as it could be malware
|
||||
getPackageManager().getApplicationInfo(Const.ORIG_PKG_NAME, 0);
|
||||
RootUtils.uninstallPkg(Const.ORIG_PKG_NAME);
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
}
|
||||
|
||||
LocaleManager.setLocale(this);
|
||||
Data.loadConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
LocaleManager.setLocale(this);
|
||||
}
|
||||
}
|
||||
211
app/src/full/java/com/topjohnwu/magisk/MainActivity.java
Normal file
211
app/src/full/java/com/topjohnwu/magisk/MainActivity.java
Normal file
@@ -0,0 +1,211 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.fragments.LogFragment;
|
||||
import com.topjohnwu.magisk.fragments.MagiskFragment;
|
||||
import com.topjohnwu.magisk.fragments.MagiskHideFragment;
|
||||
import com.topjohnwu.magisk.fragments.ModulesFragment;
|
||||
import com.topjohnwu.magisk.fragments.ReposFragment;
|
||||
import com.topjohnwu.magisk.fragments.SettingsFragment;
|
||||
import com.topjohnwu.magisk.fragments.SuperuserFragment;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
public class MainActivity extends BaseActivity
|
||||
implements NavigationView.OnNavigationItemSelectedListener, Topic.Subscriber {
|
||||
|
||||
private final Handler mDrawerHandler = new Handler();
|
||||
private int mDrawerItem;
|
||||
private static boolean fromShortcut = false;
|
||||
|
||||
public Toolbar toolbar;
|
||||
DrawerLayout drawer;
|
||||
NavigationView navigationView;
|
||||
|
||||
private float toolbarElevation;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
if (!mm.hasInit) {
|
||||
startActivity(new Intent(this, Data.classMap.get(SplashActivity.class)));
|
||||
finish();
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
ViewBinder.bind(this);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.magisk, R.string.magisk) {
|
||||
@Override
|
||||
public void onDrawerOpened(View drawerView) {
|
||||
super.onDrawerOpened(drawerView);
|
||||
super.onDrawerSlide(drawerView, 0); // this disables the arrow @ completed tate
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawerSlide(View drawerView, float slideOffset) {
|
||||
super.onDrawerSlide(drawerView, 0); // this disables the animation
|
||||
}
|
||||
};
|
||||
|
||||
toolbarElevation = toolbar.getElevation();
|
||||
|
||||
drawer.addDrawerListener(toggle);
|
||||
toggle.syncState();
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
String section = getIntent().getStringExtra(Const.Key.OPEN_SECTION);
|
||||
fromShortcut = section != null;
|
||||
navigate(section);
|
||||
}
|
||||
|
||||
navigationView.setNavigationItemSelectedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
checkHideSection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (drawer.isDrawerOpen(navigationView)) {
|
||||
drawer.closeDrawer(navigationView);
|
||||
} else if (mDrawerItem != R.id.magisk && !fromShortcut) {
|
||||
navigate(R.id.magisk);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(@NonNull final MenuItem menuItem) {
|
||||
mDrawerHandler.removeCallbacksAndMessages(null);
|
||||
mDrawerHandler.postDelayed(() -> navigate(menuItem.getItemId()), 250);
|
||||
drawer.closeDrawer(navigationView);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
recreate();
|
||||
}
|
||||
|
||||
public void checkHideSection() {
|
||||
Menu menu = navigationView.getMenu();
|
||||
menu.findItem(R.id.magiskhide).setVisible(Shell.rootAccess() &&
|
||||
Data.magiskVersionCode >= Const.MAGISK_VER.UNIFIED &&
|
||||
mm.prefs.getBoolean(Const.Key.MAGISKHIDE, false));
|
||||
menu.findItem(R.id.modules).setVisible(Shell.rootAccess() && Data.magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.downloads).setVisible(Download.checkNetworkStatus(this)
|
||||
&& Shell.rootAccess() && Data.magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.log).setVisible(Shell.rootAccess());
|
||||
menu.findItem(R.id.superuser).setVisible(Shell.rootAccess() &&
|
||||
!(Const.USER_ID > 0 && Data.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED));
|
||||
}
|
||||
|
||||
public void navigate(String item) {
|
||||
int itemId = R.id.magisk;
|
||||
if (item != null) {
|
||||
switch (item) {
|
||||
case "superuser":
|
||||
itemId = R.id.superuser;
|
||||
break;
|
||||
case "modules":
|
||||
itemId = R.id.modules;
|
||||
break;
|
||||
case "downloads":
|
||||
itemId = R.id.downloads;
|
||||
break;
|
||||
case "magiskhide":
|
||||
itemId = R.id.magiskhide;
|
||||
break;
|
||||
case "log":
|
||||
itemId = R.id.log;
|
||||
break;
|
||||
case "settings":
|
||||
itemId = R.id.settings;
|
||||
break;
|
||||
case "about":
|
||||
itemId = R.id.app_about;
|
||||
break;
|
||||
case "donation":
|
||||
itemId = R.id.donation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
navigate(itemId);
|
||||
}
|
||||
|
||||
public void navigate(int itemId) {
|
||||
int bak = mDrawerItem;
|
||||
mDrawerItem = itemId;
|
||||
navigationView.setCheckedItem(itemId);
|
||||
switch (itemId) {
|
||||
case R.id.magisk:
|
||||
fromShortcut = false;
|
||||
displayFragment(new MagiskFragment(), true);
|
||||
break;
|
||||
case R.id.superuser:
|
||||
displayFragment(new SuperuserFragment(), true);
|
||||
break;
|
||||
case R.id.modules:
|
||||
displayFragment(new ModulesFragment(), true);
|
||||
break;
|
||||
case R.id.downloads:
|
||||
displayFragment(new ReposFragment(), true);
|
||||
break;
|
||||
case R.id.magiskhide:
|
||||
displayFragment(new MagiskHideFragment(), true);
|
||||
break;
|
||||
case R.id.log:
|
||||
displayFragment(new LogFragment(), false);
|
||||
break;
|
||||
case R.id.settings:
|
||||
displayFragment(new SettingsFragment(), true);
|
||||
break;
|
||||
case R.id.app_about:
|
||||
startActivity(new Intent(this, Data.classMap.get(AboutActivity.class)));
|
||||
mDrawerItem = bak;
|
||||
break;
|
||||
case R.id.donation:
|
||||
startActivity(new Intent(this, Data.classMap.get(DonationActivity.class)));
|
||||
mDrawerItem = bak;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void displayFragment(@NonNull Fragment navFragment, boolean setElevation) {
|
||||
supportInvalidateOptionsMenu();
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
.replace(R.id.content_frame, navFragment)
|
||||
.commitNow();
|
||||
toolbar.setElevation(setElevation ? toolbarElevation : 0);
|
||||
}
|
||||
}
|
||||
13
app/src/full/java/com/topjohnwu/magisk/NoUIActivity.java
Normal file
13
app/src/full/java/com/topjohnwu/magisk/NoUIActivity.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class NoUIActivity extends BaseActivity {
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
67
app/src/full/java/com/topjohnwu/magisk/SplashActivity.java
Normal file
67
app/src/full/java/com/topjohnwu/magisk/SplashActivity.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.receivers.ShortcutReceiver;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class SplashActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Magisk working as expected
|
||||
if (Shell.rootAccess() && Data.magiskVersionCode > 0) {
|
||||
// Update check service
|
||||
Utils.setupUpdateCheck();
|
||||
// Load modules
|
||||
Utils.loadModules();
|
||||
}
|
||||
|
||||
mm.repoDB = new RepoDatabaseHelper(this);
|
||||
Data.importPrefs();
|
||||
|
||||
// Dynamic detect all locales
|
||||
LocaleManager.loadAvailableLocales();
|
||||
|
||||
// Create notification channel on Android O
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(Const.ID.NOTIFICATION_CHANNEL,
|
||||
getString(R.string.magisk_updates), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
getSystemService(NotificationManager.class).createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
// Setup shortcuts
|
||||
sendBroadcast(new Intent(this, Data.classMap.get(ShortcutReceiver.class)));
|
||||
|
||||
if (Download.checkNetworkStatus(this)) {
|
||||
// Fire update check
|
||||
CheckUpdates.check();
|
||||
// Repo update check
|
||||
new UpdateRepos().exec();
|
||||
}
|
||||
|
||||
// Write back default values
|
||||
Data.writeConfig();
|
||||
|
||||
mm.hasInit = true;
|
||||
|
||||
Intent intent = new Intent(this, Data.classMap.get(MainActivity.class));
|
||||
intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION));
|
||||
intent.putExtra(BaseActivity.INTENT_PERM, getIntent().getStringExtra(BaseActivity.INTENT_PERM));
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
258
app/src/full/java/com/topjohnwu/magisk/SuRequestActivity.java
Normal file
258
app/src/full/java/com/topjohnwu/magisk/SuRequestActivity.java
Normal file
@@ -0,0 +1,258 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.FileObserver;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.SuConnector;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class SuRequestActivity extends BaseActivity {
|
||||
LinearLayout suPopup;
|
||||
Spinner timeout;
|
||||
ImageView appIcon;
|
||||
TextView appNameView;
|
||||
TextView packageNameView;
|
||||
Button grant_btn;
|
||||
Button deny_btn;
|
||||
ImageView fingerprintImg;
|
||||
TextView warning;
|
||||
|
||||
private SuConnector connector;
|
||||
private Policy policy;
|
||||
private CountDownTimer timer;
|
||||
private FingerprintHelper fingerprintHelper;
|
||||
|
||||
class SuConnectorV1 extends SuConnector {
|
||||
|
||||
SuConnectorV1(String name) throws IOException {
|
||||
socket.connect(new LocalSocketAddress(name, LocalSocketAddress.Namespace.FILESYSTEM));
|
||||
new FileObserver(name) {
|
||||
@Override
|
||||
public void onEvent(int fileEvent, String path) {
|
||||
if (fileEvent == FileObserver.DELETE_SELF) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}.startWatching();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void response() {
|
||||
try (OutputStream out = getOutputStream()) {
|
||||
out.write((policy.policy == Policy.ALLOW ? "socket:ALLOW" : "socket:DENY").getBytes());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SuConnectorV2 extends SuConnector {
|
||||
|
||||
SuConnectorV2(String name) throws IOException {
|
||||
socket.connect(new LocalSocketAddress(name, LocalSocketAddress.Namespace.ABSTRACT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void response() {
|
||||
try (DataOutputStream out = getOutputStream()) {
|
||||
out.writeInt(policy.policy);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.SuRequest_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (timer != null)
|
||||
timer.cancel();
|
||||
if (fingerprintHelper != null)
|
||||
fingerprintHelper.cancel();
|
||||
super.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (policy != null) {
|
||||
handleAction(Policy.DENY);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
PackageManager pm = getPackageManager();
|
||||
mm.mDB.clearOutdated();
|
||||
|
||||
// Get policy
|
||||
Intent intent = getIntent();
|
||||
try {
|
||||
connector = intent.getIntExtra("version", 1) == 1 ?
|
||||
new SuConnectorV1(intent.getStringExtra("socket")) :
|
||||
new SuConnectorV2(intent.getStringExtra("socket"));
|
||||
Bundle bundle = connector.readSocketInput();
|
||||
int uid = Integer.parseInt(bundle.getString("uid"));
|
||||
policy = mm.mDB.getPolicy(uid);
|
||||
if (policy == null) {
|
||||
policy = new Policy(uid, pm);
|
||||
}
|
||||
} catch (IOException | PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Never allow com.topjohnwu.magisk (could be malware)
|
||||
if (TextUtils.equals(policy.packageName, Const.ORIG_PKG_NAME)) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (Data.suResponseType) {
|
||||
case Const.Value.SU_AUTO_DENY:
|
||||
handleAction(Policy.DENY, 0);
|
||||
return;
|
||||
case Const.Value.SU_AUTO_ALLOW:
|
||||
handleAction(Policy.ALLOW, 0);
|
||||
return;
|
||||
case Const.Value.SU_PROMPT:
|
||||
default:
|
||||
}
|
||||
|
||||
// If not interactive, response directly
|
||||
if (policy.policy != Policy.INTERACTIVE) {
|
||||
handleAction();
|
||||
return;
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_request);
|
||||
ViewBinder.bind(this);
|
||||
|
||||
appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
||||
appNameView.setText(policy.appName);
|
||||
packageNameView.setText(policy.packageName);
|
||||
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
|
||||
R.array.allow_timeout, android.R.layout.simple_spinner_item);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
timeout.setAdapter(adapter);
|
||||
|
||||
timer = new CountDownTimer(Data.suRequestTimeout * 1000, 1000) {
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
deny_btn.setText(getString(R.string.deny_with_str, "(" + millisUntilFinished / 1000 + ")"));
|
||||
}
|
||||
@Override
|
||||
public void onFinish() {
|
||||
deny_btn.setText(getString(R.string.deny_with_str, "(0)"));
|
||||
handleAction(Policy.DENY);
|
||||
}
|
||||
};
|
||||
|
||||
boolean useFingerprint = Data.suFingerprint && FingerprintHelper.canUseFingerprint();
|
||||
|
||||
if (useFingerprint) {
|
||||
try {
|
||||
fingerprintHelper = new FingerprintHelper() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
warning.setText(errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
warning.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
handleAction(Policy.ALLOW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
warning.setText(R.string.auth_fail);
|
||||
}
|
||||
};
|
||||
fingerprintHelper.authenticate();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
useFingerprint = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!useFingerprint) {
|
||||
grant_btn.setOnClickListener(v -> {
|
||||
handleAction(Policy.ALLOW);
|
||||
timer.cancel();
|
||||
});
|
||||
grant_btn.requestFocus();
|
||||
}
|
||||
|
||||
grant_btn.setVisibility(useFingerprint ? View.GONE : View.VISIBLE);
|
||||
fingerprintImg.setVisibility(useFingerprint ? View.VISIBLE : View.GONE);
|
||||
|
||||
deny_btn.setOnClickListener(v -> {
|
||||
handleAction(Policy.DENY);
|
||||
timer.cancel();
|
||||
});
|
||||
suPopup.setOnClickListener(v -> cancelTimeout());
|
||||
timeout.setOnTouchListener((v, event) -> cancelTimeout());
|
||||
timer.start();
|
||||
}
|
||||
|
||||
private boolean cancelTimeout() {
|
||||
timer.cancel();
|
||||
deny_btn.setText(getString(R.string.deny));
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleAction() {
|
||||
connector.response();
|
||||
finish();
|
||||
}
|
||||
|
||||
private void handleAction(int action) {
|
||||
handleAction(action, Const.Value.timeoutList[timeout.getSelectedItemPosition()]);
|
||||
}
|
||||
|
||||
private void handleAction(int action, int time) {
|
||||
policy.policy = action;
|
||||
if (time >= 0) {
|
||||
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
|
||||
mm.mDB.addPolicy(policy);
|
||||
}
|
||||
handleAction();
|
||||
}
|
||||
}
|
||||
293
app/src/full/java/com/topjohnwu/magisk/ViewBinder.java
Normal file
293
app/src/full/java/com/topjohnwu/magisk/ViewBinder.java
Normal file
@@ -0,0 +1,293 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
|
||||
import com.topjohnwu.magisk.adapters.ModulesAdapter;
|
||||
import com.topjohnwu.magisk.adapters.PolicyAdapter;
|
||||
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
||||
import com.topjohnwu.magisk.adapters.SuLogAdapter;
|
||||
import com.topjohnwu.magisk.components.AboutCardRow;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.fragments.LogFragment;
|
||||
import com.topjohnwu.magisk.fragments.MagiskFragment;
|
||||
import com.topjohnwu.magisk.fragments.MagiskHideFragment;
|
||||
import com.topjohnwu.magisk.fragments.MagiskLogFragment;
|
||||
import com.topjohnwu.magisk.fragments.ModulesFragment;
|
||||
import com.topjohnwu.magisk.fragments.ReposFragment;
|
||||
import com.topjohnwu.magisk.fragments.SuLogFragment;
|
||||
import com.topjohnwu.magisk.fragments.SuperuserFragment;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
public class ViewBinder {
|
||||
|
||||
public static void bind(MainActivity target) {
|
||||
target.drawer = target.findViewById(R.id.drawer_layout);
|
||||
target.toolbar = target.findViewById(R.id.toolbar);
|
||||
target.navigationView = target.findViewById(R.id.nav_view);
|
||||
}
|
||||
|
||||
public static void bind(AboutActivity target) {
|
||||
target.toolbar = target.findViewById(R.id.toolbar);
|
||||
target.appVersionInfo = target.findViewById(R.id.app_version_info);
|
||||
target.appChangelog = target.findViewById(R.id.app_changelog);
|
||||
target.appTranslators = target.findViewById(R.id.app_translators);
|
||||
target.appSourceCode = target.findViewById(R.id.app_source_code);
|
||||
target.supportThread = target.findViewById(R.id.support_thread);
|
||||
target.twitter = target.findViewById(R.id.follow_twitter);
|
||||
}
|
||||
|
||||
public static void bind(DonationActivity target) {
|
||||
target.toolbar = target.findViewById(R.id.toolbar);
|
||||
target.paypal = target.findViewById(R.id.paypal);
|
||||
target.patreon = target.findViewById(R.id.patreon);
|
||||
}
|
||||
|
||||
public static void bind(FlashActivity target) {
|
||||
target.toolbar = target.findViewById(R.id.toolbar);
|
||||
target.flashLogs = target.findViewById(R.id.txtLog);
|
||||
target.buttonPanel = target.findViewById(R.id.button_panel);
|
||||
target.sv = target.findViewById(R.id.scrollView);
|
||||
target.reboot = target.findViewById(R.id.reboot);
|
||||
target.reboot.setOnClickListener(v -> target.reboot());
|
||||
target.findViewById(R.id.no_thanks).setOnClickListener(v -> target.finish());
|
||||
target.findViewById(R.id.save_logs).setOnClickListener(v -> target.saveLogs());
|
||||
}
|
||||
|
||||
public static void bind(SuRequestActivity target) {
|
||||
target.suPopup = target.findViewById(R.id.su_popup);
|
||||
target.timeout = target.findViewById(R.id.timeout);
|
||||
target.appIcon = target.findViewById(R.id.app_icon);
|
||||
target.appNameView = target.findViewById(R.id.app_name);
|
||||
target.packageNameView = target.findViewById(R.id.package_name);
|
||||
target.grant_btn = target.findViewById(R.id.grant_btn);
|
||||
target.deny_btn = target.findViewById(R.id.deny_btn);
|
||||
target.fingerprintImg = target.findViewById(R.id.fingerprint);
|
||||
target.warning = target.findViewById(R.id.warning);
|
||||
}
|
||||
|
||||
public static void bind(LogFragment target, View v) {
|
||||
target.viewPager = v.findViewById(R.id.container);
|
||||
target.tab = v.findViewById(R.id.tab);
|
||||
}
|
||||
|
||||
public static void unbind(LogFragment target) {
|
||||
target.viewPager = null;
|
||||
target.tab = null;
|
||||
}
|
||||
|
||||
public static void bind(MagiskFragment target, View v) {
|
||||
target.mSwipeRefreshLayout = v.findViewById(R.id.swipeRefreshLayout);
|
||||
target.coreOnlyNotice = v.findViewById(R.id.core_only_notice);
|
||||
target.magiskUpdate = v.findViewById(R.id.magisk_update);
|
||||
target.magiskUpdateIcon = v.findViewById(R.id.magisk_update_icon);
|
||||
target.magiskUpdateText = v.findViewById(R.id.magisk_update_status);
|
||||
target.magiskUpdateProgress = v.findViewById(R.id.magisk_update_progress);
|
||||
target.magiskStatusIcon = v.findViewById(R.id.magisk_status_icon);
|
||||
target.magiskVersionText = v.findViewById(R.id.magisk_version);
|
||||
target.safetyNetCard = v.findViewById(R.id.safetyNet_card);
|
||||
target.safetyNetRefreshIcon = v.findViewById(R.id.safetyNet_refresh);
|
||||
target.safetyNetStatusText = v.findViewById(R.id.safetyNet_status);
|
||||
target.safetyNetProgress = v.findViewById(R.id.safetyNet_check_progress);
|
||||
target.expandLayout = v.findViewById(R.id.expand_layout);
|
||||
target.ctsStatusIcon = v.findViewById(R.id.cts_status_icon);
|
||||
target.ctsStatusText = v.findViewById(R.id.cts_status);
|
||||
target.basicStatusIcon = v.findViewById(R.id.basic_status_icon);
|
||||
target.basicStatusText = v.findViewById(R.id.basic_status);
|
||||
target.installOptionCard = v.findViewById(R.id.install_option_card);
|
||||
target.keepEncChkbox = v.findViewById(R.id.keep_force_enc);
|
||||
target.keepVerityChkbox = v.findViewById(R.id.keep_verity);
|
||||
target.installButton = v.findViewById(R.id.install_button);
|
||||
target.installText = v.findViewById(R.id.install_text);
|
||||
target.uninstallButton = v.findViewById(R.id.uninstall_button);
|
||||
|
||||
v.findViewById(R.id.safetyNet_title).setOnClickListener(v1 -> target.safetyNet());
|
||||
v.findViewById(R.id.install_button).setOnClickListener(v1 -> target.install());
|
||||
v.findViewById(R.id.uninstall_button).setOnClickListener(v1 -> target.uninstall());
|
||||
|
||||
Context ctx = target.getContext();
|
||||
target.colorBad = ContextCompat.getColor(ctx, R.color.red500);
|
||||
target.colorOK = ContextCompat.getColor(ctx, R.color.green500);
|
||||
target.colorWarn = ContextCompat.getColor(ctx, R.color.yellow500);
|
||||
target.colorNeutral = ContextCompat.getColor(ctx, R.color.grey500);
|
||||
target.colorInfo = ContextCompat.getColor(ctx, R.color.blue500);
|
||||
}
|
||||
|
||||
public static void unbind(MagiskFragment target) {
|
||||
target.mSwipeRefreshLayout = null;
|
||||
target.coreOnlyNotice = null;
|
||||
target.magiskUpdate = null;
|
||||
target.magiskUpdateIcon = null;
|
||||
target.magiskUpdateText = null;
|
||||
target.magiskUpdateProgress = null;
|
||||
target.magiskStatusIcon = null;
|
||||
target.magiskVersionText = null;
|
||||
target.safetyNetCard = null;
|
||||
target.safetyNetRefreshIcon = null;
|
||||
target.safetyNetStatusText = null;
|
||||
target.safetyNetProgress = null;
|
||||
target.expandLayout = null;
|
||||
target.ctsStatusIcon = null;
|
||||
target.ctsStatusText = null;
|
||||
target.basicStatusIcon = null;
|
||||
target.basicStatusText = null;
|
||||
target.installOptionCard = null;
|
||||
target.keepEncChkbox = null;
|
||||
target.keepVerityChkbox = null;
|
||||
target.installButton = null;
|
||||
target.installText = null;
|
||||
target.uninstallButton = null;
|
||||
|
||||
View v = target.getView();
|
||||
v.findViewById(R.id.safetyNet_title).setOnClickListener(null);
|
||||
v.findViewById(R.id.install_button).setOnClickListener(null);
|
||||
v.findViewById(R.id.uninstall_button).setOnClickListener(null);
|
||||
}
|
||||
|
||||
public static void bind(MagiskHideFragment target, View v) {
|
||||
target.mSwipeRefreshLayout = v.findViewById(R.id.swipeRefreshLayout);
|
||||
target.recyclerView = v.findViewById(R.id.recyclerView);
|
||||
}
|
||||
|
||||
public static void unbind(MagiskHideFragment target) {
|
||||
target.mSwipeRefreshLayout = null;
|
||||
target.recyclerView = null;
|
||||
}
|
||||
|
||||
public static void bind(MagiskLogFragment target, View v) {
|
||||
target.txtLog = v.findViewById(R.id.txtLog);
|
||||
target.svLog = v.findViewById(R.id.svLog);
|
||||
target.hsvLog = v.findViewById(R.id.hsvLog);
|
||||
target.progressBar = v.findViewById(R.id.progressBar);
|
||||
}
|
||||
|
||||
public static void unbind(MagiskLogFragment target) {
|
||||
target.txtLog = null;
|
||||
target.svLog = null;
|
||||
target.hsvLog = null;
|
||||
target.progressBar = null;
|
||||
}
|
||||
|
||||
public static void bind(ModulesFragment target, View v) {
|
||||
target.mSwipeRefreshLayout = v.findViewById(R.id.swipeRefreshLayout);
|
||||
target.recyclerView = v.findViewById(R.id.recyclerView);
|
||||
target.emptyRv = v.findViewById(R.id.empty_rv);
|
||||
v.findViewById(R.id.fab).setOnClickListener(v1 -> target.selectFile());
|
||||
}
|
||||
|
||||
public static void unbind(ModulesFragment target) {
|
||||
target.mSwipeRefreshLayout = null;
|
||||
target.recyclerView = null;
|
||||
target.emptyRv = null;
|
||||
View v = target.getView();
|
||||
v.findViewById(R.id.fab).setOnClickListener(null);
|
||||
}
|
||||
|
||||
public static void bind(ReposFragment target, View source) {
|
||||
target.recyclerView = source.findViewById(R.id.recyclerView);
|
||||
target.emptyRv = source.findViewById(R.id.empty_rv);
|
||||
target.mSwipeRefreshLayout = source.findViewById(R.id.swipeRefreshLayout);
|
||||
}
|
||||
|
||||
public static void unbind(ReposFragment target) {
|
||||
target.recyclerView = null;
|
||||
target.emptyRv = null;
|
||||
target.mSwipeRefreshLayout = null;
|
||||
}
|
||||
|
||||
public static void bind(SuLogFragment target, View source) {
|
||||
target.emptyRv = source.findViewById(R.id.empty_rv);
|
||||
target.recyclerView = source.findViewById(R.id.recyclerView);
|
||||
}
|
||||
|
||||
public static void unbind(SuLogFragment target) {
|
||||
target.emptyRv = null;
|
||||
target.recyclerView = null;
|
||||
}
|
||||
|
||||
public static void bind(SuperuserFragment target, View source) {
|
||||
target.recyclerView = source.findViewById(R.id.recyclerView);
|
||||
target.emptyRv = source.findViewById(R.id.empty_rv);
|
||||
}
|
||||
|
||||
public static void unbind(SuperuserFragment target) {
|
||||
target.emptyRv = null;
|
||||
target.recyclerView = null;
|
||||
}
|
||||
|
||||
public static void bind(CustomAlertDialog.ViewHolder target, View source) {
|
||||
target.dialogLayout = source.findViewById(R.id.dialog_layout);
|
||||
target.buttons = source.findViewById(R.id.button_panel);
|
||||
target.messageView = source.findViewById(R.id.message);
|
||||
target.negative = source.findViewById(R.id.negative);
|
||||
target.positive = source.findViewById(R.id.positive);
|
||||
target.neutral = source.findViewById(R.id.neutral);
|
||||
}
|
||||
|
||||
public static void bind(AboutCardRow target, View source) {
|
||||
target.mTitle = source.findViewById(android.R.id.title);
|
||||
target.mSummary = source.findViewById(android.R.id.summary);
|
||||
target.mIcon = source.findViewById(android.R.id.icon);
|
||||
target.mView = source.findViewById(R.id.container);
|
||||
}
|
||||
|
||||
public static void bind(ApplicationAdapter.ViewHolder target, View source) {
|
||||
target.appIcon = source.findViewById(R.id.app_icon);
|
||||
target.appName = source.findViewById(R.id.app_name);
|
||||
target.appPackage = source.findViewById(R.id.package_name);
|
||||
target.checkBox = source.findViewById(R.id.checkbox);
|
||||
}
|
||||
|
||||
public static void bind(ModulesAdapter.ViewHolder target, View source) {
|
||||
target.title = source.findViewById(R.id.title);
|
||||
target.versionName = source.findViewById(R.id.version_name);
|
||||
target.description = source.findViewById(R.id.description);
|
||||
target.notice = source.findViewById(R.id.notice);
|
||||
target.checkBox = source.findViewById(R.id.checkbox);
|
||||
target.author = source.findViewById(R.id.author);
|
||||
target.delete = source.findViewById(R.id.delete);
|
||||
}
|
||||
|
||||
public static void bind(PolicyAdapter.ViewHolder target, View source) {
|
||||
target.appName = source.findViewById(R.id.app_name);
|
||||
target.packageName = source.findViewById(R.id.package_name);
|
||||
target.appIcon = source.findViewById(R.id.app_icon);
|
||||
target.masterSwitch = source.findViewById(R.id.master_switch);
|
||||
target.notificationSwitch = source.findViewById(R.id.notification_switch);
|
||||
target.loggingSwitch = source.findViewById(R.id.logging_switch);
|
||||
target.expandLayout = source.findViewById(R.id.expand_layout);
|
||||
target.delete = source.findViewById(R.id.delete);
|
||||
target.moreInfo = source.findViewById(R.id.more_info);
|
||||
}
|
||||
|
||||
public static void bind(ReposAdapter.SectionHolder target, View source) {
|
||||
target.sectionText = source.findViewById(R.id.section_text);
|
||||
}
|
||||
|
||||
public static void bind(ReposAdapter.RepoHolder target, View source) {
|
||||
target.title = source.findViewById(R.id.title);
|
||||
target.versionName = source.findViewById(R.id.version_name);
|
||||
target.description = source.findViewById(R.id.description);
|
||||
target.author = source.findViewById(R.id.author);
|
||||
target.infoLayout = source.findViewById(R.id.info_layout);
|
||||
target.downloadImage = source.findViewById(R.id.download);
|
||||
target.updateTime = source.findViewById(R.id.update_time);
|
||||
}
|
||||
|
||||
public static void bind(SuLogAdapter.SectionHolder target, View source) {
|
||||
target.date = source.findViewById(R.id.date);
|
||||
target.arrow = source.findViewById(R.id.arrow);
|
||||
}
|
||||
|
||||
public static void bind(SuLogAdapter.LogViewHolder target, View source) {
|
||||
target.appName = source.findViewById(R.id.app_name);
|
||||
target.action = source.findViewById(R.id.action);
|
||||
target.time = source.findViewById(R.id.time);
|
||||
target.fromPid = source.findViewById(R.id.fromPid);
|
||||
target.toUid = source.findViewById(R.id.toUid);
|
||||
target.command = source.findViewById(R.id.command);
|
||||
target.expandLayout = source.findViewById(R.id.expand_layout);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.AsyncTask;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.Filter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> {
|
||||
|
||||
private List<ApplicationInfo> fullList, showList;
|
||||
private List<String> hideList;
|
||||
private PackageManager pm;
|
||||
private ApplicationFilter filter;
|
||||
|
||||
public ApplicationAdapter(Context context) {
|
||||
fullList = showList = Collections.emptyList();
|
||||
hideList = Collections.emptyList();
|
||||
filter = new ApplicationFilter();
|
||||
pm = context.getPackageManager();
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(this::loadApps);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_app, parent, false);
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
private String getLabel(ApplicationInfo info) {
|
||||
if (info.labelRes > 0) {
|
||||
try {
|
||||
Resources res = pm.getResourcesForApplication(info);
|
||||
Configuration config = new Configuration();
|
||||
config.setLocale(LocaleManager.locale);
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
return res.getString(info.labelRes);
|
||||
} catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
|
||||
}
|
||||
return info.loadLabel(pm).toString();
|
||||
}
|
||||
|
||||
private void loadApps() {
|
||||
fullList = pm.getInstalledApplications(0);
|
||||
hideList = Shell.su("magiskhide --ls").exec().getOut();
|
||||
for (Iterator<ApplicationInfo> i = fullList.iterator(); i.hasNext(); ) {
|
||||
ApplicationInfo info = i.next();
|
||||
if (Const.HIDE_BLACKLIST.contains(info.packageName) || !info.enabled) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
Collections.sort(fullList, (a, b) -> {
|
||||
boolean ah = hideList.contains(a.packageName);
|
||||
boolean bh = hideList.contains(b.packageName);
|
||||
if (ah == bh) {
|
||||
return getLabel(a).toLowerCase().compareTo(getLabel(b).toLowerCase());
|
||||
} else if (ah) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
Topic.publish(false, Topic.MAGISK_HIDE_DONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
ApplicationInfo info = showList.get(position);
|
||||
|
||||
holder.appIcon.setImageDrawable(info.loadIcon(pm));
|
||||
holder.appName.setText(getLabel(info));
|
||||
holder.appPackage.setText(info.packageName);
|
||||
|
||||
holder.checkBox.setOnCheckedChangeListener(null);
|
||||
holder.checkBox.setChecked(hideList.contains(info.packageName));
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if (isChecked) {
|
||||
Shell.su("magiskhide --add " + info.packageName).submit();
|
||||
hideList.add(info.packageName);
|
||||
} else {
|
||||
Shell.su("magiskhide --rm " + info.packageName).submit();
|
||||
hideList.remove(info.packageName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return showList.size();
|
||||
}
|
||||
|
||||
public void filter(String constraint) {
|
||||
filter.filter(constraint);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(this::loadApps);
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public ImageView appIcon;
|
||||
public TextView appName;
|
||||
public TextView appPackage;
|
||||
public CheckBox checkBox;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ViewBinder.bind(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
private class ApplicationFilter extends Filter {
|
||||
|
||||
private boolean lowercaseContains(String s, CharSequence filter) {
|
||||
return !TextUtils.isEmpty(s) && s.toLowerCase().contains(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
if (constraint == null || constraint.length() == 0) {
|
||||
showList = fullList;
|
||||
} else {
|
||||
showList = new ArrayList<>();
|
||||
String filter = constraint.toString().toLowerCase();
|
||||
for (ApplicationInfo info : fullList) {
|
||||
if (lowercaseContains(getLabel(info), filter)
|
||||
|| lowercaseContains(info.packageName, filter)) {
|
||||
showList.add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHolder> {
|
||||
|
||||
private final List<Module> mList;
|
||||
|
||||
public ModulesAdapter(List<Module> list) {
|
||||
mList = list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_module, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
||||
Context context = holder.itemView.getContext();
|
||||
final Module module = mList.get(position);
|
||||
|
||||
String version = module.getVersion();
|
||||
String author = module.getAuthor();
|
||||
String description = module.getDescription();
|
||||
String noInfo = context.getString(R.string.no_info_provided);
|
||||
|
||||
holder.title.setText(module.getName());
|
||||
holder.versionName.setText(TextUtils.isEmpty(version) ? noInfo : version);
|
||||
holder.author.setText(TextUtils.isEmpty(author) ? noInfo : context.getString(R.string.author, author));
|
||||
holder.description.setText(TextUtils.isEmpty(description) ? noInfo : description);
|
||||
|
||||
holder.checkBox.setOnCheckedChangeListener(null);
|
||||
holder.checkBox.setChecked(module.isEnabled());
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
int snack;
|
||||
if (isChecked) {
|
||||
module.removeDisableFile();
|
||||
snack = R.string.disable_file_removed;
|
||||
} else {
|
||||
module.createDisableFile();
|
||||
snack = R.string.disable_file_created;
|
||||
}
|
||||
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
|
||||
});
|
||||
|
||||
holder.delete.setOnClickListener(v -> {
|
||||
boolean removed = module.willBeRemoved();
|
||||
int snack;
|
||||
if (removed) {
|
||||
module.deleteRemoveFile();
|
||||
snack = R.string.remove_file_deleted;
|
||||
} else {
|
||||
module.createRemoveFile();
|
||||
snack = R.string.remove_file_created;
|
||||
}
|
||||
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
|
||||
updateDeleteButton(holder, module);
|
||||
});
|
||||
|
||||
if (module.isUpdated()) {
|
||||
holder.notice.setVisibility(View.VISIBLE);
|
||||
holder.notice.setText(R.string.update_file_created);
|
||||
holder.delete.setEnabled(false);
|
||||
} else {
|
||||
updateDeleteButton(holder, module);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDeleteButton(ViewHolder holder, Module module) {
|
||||
holder.notice.setVisibility(module.willBeRemoved() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (module.willBeRemoved()) {
|
||||
holder.delete.setImageResource(R.drawable.ic_undelete);
|
||||
} else {
|
||||
holder.delete.setImageResource(R.drawable.ic_delete);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mList.size();
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public TextView title;
|
||||
public TextView versionName;
|
||||
public TextView description;
|
||||
public TextView notice;
|
||||
public CheckBox checkBox;
|
||||
public TextView author;
|
||||
public ImageView delete;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ViewBinder.bind(this, itemView);
|
||||
|
||||
if (!Shell.rootAccess()) {
|
||||
checkBox.setEnabled(false);
|
||||
delete.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.database.MagiskDatabaseHelper;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder> {
|
||||
|
||||
private List<Policy> policyList;
|
||||
private MagiskDatabaseHelper dbHelper;
|
||||
private PackageManager pm;
|
||||
private Set<Policy> expandList = new HashSet<>();
|
||||
|
||||
public PolicyAdapter(List<Policy> list, MagiskDatabaseHelper db, PackageManager pm) {
|
||||
policyList = list;
|
||||
dbHelper = db;
|
||||
this.pm = pm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_policy, parent, false);
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Policy policy = policyList.get(position);
|
||||
|
||||
holder.setExpanded(expandList.contains(policy));
|
||||
|
||||
holder.itemView.setOnClickListener(view -> {
|
||||
if (holder.isExpanded()) {
|
||||
holder.collapse();
|
||||
expandList.remove(policy);
|
||||
} else {
|
||||
holder.expand();
|
||||
expandList.add(policy);
|
||||
}
|
||||
});
|
||||
|
||||
holder.appName.setText(policy.appName);
|
||||
holder.packageName.setText(policy.packageName);
|
||||
holder.appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
||||
holder.masterSwitch.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if ((isChecked && policy.policy == Policy.DENY) ||
|
||||
(!isChecked && policy.policy == Policy.ALLOW)) {
|
||||
policy.policy = isChecked ? Policy.ALLOW : Policy.DENY;
|
||||
String message = v.getContext().getString(
|
||||
isChecked ? R.string.su_snack_grant : R.string.su_snack_deny, policy.appName);
|
||||
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
||||
dbHelper.updatePolicy(policy);
|
||||
}
|
||||
});
|
||||
holder.notificationSwitch.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if ((isChecked && !policy.notification) ||
|
||||
(!isChecked && policy.notification)) {
|
||||
policy.notification = isChecked;
|
||||
String message = v.getContext().getString(
|
||||
isChecked ? R.string.su_snack_notif_on : R.string.su_snack_notif_off, policy.appName);
|
||||
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
||||
dbHelper.updatePolicy(policy);
|
||||
}
|
||||
});
|
||||
holder.loggingSwitch.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if ((isChecked && !policy.logging) ||
|
||||
(!isChecked && policy.logging)) {
|
||||
policy.logging = isChecked;
|
||||
String message = v.getContext().getString(
|
||||
isChecked ? R.string.su_snack_log_on : R.string.su_snack_log_off, policy.appName);
|
||||
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
||||
dbHelper.updatePolicy(policy);
|
||||
}
|
||||
});
|
||||
holder.delete.setOnClickListener(v -> new CustomAlertDialog((Activity) v.getContext())
|
||||
.setTitle(R.string.su_revoke_title)
|
||||
.setMessage(v.getContext().getString(R.string.su_revoke_msg, policy.appName))
|
||||
.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||
policyList.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
notifyItemRangeChanged(position, policyList.size());
|
||||
SnackbarMaker.make(holder.itemView, v.getContext().getString(R.string.su_snack_revoke, policy.appName),
|
||||
Snackbar.LENGTH_SHORT).show();
|
||||
dbHelper.deletePolicy(policy);
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.setCancelable(true)
|
||||
.show());
|
||||
holder.masterSwitch.setChecked(policy.policy == Policy.ALLOW);
|
||||
holder.notificationSwitch.setChecked(policy.notification);
|
||||
holder.loggingSwitch.setChecked(policy.logging);
|
||||
|
||||
// Hide for now
|
||||
holder.moreInfo.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return policyList.size();
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder implements ExpandableView {
|
||||
|
||||
public TextView appName;
|
||||
public TextView packageName;
|
||||
public ImageView appIcon;
|
||||
public Switch masterSwitch;
|
||||
public Switch notificationSwitch;
|
||||
public Switch loggingSwitch;
|
||||
public ViewGroup expandLayout;
|
||||
|
||||
public ImageView delete;
|
||||
public ImageView moreInfo;
|
||||
|
||||
private Container container = new Container();
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ViewBinder.bind(this, itemView);
|
||||
container.expandLayout = expandLayout;
|
||||
setupExpandable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Container getContainer() {
|
||||
return container;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.asyncs.ProcessRepoZip;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, ReposAdapter.RepoHolder> {
|
||||
|
||||
private static final int UPDATES = 0;
|
||||
private static final int INSTALLED = 1;
|
||||
private static final int OTHERS = 2;
|
||||
|
||||
private Cursor repoCursor = null;
|
||||
private Map<String, Module> moduleMap;
|
||||
private RepoDatabaseHelper repoDB;
|
||||
private List<Pair<Integer, List<Repo>>> repoPairs;
|
||||
|
||||
public ReposAdapter(RepoDatabaseHelper db, Map<String, Module> map) {
|
||||
repoDB = db;
|
||||
moduleMap = map;
|
||||
repoPairs = new ArrayList<>();
|
||||
notifyDBChanged();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getSectionCount() {
|
||||
return repoPairs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(int section) {
|
||||
return repoPairs.get(section).second.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SectionHolder onCreateSectionViewHolder(ViewGroup parent) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.section, parent, false);
|
||||
return new SectionHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepoHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_repo, parent, false);
|
||||
return new RepoHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindSectionViewHolder(SectionHolder holder, int section) {
|
||||
switch (repoPairs.get(section).first) {
|
||||
case UPDATES:
|
||||
holder.sectionText.setText(R.string.update_available);
|
||||
break;
|
||||
case INSTALLED:
|
||||
holder.sectionText.setText(R.string.installed);
|
||||
break;
|
||||
case OTHERS:
|
||||
holder.sectionText.setText(R.string.not_installed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindItemViewHolder(RepoHolder holder, int section, int position) {
|
||||
Repo repo = repoPairs.get(section).second.get(position);
|
||||
Context context = holder.itemView.getContext();
|
||||
|
||||
String name = repo.getName();
|
||||
String version = repo.getVersion();
|
||||
String author = repo.getAuthor();
|
||||
String description = repo.getDescription();
|
||||
String noInfo = context.getString(R.string.no_info_provided);
|
||||
|
||||
holder.title.setText(TextUtils.isEmpty(name) ? noInfo : name);
|
||||
holder.versionName.setText(TextUtils.isEmpty(version) ? noInfo : version);
|
||||
holder.author.setText(TextUtils.isEmpty(author) ? noInfo : context.getString(R.string.author, author));
|
||||
holder.description.setText(TextUtils.isEmpty(description) ? noInfo : description);
|
||||
holder.updateTime.setText(context.getString(R.string.updated_on, repo.getLastUpdateString()));
|
||||
|
||||
holder.infoLayout.setOnClickListener(v ->
|
||||
new MarkDownWindow((BaseActivity) context, null, repo.getDetailUrl()).exec());
|
||||
|
||||
holder.downloadImage.setOnClickListener(v -> {
|
||||
new CustomAlertDialog((BaseActivity) context)
|
||||
.setTitle(context.getString(R.string.repo_install_title, repo.getName()))
|
||||
.setMessage(context.getString(R.string.repo_install_msg, repo.getDownloadFilename()))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.install, (d, i) ->
|
||||
new ProcessRepoZip((BaseActivity) context, repo, true).exec()
|
||||
)
|
||||
.setNeutralButton(R.string.download, (d, i) ->
|
||||
new ProcessRepoZip((BaseActivity) context, repo, false).exec())
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
public void notifyDBChanged() {
|
||||
if (repoCursor != null)
|
||||
repoCursor.close();
|
||||
repoCursor = repoDB.getRepoCursor();
|
||||
filter("");
|
||||
}
|
||||
|
||||
public void filter(String s) {
|
||||
List<Repo> updates = new ArrayList<>();
|
||||
List<Repo> installed = new ArrayList<>();
|
||||
List<Repo> others = new ArrayList<>();
|
||||
|
||||
repoPairs.clear();
|
||||
while (repoCursor.moveToNext()) {
|
||||
Repo repo = new Repo(repoCursor);
|
||||
if (repo.getName().toLowerCase().contains(s.toLowerCase())
|
||||
|| repo.getAuthor().toLowerCase().contains(s.toLowerCase())
|
||||
|| repo.getDescription().toLowerCase().contains(s.toLowerCase())
|
||||
) {
|
||||
// Passed the repoFilter
|
||||
Module module = moduleMap.get(repo.getId());
|
||||
if (module != null) {
|
||||
if (repo.getVersionCode() > module.getVersionCode()) {
|
||||
// Updates
|
||||
updates.add(repo);
|
||||
} else {
|
||||
installed.add(repo);
|
||||
}
|
||||
} else {
|
||||
others.add(repo);
|
||||
}
|
||||
}
|
||||
}
|
||||
repoCursor.moveToFirst();
|
||||
|
||||
if (!updates.isEmpty())
|
||||
repoPairs.add(new Pair<>(UPDATES, updates));
|
||||
if (!installed.isEmpty())
|
||||
repoPairs.add(new Pair<>(INSTALLED, installed));
|
||||
if (!others.isEmpty())
|
||||
repoPairs.add(new Pair<>(OTHERS, others));
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public static class SectionHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public TextView sectionText;
|
||||
|
||||
SectionHolder(View itemView) {
|
||||
super(itemView);
|
||||
ViewBinder.bind(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RepoHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public TextView title;
|
||||
public TextView versionName;
|
||||
public TextView description;
|
||||
public TextView author;
|
||||
public LinearLayout infoLayout;
|
||||
public ImageView downloadImage;
|
||||
public TextView updateTime;
|
||||
|
||||
RepoHolder(View itemView) {
|
||||
super(itemView);
|
||||
ViewBinder.bind(this, itemView);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public abstract class SectionedAdapter<S extends RecyclerView.ViewHolder, C extends RecyclerView.ViewHolder>
|
||||
extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private static final int SECTION_TYPE = Integer.MIN_VALUE;
|
||||
|
||||
@Override
|
||||
final public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
if (viewType == SECTION_TYPE)
|
||||
return onCreateSectionViewHolder(parent);
|
||||
return onCreateItemViewHolder(parent, viewType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
final public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||
PositionInfo info = getPositionInfo(position);
|
||||
if (info.position == -1)
|
||||
onBindSectionViewHolder((S) holder, info.section);
|
||||
else
|
||||
onBindItemViewHolder((C) holder, info.section, info.position);
|
||||
}
|
||||
|
||||
@Override
|
||||
final public int getItemCount() {
|
||||
int size, sec;
|
||||
size = sec = getSectionCount();
|
||||
for (int i = 0; i < sec; ++i){
|
||||
size += getItemCount(i);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
final public int getItemViewType(int position) {
|
||||
PositionInfo info = getPositionInfo(position);
|
||||
if (info.position == -1)
|
||||
return SECTION_TYPE;
|
||||
else
|
||||
return getItemViewType(info.section, info.position);
|
||||
}
|
||||
|
||||
public int getItemViewType(int section, int position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected int getSectionPosition(int section) {
|
||||
return getItemPosition(section, -1);
|
||||
}
|
||||
|
||||
protected int getItemPosition(int section, int position) {
|
||||
int realPosition = 0;
|
||||
// Previous sections
|
||||
for (int i = 0; i < section; ++i) {
|
||||
realPosition += getItemCount(i) + 1;
|
||||
}
|
||||
// Current section
|
||||
realPosition += position + 1;
|
||||
return realPosition;
|
||||
}
|
||||
|
||||
private PositionInfo getPositionInfo(int position) {
|
||||
int section = 0;
|
||||
while (true) {
|
||||
if (position == 0)
|
||||
return new PositionInfo(section, -1);
|
||||
position -= 1;
|
||||
if (position < getItemCount(section))
|
||||
return new PositionInfo(section, position);
|
||||
position -= getItemCount(section++);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PositionInfo {
|
||||
int section;
|
||||
int position;
|
||||
PositionInfo(int section, int position) {
|
||||
this.section = section;
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract int getSectionCount();
|
||||
public abstract int getItemCount(int section);
|
||||
public abstract S onCreateSectionViewHolder(ViewGroup parent);
|
||||
public abstract C onCreateItemViewHolder(ViewGroup parent, int viewType);
|
||||
public abstract void onBindSectionViewHolder(S holder, int section);
|
||||
public abstract void onBindItemViewHolder(C holder, int section, int position);
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.RotateAnimation;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.database.MagiskDatabaseHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, SuLogAdapter.LogViewHolder> {
|
||||
|
||||
private List<List<Integer>> logEntryList;
|
||||
private Set<Integer> itemExpanded, sectionExpanded;
|
||||
private MagiskDatabaseHelper suDB;
|
||||
private Cursor suLogCursor = null;
|
||||
|
||||
public SuLogAdapter(MagiskDatabaseHelper db) {
|
||||
suDB = db;
|
||||
logEntryList = Collections.emptyList();
|
||||
sectionExpanded = new HashSet<>();
|
||||
itemExpanded = new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSectionCount() {
|
||||
return logEntryList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(int section) {
|
||||
return sectionExpanded.contains(section) ? logEntryList.get(section).size() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SectionHolder onCreateSectionViewHolder(ViewGroup parent) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog_group, parent, false);
|
||||
return new SectionHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog, parent, false);
|
||||
return new LogViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindSectionViewHolder(SectionHolder holder, int section) {
|
||||
suLogCursor.moveToPosition(logEntryList.get(section).get(0));
|
||||
SuLogEntry entry = new SuLogEntry(suLogCursor);
|
||||
holder.arrow.setRotation(sectionExpanded.contains(section) ? 180 : 0);
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
RotateAnimation rotate;
|
||||
if (sectionExpanded.contains(section)) {
|
||||
holder.arrow.setRotation(0);
|
||||
rotate = new RotateAnimation(180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
||||
sectionExpanded.remove(section);
|
||||
notifyItemRangeRemoved(getItemPosition(section, 0), logEntryList.get(section).size());
|
||||
} else {
|
||||
rotate = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
||||
sectionExpanded.add(section);
|
||||
notifyItemRangeInserted(getItemPosition(section, 0), logEntryList.get(section).size());
|
||||
}
|
||||
rotate.setDuration(300);
|
||||
rotate.setFillAfter(true);
|
||||
holder.arrow.setAnimation(rotate);
|
||||
});
|
||||
holder.date.setText(entry.getDateString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindItemViewHolder(LogViewHolder holder, int section, int position) {
|
||||
int sqlPosition = logEntryList.get(section).get(position);
|
||||
suLogCursor.moveToPosition(sqlPosition);
|
||||
SuLogEntry entry = new SuLogEntry(suLogCursor);
|
||||
holder.setExpanded(itemExpanded.contains(sqlPosition));
|
||||
holder.itemView.setOnClickListener(view -> {
|
||||
if (holder.isExpanded()) {
|
||||
holder.collapse();
|
||||
itemExpanded.remove(sqlPosition);
|
||||
} else {
|
||||
holder.expand();
|
||||
itemExpanded.add(sqlPosition);
|
||||
}
|
||||
});
|
||||
holder.appName.setText(entry.appName);
|
||||
holder.action.setText(entry.action ? R.string.grant : R.string.deny);
|
||||
holder.command.setText(entry.command);
|
||||
holder.fromPid.setText(String.valueOf(entry.fromPid));
|
||||
holder.toUid.setText(String.valueOf(entry.toUid));
|
||||
holder.time.setText(entry.getTimeString());
|
||||
}
|
||||
|
||||
public void notifyDBChanged() {
|
||||
if (suLogCursor != null)
|
||||
suLogCursor.close();
|
||||
suLogCursor = suDB.getLogCursor();
|
||||
logEntryList = suDB.getLogStructure();
|
||||
itemExpanded.clear();
|
||||
sectionExpanded.clear();
|
||||
sectionExpanded.add(0);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public static class SectionHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public TextView date;
|
||||
public ImageView arrow;
|
||||
|
||||
SectionHolder(View itemView) {
|
||||
super(itemView);
|
||||
ViewBinder.bind(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
public static class LogViewHolder extends RecyclerView.ViewHolder implements ExpandableView {
|
||||
|
||||
public TextView appName;
|
||||
public TextView action;
|
||||
public TextView time;
|
||||
public TextView fromPid;
|
||||
public TextView toUid;
|
||||
public TextView command;
|
||||
public ViewGroup expandLayout;
|
||||
|
||||
private Container container = new Container();
|
||||
|
||||
LogViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ViewBinder.bind(this, itemView);
|
||||
container.expandLayout = expandLayout;
|
||||
setupExpandable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Container getContainer() {
|
||||
return container;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
|
||||
public class TabFragmentAdapter extends FragmentPagerAdapter {
|
||||
|
||||
private List<Fragment> fragmentList;
|
||||
private List<String> titleList;
|
||||
|
||||
public TabFragmentAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
fragmentList = new ArrayList<>();
|
||||
titleList = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return fragmentList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return fragmentList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return titleList.get(position);
|
||||
}
|
||||
|
||||
public void addTab(Fragment fragment, String title) {
|
||||
fragmentList.add(fragment);
|
||||
titleList.add(title);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.utils.ISafetyNetHelper;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
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, Void> {
|
||||
|
||||
public static final File dexPath =
|
||||
new File(Data.MM().getFilesDir().getParent() + "/snet", "snet.apk");
|
||||
private ISafetyNetHelper helper;
|
||||
|
||||
public CheckSafetyNet(Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
private void dlSnet() throws Exception {
|
||||
Shell.sh("rm -rf " + dexPath.getParent()).exec();
|
||||
dexPath.getParentFile().mkdir();
|
||||
HttpURLConnection conn = WebService.mustRequest(Data.snetLink, 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.Snet");
|
||||
helper = (ISafetyNetHelper) clazz.getMethod("newHelper",
|
||||
Class.class, String.class, Activity.class, Object.class)
|
||||
.invoke(null, ISafetyNetHelper.class, dexPath.getPath(), getActivity(),
|
||||
(ISafetyNetHelper.Callback) code ->
|
||||
Topic.publish(false, Topic.SNET_CHECK_DONE, code));
|
||||
if (helper.getVersion() < Data.snetVersionCode) {
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
try {
|
||||
try {
|
||||
dyload();
|
||||
} catch (Exception e) {
|
||||
// If dynamic load failed, try re-downloading and reload
|
||||
dlSnet();
|
||||
dyload();
|
||||
}
|
||||
// Run attestation
|
||||
helper.attest();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Topic.publish(false, Topic.SNET_CHECK_DONE, -1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
105
app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java
Normal file
105
app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.utils.NotificationMgr;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class CheckUpdates {
|
||||
|
||||
private static int getInt(JSONObject json, String name, int defValue) {
|
||||
if (json == null)
|
||||
return defValue;
|
||||
try {
|
||||
return json.getInt(name);
|
||||
} catch (JSONException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getString(JSONObject json, String name, String defValue) {
|
||||
if (json == null)
|
||||
return defValue;
|
||||
try {
|
||||
return json.getString(name);
|
||||
} catch (JSONException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static JSONObject getJson(JSONObject json, String name) {
|
||||
try {
|
||||
return json.getJSONObject(name);
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void fetchUpdates() {
|
||||
String jsonStr = "";
|
||||
switch (Data.updateChannel) {
|
||||
case Const.Value.STABLE_CHANNEL:
|
||||
jsonStr = WebService.getString(Const.Url.STABLE_URL);
|
||||
break;
|
||||
case Const.Value.BETA_CHANNEL:
|
||||
jsonStr = WebService.getString(Const.Url.BETA_URL);
|
||||
break;
|
||||
case Const.Value.CUSTOM_CHANNEL:
|
||||
jsonStr = WebService.getString(Data.MM().prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||
break;
|
||||
}
|
||||
|
||||
JSONObject json;
|
||||
try {
|
||||
json = new JSONObject(jsonStr);
|
||||
} catch (JSONException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject magisk = getJson(json, "magisk");
|
||||
Data.remoteMagiskVersionString = getString(magisk, "version", null);
|
||||
Data.remoteMagiskVersionCode = getInt(magisk, "versionCode", -1);
|
||||
Data.magiskLink = getString(magisk, "link", null);
|
||||
Data.magiskNoteLink = getString(magisk, "note", null);
|
||||
Data.magiskMD5 = getString(magisk, "md5", null);
|
||||
|
||||
JSONObject manager = getJson(json, "app");
|
||||
Data.remoteManagerVersionString = getString(manager, "version", null);
|
||||
Data.remoteManagerVersionCode = getInt(manager, "versionCode", -1);
|
||||
Data.managerLink = getString(manager, "link", null);
|
||||
Data.managerNoteLink = getString(manager, "note", null);
|
||||
|
||||
JSONObject uninstaller = getJson(json, "uninstaller");
|
||||
Data.uninstallerLink = getString(uninstaller, "link", null);
|
||||
|
||||
JSONObject snet = getJson(json, "snet");
|
||||
Data.snetVersionCode = getInt(snet, "versionCode", -1);
|
||||
Data.snetLink = getString(snet, "link", null);
|
||||
}
|
||||
|
||||
public static void check(Runnable cb) {
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
fetchUpdates();
|
||||
if (cb != null) {
|
||||
if (BuildConfig.VERSION_CODE < Data.remoteManagerVersionCode) {
|
||||
NotificationMgr.managerUpdate();
|
||||
} else if (Data.magiskVersionCode < Data.remoteMagiskVersionCode) {
|
||||
NotificationMgr.magiskUpdate();
|
||||
}
|
||||
cb.run();
|
||||
}
|
||||
Topic.publish(Topic.UPDATE_CHECK_DONE);
|
||||
});
|
||||
}
|
||||
|
||||
public static void check() {
|
||||
check(null);
|
||||
}
|
||||
}
|
||||
104
app/src/full/java/com/topjohnwu/magisk/asyncs/FlashZip.java
Normal file
104
app/src/full/java/com/topjohnwu/magisk/asyncs/FlashZip.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.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;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
|
||||
private Uri mUri;
|
||||
private File mCachedFile;
|
||||
private List<String> console, logs;
|
||||
|
||||
public FlashZip(Activity context, Uri uri, List<String> console, List<String> logs) {
|
||||
super(context);
|
||||
mUri = uri;
|
||||
this.console = console;
|
||||
this.logs = logs;
|
||||
mCachedFile = new File(context.getCacheDir(), "install.zip");
|
||||
}
|
||||
|
||||
private boolean unzipAndCheck() throws Exception {
|
||||
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android", true);
|
||||
return ShellUtils.fastCmdResult("grep -q '#MAGISK' " + new File(mCachedFile.getParentFile(), "updater-script"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... voids) {
|
||||
MagiskManager mm = Data.MM();
|
||||
try {
|
||||
console.add("- Copying zip to temp directory");
|
||||
|
||||
mCachedFile.delete();
|
||||
try (
|
||||
InputStream in = mm.getContentResolver().openInputStream(mUri);
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(mCachedFile))
|
||||
) {
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
InputStream buf= new BufferedInputStream(in);
|
||||
ShellUtils.pump(buf, out);
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
console.add("! Cannot copy to cache");
|
||||
throw e;
|
||||
}
|
||||
if (!unzipAndCheck()) return 0;
|
||||
console.add("- Installing " + Utils.getNameFromUri(mm, mUri));
|
||||
if (!Shell.su("cd " + mCachedFile.getParent(),
|
||||
"BOOTMODE=true sh update-binary dummy 1 " + mCachedFile)
|
||||
.to(console, logs)
|
||||
.exec().isSuccess())
|
||||
return -1;
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
console.add("- All done!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// -1 = error, manual install; 0 = invalid zip; 1 = success
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
FlashActivity activity = (FlashActivity) getActivity();
|
||||
Shell.su("rm -rf " + mCachedFile.getParent(), "rm -rf " + Const.TMP_FOLDER_PATH).submit();
|
||||
switch (result) {
|
||||
case -1:
|
||||
console.add("! Installation failed");
|
||||
SnackbarMaker.showUri(getActivity(), mUri);
|
||||
break;
|
||||
case 0:
|
||||
console.add("! This zip is not a Magisk Module!");
|
||||
break;
|
||||
case 1:
|
||||
// Reload modules
|
||||
Utils.loadModules();
|
||||
break;
|
||||
}
|
||||
activity.reboot.setVisibility(result > 0 ? View.VISIBLE : View.GONE);
|
||||
activity.buttonPanel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
398
app/src/full/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java
Normal file
398
app/src/full/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java
Normal file
@@ -0,0 +1,398 @@
|
||||
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.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.TarEntry;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.internal.NOPList;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
import com.topjohnwu.utils.SignBoot;
|
||||
|
||||
import org.kamranzafar.jtar.TarInputStream;
|
||||
import org.kamranzafar.jtar.TarOutputStream;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
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;
|
||||
private List<String> console, logs;
|
||||
private String mBoot;
|
||||
private int mode;
|
||||
private File installDir;
|
||||
private ProgressDialog dialog;
|
||||
private MagiskManager mm;
|
||||
|
||||
public InstallMagisk(Activity context) {
|
||||
super(context);
|
||||
mm = Data.MM();
|
||||
mode = FIX_ENV_MODE;
|
||||
}
|
||||
|
||||
public InstallMagisk(Activity context, List<String> console, List<String> logs, int mode) {
|
||||
this(context);
|
||||
this.console = console;
|
||||
this.logs = logs;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public InstallMagisk(FlashActivity context, List<String> console, List<String> logs, Uri boot) {
|
||||
this(context, console, logs, 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 = NOPList.getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
private class ProgressStream extends FilterInputStream {
|
||||
|
||||
private int prev = -1;
|
||||
private int progress = 0;
|
||||
private int total;
|
||||
|
||||
private ProgressStream(HttpURLConnection conn) throws IOException {
|
||||
super(conn.getInputStream());
|
||||
total = conn.getContentLength();
|
||||
console.add("... 0%");
|
||||
}
|
||||
|
||||
private void update(int step) {
|
||||
progress += step;
|
||||
int curr = (int) (100 * (double) progress / total);
|
||||
if (prev != curr) {
|
||||
prev = curr;
|
||||
console.set(console.size() - 1, "... " + prev + "%");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int b = super.read();
|
||||
if (b > 0)
|
||||
update(1);
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b, int off, int len) throws IOException {
|
||||
int step = super.read(b, off, len);
|
||||
if (step > 0)
|
||||
update(step);
|
||||
return step;
|
||||
}
|
||||
}
|
||||
|
||||
private void extractFiles(String arch) throws IOException {
|
||||
File zip = new File(mm.getFilesDir(), "magisk.zip");
|
||||
BufferedInputStream buf;
|
||||
|
||||
if (!ShellUtils.checkSum("MD5", zip, Data.magiskMD5)) {
|
||||
console.add("- Downloading zip");
|
||||
HttpURLConnection conn = WebService.mustRequest(Data.magiskLink, null);
|
||||
buf = new BufferedInputStream(new ProgressStream(conn), conn.getContentLength());
|
||||
buf.mark(conn.getContentLength() + 1);
|
||||
try (OutputStream out = new FileOutputStream(zip)) {
|
||||
ShellUtils.pump(buf, out);
|
||||
}
|
||||
buf.reset();
|
||||
conn.disconnect();
|
||||
} else {
|
||||
console.add("- Existing zip found");
|
||||
buf = new BufferedInputStream(new FileInputStream(zip), (int) zip.length());
|
||||
buf.mark((int) zip.length() + 1);
|
||||
}
|
||||
|
||||
console.add("- Extracting files");
|
||||
try (InputStream in = buf) {
|
||||
ZipUtils.unzip(in, installDir, arch + "/", true);
|
||||
in.reset();
|
||||
ZipUtils.unzip(in, installDir, "common/", true);
|
||||
in.reset();
|
||||
ZipUtils.unzip(in, installDir, "chromeos/", false);
|
||||
in.reset();
|
||||
ZipUtils.unzip(in, installDir, "META-INF/com/google/android/update-binary", true);
|
||||
} catch (IOException e) {
|
||||
console.add("! Cannot unzip zip");
|
||||
throw e;
|
||||
}
|
||||
Shell.sh(Utils.fmt("chmod -R 755 %s/*; %s/magiskinit -x magisk %s/magisk",
|
||||
installDir, installDir, installDir)).exec();
|
||||
}
|
||||
|
||||
private boolean dumpBoot() {
|
||||
console.add("- Copying image to cache");
|
||||
// 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
|
||||
if (!Shell.sh("cd " + installDir, Utils.fmt(
|
||||
"KEEPFORCEENCRYPT=%b KEEPVERITY=%b sh update-binary indep boot_patch.sh %s",
|
||||
Data.keepEnc, Data.keepVerity, mBoot))
|
||||
.to(console, logs).exec().isSuccess())
|
||||
return null;
|
||||
|
||||
Shell.Job job = Shell.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);
|
||||
}
|
||||
job.add("mv -f " + signed + " " + patched);
|
||||
}
|
||||
job.exec();
|
||||
return patched;
|
||||
}
|
||||
|
||||
private boolean outputBoot(File patched) throws IOException {
|
||||
switch (mode) {
|
||||
case PATCH_MODE:
|
||||
String fmt = mm.prefs.getString(Const.Key.BOOT_FORMAT, ".img");
|
||||
File dest = new File(Download.EXTERNAL_PATH, "patched_boot" + fmt);
|
||||
dest.getParentFile().mkdirs();
|
||||
OutputStream out;
|
||||
switch (fmt) {
|
||||
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.sh("rm -f " + patched).exec();
|
||||
console.add("");
|
||||
console.add("****************************");
|
||||
console.add(" Patched image is placed in ");
|
||||
console.add(" " + dest + " ");
|
||||
console.add("****************************");
|
||||
break;
|
||||
case SECOND_SLOT_MODE:
|
||||
case DIRECT_MODE:
|
||||
if (!Shell.su(Utils.fmt("direct_install %s %s", installDir, mBoot))
|
||||
.to(console, logs).exec().isSuccess())
|
||||
return false;
|
||||
if (!Data.keepVerity)
|
||||
Shell.su("find_dtbo_image", "patch_dtbo_image").to(console, logs).exec();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void postOTA() {
|
||||
SuFile bootctl = new SuFile(Const.MAGISK_PATH + "/.core/bootctl");
|
||||
try (InputStream in = mm.getResources().openRawResource(R.raw.bootctl);
|
||||
OutputStream out = new SuFileOutputStream(bootctl)) {
|
||||
ShellUtils.pump(in, out);
|
||||
Shell.su("post_ota " + bootctl.getParent()).exec();
|
||||
console.add("***************************************");
|
||||
console.add(" Next reboot will boot to second slot!");
|
||||
console.add("***************************************");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
if (mode == FIX_ENV_MODE) {
|
||||
installDir = new File("/data/adb/magisk");
|
||||
Shell.su("rm -rf /data/adb/magisk/*").exec();
|
||||
} else {
|
||||
installDir = new File(
|
||||
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
|
||||
mm.createDeviceProtectedStorageContext() : mm)
|
||||
.getFilesDir().getParent()
|
||||
, "install");
|
||||
Shell.sh("rm -rf " + installDir).exec();
|
||||
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:
|
||||
String slot = ShellUtils.fastCmd("echo $SLOT");
|
||||
String target = (TextUtils.equals(slot, "_a") ? "_b" : "_a");
|
||||
console.add("- Target slot: " + target);
|
||||
console.add("- Detecting target image");
|
||||
mBoot = ShellUtils.fastCmd(
|
||||
"SLOT=" + target,
|
||||
"find_boot_image",
|
||||
"SLOT=" + slot,
|
||||
"echo \"$BOOTIMAGE\""
|
||||
);
|
||||
break;
|
||||
case FIX_ENV_MODE:
|
||||
mBoot = "";
|
||||
break;
|
||||
}
|
||||
if (mBoot == null) {
|
||||
console.add("! Unable to detect target image");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode == DIRECT_MODE || mode == SECOND_SLOT_MODE)
|
||||
console.add("- Target image: " + mBoot);
|
||||
|
||||
List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
|
||||
String arch;
|
||||
|
||||
if (Data.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.su("fix_env").exec();
|
||||
} else {
|
||||
File patched = patchBoot();
|
||||
if (patched == null)
|
||||
return false;
|
||||
if (!outputBoot(patched))
|
||||
return false;
|
||||
if (mode == SECOND_SLOT_MODE)
|
||||
postOTA();
|
||||
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();
|
||||
Utils.toast(result ? R.string.setup_done : R.string.setup_fail, Toast.LENGTH_LONG);
|
||||
} else {
|
||||
// Running in FlashActivity
|
||||
FlashActivity activity = (FlashActivity) getActivity();
|
||||
if (!result) {
|
||||
Shell.sh("rm -rf " + installDir).submit();
|
||||
console.add("! Installation failed");
|
||||
activity.reboot.setVisibility(View.GONE);
|
||||
}
|
||||
activity.buttonPanel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public class MarkDownWindow extends ParallelTask<Void, Void, String> {
|
||||
|
||||
private String mTitle;
|
||||
private String mUrl;
|
||||
private InputStream is;
|
||||
|
||||
|
||||
public MarkDownWindow(Activity context, String title, String url) {
|
||||
super(context);
|
||||
mTitle = title;
|
||||
mUrl = url;
|
||||
}
|
||||
|
||||
public MarkDownWindow(Activity context, String title, InputStream in) {
|
||||
super(context);
|
||||
mTitle = title;
|
||||
is = in;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Void... voids) {
|
||||
MagiskManager mm = Data.MM();
|
||||
String md;
|
||||
if (mUrl != null) {
|
||||
md = WebService.getString(mUrl);
|
||||
} else {
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
ShellUtils.pump(is, out);
|
||||
md = out.toString();
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
String css;
|
||||
try (
|
||||
InputStream in = mm.getResources().openRawResource(
|
||||
Data.isDarkTheme ? R.raw.dark : R.raw.light);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream()
|
||||
) {
|
||||
ShellUtils.pump(in, out);
|
||||
css = out.toString();
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
Parser parser = Parser.builder().build();
|
||||
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
||||
Node doc = parser.parse(md);
|
||||
return String.format("<style>%s</style>%s", css, renderer.render(doc));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String html) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
|
||||
alert.setTitle(mTitle);
|
||||
|
||||
WebView wv = new WebView(getActivity());
|
||||
wv.loadDataWithBaseURL("fake://", html, "text/html", "UTF-8", null);
|
||||
|
||||
alert.setView(wv);
|
||||
alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss());
|
||||
alert.show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public abstract class ParallelTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
|
||||
|
||||
private WeakReference<Activity> weakActivity;
|
||||
|
||||
public ParallelTask() {}
|
||||
|
||||
public ParallelTask(Activity context) {
|
||||
weakActivity = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
protected Activity getActivity() {
|
||||
return weakActivity.get();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void exec(Params... params) {
|
||||
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
|
||||
}
|
||||
}
|
||||
153
app/src/full/java/com/topjohnwu/magisk/asyncs/PatchAPK.java
Normal file
153
app/src/full/java/com/topjohnwu/magisk/asyncs/PatchAPK.java
Normal file
@@ -0,0 +1,153 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.os.AsyncTask;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
import com.topjohnwu.utils.JarMap;
|
||||
import com.topjohnwu.utils.SignAPK;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
public class PatchAPK {
|
||||
|
||||
private static String genPackageName(String prefix, int length) {
|
||||
StringBuilder builder = new StringBuilder(length);
|
||||
builder.append(prefix);
|
||||
length -= prefix.length();
|
||||
SecureRandom random = new SecureRandom();
|
||||
String base = "abcdefghijklmnopqrstuvwxyz";
|
||||
String alpha = base + base.toUpperCase();
|
||||
String full = alpha + "0123456789..........";
|
||||
char next, prev = '\0';
|
||||
for (int i = 0; i < length; ++i) {
|
||||
if (prev == '.' || i == length - 1 || i == 0) {
|
||||
next = alpha.charAt(random.nextInt(alpha.length()));
|
||||
} else {
|
||||
next = full.charAt(random.nextInt(full.length()));
|
||||
}
|
||||
builder.append(next);
|
||||
prev = next;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static int findOffset(byte buf[], byte pattern[]) {
|
||||
int offset = -1;
|
||||
for (int i = 0; i < buf.length - pattern.length; ++i) {
|
||||
boolean match = true;
|
||||
for (int j = 0; j < pattern.length; ++j) {
|
||||
if (buf[i + j] != pattern[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
offset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
/* It seems that AAPT sometimes generate another type of string format */
|
||||
private static boolean fallbackPatch(byte xml[], String from, String to) {
|
||||
|
||||
byte[] target = new byte[from.length() * 2 + 2];
|
||||
for (int i = 0; i < from.length(); ++i) {
|
||||
target[i * 2] = (byte) from.charAt(i);
|
||||
}
|
||||
int offset = findOffset(xml, target);
|
||||
if (offset < 0)
|
||||
return false;
|
||||
byte[] dest = new byte[target.length - 2];
|
||||
for (int i = 0; i < to.length(); ++i) {
|
||||
dest[i * 2] = (byte) to.charAt(i);
|
||||
}
|
||||
System.arraycopy(dest, 0, xml, offset, dest.length);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean findAndPatch(byte xml[], String from, String to) {
|
||||
byte target[] = (from + '\0').getBytes();
|
||||
int offset = findOffset(xml, target);
|
||||
if (offset < 0)
|
||||
return fallbackPatch(xml, from, to);
|
||||
System.arraycopy(to.getBytes(), 0, xml, offset, to.length());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean patchAndHide() {
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
// Generate a new app with random package name
|
||||
SuFile repack = new SuFile("/data/local/tmp/repack.apk");
|
||||
String pkg = genPackageName("com.", Const.ORIG_PKG_NAME.length());
|
||||
|
||||
try {
|
||||
JarMap apk = new JarMap(mm.getPackageCodePath());
|
||||
if (!patchPackageID(apk, Const.ORIG_PKG_NAME, pkg))
|
||||
return false;
|
||||
SignAPK.sign(apk, new SuFileOutputStream(repack));
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Install the application
|
||||
if (!ShellUtils.fastCmdResult("pm install " + repack))
|
||||
return false;
|
||||
|
||||
repack.delete();
|
||||
|
||||
mm.mDB.setStrings(Const.Key.SU_MANAGER, pkg);
|
||||
Data.exportPrefs();
|
||||
RootUtils.uninstallPkg(Const.ORIG_PKG_NAME);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean patchPackageID(JarMap apk, String from, String to) {
|
||||
try {
|
||||
JarEntry je = apk.getJarEntry(Const.ANDROID_MANIFEST);
|
||||
byte xml[] = apk.getRawData(je);
|
||||
|
||||
if (!findAndPatch(xml, from, to))
|
||||
return false;
|
||||
if (!findAndPatch(xml, from + ".provider", to + ".provider"))
|
||||
return false;
|
||||
|
||||
// Write in changes
|
||||
apk.getOutputStream(je).write(xml);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void hideManager(Activity activity) {
|
||||
ProgressDialog dialog = ProgressDialog.show(activity,
|
||||
activity.getString(R.string.hide_manager_toast),
|
||||
activity.getString(R.string.hide_manager_toast2));
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
boolean b = patchAndHide();
|
||||
Data.mainHandler.post(() -> {
|
||||
dialog.cancel();
|
||||
if (!b) {
|
||||
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
|
||||
private ProgressDialog progressDialog;
|
||||
private boolean mInstall;
|
||||
private File mFile;
|
||||
private Repo mRepo;
|
||||
private int progress = 0, total = -1;
|
||||
|
||||
public ProcessRepoZip(BaseActivity context, Repo repo, boolean install) {
|
||||
super(context);
|
||||
mRepo = repo;
|
||||
mInstall = install && Shell.rootAccess();
|
||||
mFile = new File(Download.EXTERNAL_PATH, repo.getDownloadFilename());
|
||||
}
|
||||
|
||||
private void removeTopFolder(File input, File output) throws IOException {
|
||||
JarEntry entry;
|
||||
try (
|
||||
JarInputStream in = new JarInputStream(new BufferedInputStream(new FileInputStream(input)));
|
||||
JarOutputStream out = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(output)))
|
||||
) {
|
||||
String path;
|
||||
while ((entry = in.getNextJarEntry()) != null) {
|
||||
// Remove the top directory from the path
|
||||
path = entry.getName().substring(entry.getName().indexOf("/") + 1);
|
||||
// If it's the top folder, ignore it
|
||||
if (path.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
// Don't include placeholder
|
||||
if (path.equals("system/placeholder")) {
|
||||
continue;
|
||||
}
|
||||
out.putNextEntry(new JarEntry(path));
|
||||
ShellUtils.pump(in, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BaseActivity getActivity() {
|
||||
return (BaseActivity) super.getActivity();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
BaseActivity activity = getActivity();
|
||||
mFile.getParentFile().mkdirs();
|
||||
progressDialog = ProgressDialog.show(activity, activity.getString(R.string.zip_download_title), activity.getString(R.string.zip_download_msg, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
BaseActivity activity = getActivity();
|
||||
if (activity == null) return null;
|
||||
try {
|
||||
// Request zip from Internet
|
||||
HttpURLConnection conn = WebService.mustRequest(mRepo.getZipUrl(), null);
|
||||
total = conn.getContentLength();
|
||||
|
||||
// Temp files
|
||||
File temp1 = new File(activity.getCacheDir(), "1.zip");
|
||||
File temp2 = new File(temp1.getParentFile(), "2.zip");
|
||||
temp1.getParentFile().mkdir();
|
||||
|
||||
// First download the zip, Web -> temp1
|
||||
try (
|
||||
InputStream in = new BufferedInputStream(new ProgressInputStream(conn.getInputStream()));
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(temp1))
|
||||
) {
|
||||
ShellUtils.pump(in, out);
|
||||
in.close();
|
||||
}
|
||||
conn.disconnect();
|
||||
|
||||
Data.mainHandler.post(() -> {
|
||||
progressDialog.setTitle(R.string.zip_process_title);
|
||||
progressDialog.setMessage(getActivity().getString(R.string.zip_process_msg));
|
||||
});
|
||||
|
||||
// First remove top folder in Github source zip, temp1 -> temp2
|
||||
removeTopFolder(temp1, temp2);
|
||||
|
||||
// Then sign the zip
|
||||
ZipUtils.signZip(temp2, mFile);
|
||||
|
||||
// Delete temp files
|
||||
temp1.delete();
|
||||
temp2.delete();
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
BaseActivity activity = getActivity();
|
||||
if (activity == null) return;
|
||||
progressDialog.dismiss();
|
||||
if (result) {
|
||||
Uri uri = Uri.fromFile(mFile);
|
||||
if (mInstall) {
|
||||
Intent intent = new Intent(activity, Data.classMap.get(FlashActivity.class));
|
||||
intent.setData(uri).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||
activity.startActivity(intent);
|
||||
} else {
|
||||
SnackbarMaker.showUri(activity, uri);
|
||||
}
|
||||
} else {
|
||||
Utils.toast(R.string.process_error, Toast.LENGTH_LONG);
|
||||
}
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exec(Void... voids) {
|
||||
getActivity().runWithPermission(
|
||||
new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, super::exec);
|
||||
}
|
||||
|
||||
private class ProgressInputStream extends FilterInputStream {
|
||||
|
||||
ProgressInputStream(InputStream in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
private void updateDlProgress(int step) {
|
||||
progress += step;
|
||||
progressDialog.setMessage(getActivity().getString(R.string.zip_download_msg,
|
||||
(int) (100 * (double) progress / total + 0.5)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read() throws IOException {
|
||||
int b = super.read();
|
||||
if (b > 0) {
|
||||
Data.mainHandler.post(() -> updateDlProgress(1));
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read(@NonNull byte[] b, int off, int len) throws IOException {
|
||||
int read = super.read(b, off, len);
|
||||
if (read > 0) {
|
||||
Data.mainHandler.post(() -> updateDlProgress(read));
|
||||
}
|
||||
return read;
|
||||
}
|
||||
}
|
||||
}
|
||||
161
app/src/full/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java
Normal file
161
app/src/full/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java
Normal file
@@ -0,0 +1,161 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class UpdateRepos {
|
||||
|
||||
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
|
||||
private static final int CORE_POOL_SIZE = Math.max(2, CPU_COUNT - 1);
|
||||
private static final DateFormat dateFormat;
|
||||
|
||||
static {
|
||||
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
private MagiskManager mm;
|
||||
private Set<String> cached;
|
||||
private ExecutorService threadPool;
|
||||
|
||||
public UpdateRepos() {
|
||||
mm = Data.MM();
|
||||
}
|
||||
|
||||
private void waitTasks() {
|
||||
threadPool.shutdown();
|
||||
try {
|
||||
threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
|
||||
private boolean loadJSON(String jsonString) throws JSONException, ParseException {
|
||||
JSONArray jsonArray = new JSONArray(jsonString);
|
||||
|
||||
// Empty page, halt
|
||||
if (jsonArray.length() == 0)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
JSONObject rawRepo = jsonArray.getJSONObject(i);
|
||||
String id = rawRepo.getString("description");
|
||||
String name = rawRepo.getString("name");
|
||||
Date date = dateFormat.parse(rawRepo.getString("pushed_at"));
|
||||
Set<String> set = Collections.synchronizedSet(cached);
|
||||
threadPool.execute(() -> {
|
||||
Repo repo = mm.repoDB.getRepo(id);
|
||||
try {
|
||||
if (repo == null)
|
||||
repo = new Repo(name);
|
||||
else
|
||||
set.remove(id);
|
||||
repo.update(date);
|
||||
mm.repoDB.addRepo(repo);
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.debug(e.getMessage());
|
||||
mm.repoDB.removeRepo(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* We sort repos by last push, which means that we only need to check whether the
|
||||
* first page is updated to determine whether the online repo database is changed
|
||||
*/
|
||||
private boolean loadPage(int page) {
|
||||
Map<String, String> header = new HashMap<>();
|
||||
if (page == 0)
|
||||
header.put(Const.Key.IF_NONE_MATCH, mm.prefs.getString(Const.Key.ETAG_KEY, ""));
|
||||
String url = Utils.fmt(Const.Url.REPO_URL, page + 1);
|
||||
|
||||
try {
|
||||
HttpURLConnection conn = WebService.request(url, header);
|
||||
// No updates
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED)
|
||||
return false;
|
||||
// Current page is the last page
|
||||
if (!loadJSON(WebService.getString(conn)))
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
// Should not happen, but if exception occurs, page load fails
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update ETAG
|
||||
if (page == 0) {
|
||||
String etag = header.get(Const.Key.ETAG_KEY);
|
||||
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
|
||||
mm.prefs.edit().putString(Const.Key.ETAG_KEY, etag).apply();
|
||||
}
|
||||
|
||||
String links = header.get(Const.Key.LINK_KEY);
|
||||
return links == null || !links.contains("next") || loadPage(page + 1);
|
||||
}
|
||||
|
||||
private void fullReload() {
|
||||
Cursor c = mm.repoDB.getRawCursor();
|
||||
while (c.moveToNext()) {
|
||||
Repo repo = new Repo(c);
|
||||
threadPool.execute(() -> {
|
||||
try {
|
||||
repo.update();
|
||||
mm.repoDB.addRepo(repo);
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.debug(e.getMessage());
|
||||
mm.repoDB.removeRepo(repo);
|
||||
}
|
||||
});
|
||||
}
|
||||
waitTasks();
|
||||
}
|
||||
|
||||
public void exec(boolean force) {
|
||||
Topic.reset(Topic.REPO_LOAD_DONE);
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
cached = mm.repoDB.getRepoIDSet();
|
||||
threadPool = Executors.newFixedThreadPool(CORE_POOL_SIZE);
|
||||
|
||||
if (loadPage(0)) {
|
||||
waitTasks();
|
||||
// The leftover cached means they are removed from online repo
|
||||
mm.repoDB.removeRepo(cached);
|
||||
} else if (force) {
|
||||
fullReload();
|
||||
}
|
||||
Topic.publish(Topic.REPO_LOAD_DONE);
|
||||
});
|
||||
}
|
||||
|
||||
public void exec() {
|
||||
exec(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2016 dvdandroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
|
||||
/**
|
||||
* @author dvdandroid
|
||||
*/
|
||||
public class AboutCardRow extends LinearLayout {
|
||||
|
||||
public TextView mTitle;
|
||||
public TextView mSummary;
|
||||
public ImageView mIcon;
|
||||
public View mView;
|
||||
|
||||
public AboutCardRow(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AboutCardRow(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public AboutCardRow(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
LayoutInflater.from(context).inflate(R.layout.info_item_row, this);
|
||||
ViewBinder.bind(this, this);
|
||||
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AboutCardRow, 0, 0);
|
||||
String title;
|
||||
Drawable icon;
|
||||
try {
|
||||
title = a.getString(R.styleable.AboutCardRow_text);
|
||||
icon = a.getDrawable(R.styleable.AboutCardRow_icon);
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
mTitle.setText(title);
|
||||
mIcon.setImageDrawable(icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(OnClickListener l) {
|
||||
mView.setOnClickListener(l);
|
||||
}
|
||||
|
||||
public void setSummary(String s) {
|
||||
mSummary.setVisibility(VISIBLE);
|
||||
mSummary.setText(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
public class BaseFragment extends Fragment implements Topic.AutoSubscriber {
|
||||
|
||||
public MagiskManager mm;
|
||||
|
||||
public BaseFragment() {
|
||||
mm = Data.MM();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Topic.subscribe(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
Topic.unsubscribe(this);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startActivityForResult(Intent intent, int requestCode) {
|
||||
startActivityForResult(intent, requestCode, this::onActivityResult);
|
||||
}
|
||||
|
||||
public void startActivityForResult(Intent intent, int requestCode, BaseActivity.ActivityResultListener listener) {
|
||||
((BaseActivity) requireActivity()).startActivityForResult(intent, requestCode, listener);
|
||||
}
|
||||
|
||||
public void runWithPermission(String[] permissions, Runnable callback) {
|
||||
((BaseActivity) requireActivity()).runWithPermission(permissions,callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return FlavorActivity.EMPTY_INT_ARRAY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public class CustomAlertDialog extends AlertDialog.Builder {
|
||||
|
||||
private DialogInterface.OnClickListener positiveListener;
|
||||
private DialogInterface.OnClickListener negativeListener;
|
||||
private DialogInterface.OnClickListener neutralListener;
|
||||
private AlertDialog dialog;
|
||||
|
||||
private ViewHolder vh;
|
||||
|
||||
public class ViewHolder {
|
||||
public LinearLayout dialogLayout;
|
||||
public LinearLayout buttons;
|
||||
|
||||
public TextView messageView;
|
||||
public Button negative;
|
||||
public Button positive;
|
||||
public Button neutral;
|
||||
|
||||
ViewHolder(View v) {
|
||||
ViewBinder.bind(this, v);
|
||||
messageView.setVisibility(View.GONE);
|
||||
negative.setVisibility(View.GONE);
|
||||
positive.setVisibility(View.GONE);
|
||||
neutral.setVisibility(View.GONE);
|
||||
buttons.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
View v = LayoutInflater.from(getContext()).inflate(R.layout.alert_dialog, null);
|
||||
vh = new ViewHolder(v);
|
||||
super.setView(v);
|
||||
|
||||
}
|
||||
|
||||
public CustomAlertDialog(@NonNull Activity context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CustomAlertDialog(@NonNull Activity context, @StyleRes int themeResId) {
|
||||
super(context, themeResId);
|
||||
}
|
||||
|
||||
public ViewHolder getViewHolder() {
|
||||
return vh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setView(int layoutResId) { return this; }
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setView(View view) { return this; }
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setMessage(@Nullable CharSequence message) {
|
||||
vh.messageView.setVisibility(View.VISIBLE);
|
||||
vh.messageView.setText(message);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setMessage(@StringRes int messageId) {
|
||||
return setMessage(getContext().getString(messageId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setPositiveButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
vh.buttons.setVisibility(View.VISIBLE);
|
||||
vh.positive.setVisibility(View.VISIBLE);
|
||||
vh.positive.setText(text);
|
||||
positiveListener = listener;
|
||||
vh.positive.setOnClickListener((v) -> {
|
||||
if (positiveListener != null) {
|
||||
positiveListener.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setPositiveButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setPositiveButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNegativeButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
vh.buttons.setVisibility(View.VISIBLE);
|
||||
vh.negative.setVisibility(View.VISIBLE);
|
||||
vh.negative.setText(text);
|
||||
negativeListener = listener;
|
||||
vh.negative.setOnClickListener((v) -> {
|
||||
if (negativeListener != null) {
|
||||
negativeListener.onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNegativeButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setNegativeButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNeutralButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
vh.buttons.setVisibility(View.VISIBLE);
|
||||
vh.neutral.setVisibility(View.VISIBLE);
|
||||
vh.neutral.setText(text);
|
||||
neutralListener = listener;
|
||||
vh.neutral.setOnClickListener((v) -> {
|
||||
if (neutralListener != null) {
|
||||
neutralListener.onClick(dialog, DialogInterface.BUTTON_NEUTRAL);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNeutralButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setNeutralButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog create() {
|
||||
dialog = super.create();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog show() {
|
||||
create();
|
||||
dialog.show();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.InstallMagisk;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class EnvFixDialog extends CustomAlertDialog {
|
||||
|
||||
public EnvFixDialog(@NonNull Activity activity) {
|
||||
super(activity);
|
||||
setTitle(R.string.env_fix_title);
|
||||
setMessage(R.string.env_fix_msg);
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.yes, (d, i) -> new InstallMagisk(activity).exec());
|
||||
setNegativeButton(R.string.no_thanks, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
public interface ExpandableView {
|
||||
|
||||
class Container {
|
||||
public ViewGroup expandLayout;
|
||||
ValueAnimator expandAnimator, collapseAnimator;
|
||||
boolean mExpanded = false;
|
||||
int expandHeight = 0;
|
||||
}
|
||||
|
||||
// Provide state info
|
||||
Container getContainer();
|
||||
|
||||
default void setupExpandable() {
|
||||
Container container = getContainer();
|
||||
container.expandLayout.getViewTreeObserver().addOnPreDrawListener(
|
||||
new ViewTreeObserver.OnPreDrawListener() {
|
||||
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
if (container.expandHeight == 0) {
|
||||
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||
container.expandLayout.measure(widthSpec, heightSpec);
|
||||
container.expandHeight = container.expandLayout.getMeasuredHeight();
|
||||
}
|
||||
|
||||
container.expandLayout.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
container.expandLayout.setVisibility(View.GONE);
|
||||
container.expandAnimator = slideAnimator(0, container.expandHeight);
|
||||
container.collapseAnimator = slideAnimator(container.expandHeight, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
default boolean isExpanded() {
|
||||
return getContainer().mExpanded;
|
||||
}
|
||||
|
||||
default void setExpanded(boolean expanded) {
|
||||
Container container = getContainer();
|
||||
container.mExpanded = expanded;
|
||||
ViewGroup.LayoutParams layoutParams = container.expandLayout.getLayoutParams();
|
||||
layoutParams.height = expanded ? container.expandHeight : 0;
|
||||
container.expandLayout.setLayoutParams(layoutParams);
|
||||
container.expandLayout.setVisibility(expanded ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
default void expand() {
|
||||
Container container = getContainer();
|
||||
if (container.mExpanded) return;
|
||||
container.expandLayout.setVisibility(View.VISIBLE);
|
||||
container.expandAnimator.start();
|
||||
container.mExpanded = true;
|
||||
}
|
||||
|
||||
default void collapse() {
|
||||
Container container = getContainer();
|
||||
if (!container.mExpanded) return;
|
||||
container.collapseAnimator.start();
|
||||
container.mExpanded = false;
|
||||
}
|
||||
|
||||
default ValueAnimator slideAnimator(int start, int end) {
|
||||
Container container = getContainer();
|
||||
ValueAnimator animator = ValueAnimator.ofInt(start, end);
|
||||
|
||||
animator.addUpdateListener(valueAnimator -> {
|
||||
int value = (Integer) valueAnimator.getAnimatedValue();
|
||||
ViewGroup.LayoutParams layoutParams = container.expandLayout.getLayoutParams();
|
||||
layoutParams.height = value;
|
||||
container.expandLayout.setLayoutParams(layoutParams);
|
||||
});
|
||||
return animator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
public abstract class FlavorActivity extends AppCompatActivity implements Topic.AutoSubscriber {
|
||||
|
||||
private ActivityResultListener activityResultListener;
|
||||
static int[] EMPTY_INT_ARRAY = new int[0];
|
||||
public MagiskManager mm;
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
Configuration config = base.getResources().getConfiguration();
|
||||
config.setLocale(LocaleManager.locale);
|
||||
applyOverrideConfiguration(config);
|
||||
mm = Data.MM();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return EMPTY_INT_ARRAY;
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
public int getDarkTheme() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Topic.subscribe(this);
|
||||
if (Data.isDarkTheme && getDarkTheme() != -1) {
|
||||
setTheme(getDarkTheme());
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
Topic.unsubscribe(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
protected void setFloating() {
|
||||
boolean isTablet = getResources().getBoolean(R.bool.isTablet);
|
||||
if (isTablet) {
|
||||
WindowManager.LayoutParams params = getWindow().getAttributes();
|
||||
params.height = getResources().getDimensionPixelSize(R.dimen.floating_height);
|
||||
params.width = getResources().getDimensionPixelSize(R.dimen.floating_width);
|
||||
params.alpha = 1.0f;
|
||||
params.dimAmount = 0.6f;
|
||||
params.flags |= 2;
|
||||
getWindow().setAttributes(params);
|
||||
setFinishOnTouchOutside(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (activityResultListener != null)
|
||||
activityResultListener.onActivityResult(requestCode, resultCode, data);
|
||||
activityResultListener = null;
|
||||
}
|
||||
|
||||
public void startActivityForResult(Intent intent, int requestCode, ActivityResultListener listener) {
|
||||
activityResultListener = listener;
|
||||
super.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
public interface ActivityResultListener {
|
||||
void onActivityResult(int requestCode, int resultCode, Intent data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
class InstallMethodDialog extends AlertDialog.Builder {
|
||||
|
||||
InstallMethodDialog(BaseActivity activity, List<String> options) {
|
||||
super(activity);
|
||||
setTitle(R.string.select_method);
|
||||
setItems(options.toArray(new String [0]), (dialog, idx) -> {
|
||||
Intent intent;
|
||||
switch (idx) {
|
||||
case 1:
|
||||
if (Data.remoteMagiskVersionCode < 1400) {
|
||||
SnackbarMaker.make(activity, R.string.no_boot_file_patch_support,
|
||||
Snackbar.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
Utils.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
|
||||
intent = new Intent(Intent.ACTION_GET_CONTENT).setType("*/*");
|
||||
activity.startActivityForResult(intent, Const.ID.SELECT_BOOT,
|
||||
(requestCode, resultCode, data) -> {
|
||||
if (requestCode == Const.ID.SELECT_BOOT &&
|
||||
resultCode == BaseActivity.RESULT_OK && data != null) {
|
||||
Intent i = new Intent(activity, Data.classMap.get(FlashActivity.class))
|
||||
.putExtra(Const.Key.FLASH_SET_BOOT, data.getData())
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_BOOT);
|
||||
activity.startActivity(i);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 0:
|
||||
String filename = Utils.fmt("Magisk-v%s(%d).zip",
|
||||
Data.remoteMagiskVersionString, Data.remoteMagiskVersionCode);
|
||||
Download.receive(activity, new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
SnackbarMaker.showUri(activity, uri);
|
||||
}
|
||||
}, Data.magiskLink, filename);
|
||||
break;
|
||||
case 2:
|
||||
intent = new Intent(activity, Data.classMap.get(FlashActivity.class))
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_MAGISK);
|
||||
activity.startActivity(intent);
|
||||
break;
|
||||
case 3:
|
||||
new CustomAlertDialog(activity)
|
||||
.setTitle(R.string.warning)
|
||||
.setMessage(R.string.install_inactive_slot_msg)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.yes, (d, i) -> {
|
||||
Intent it = new Intent(activity, Data.classMap.get(FlashActivity.class))
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_INACTIVE_SLOT);
|
||||
activity.startActivity(it);
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MagiskInstallDialog extends CustomAlertDialog {
|
||||
public MagiskInstallDialog(BaseActivity activity) {
|
||||
super(activity);
|
||||
MagiskManager mm = Data.MM();
|
||||
String filename = Utils.fmt("Magisk-v%s(%d).zip",
|
||||
Data.remoteMagiskVersionString, Data.remoteMagiskVersionCode);
|
||||
setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.magisk)));
|
||||
setMessage(mm.getString(R.string.repo_install_msg, filename));
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.install, (d, i) -> {
|
||||
List<String> options = new ArrayList<>();
|
||||
options.add(mm.getString(R.string.download_zip_only));
|
||||
options.add(mm.getString(R.string.patch_boot_file));
|
||||
if (Shell.rootAccess()) {
|
||||
options.add(mm.getString(R.string.direct_install));
|
||||
String s = ShellUtils.fastCmd("grep_prop ro.build.ab_update");
|
||||
if (!s.isEmpty() && Boolean.parseBoolean(s)) {
|
||||
options.add(mm.getString(R.string.install_inactive_slot));
|
||||
}
|
||||
}
|
||||
new InstallMethodDialog(activity, options).show();
|
||||
});
|
||||
setNegativeButton(R.string.no_thanks, null);
|
||||
if (!TextUtils.isEmpty(Data.magiskNoteLink)) {
|
||||
setNeutralButton(R.string.release_notes, (d, i) -> {
|
||||
if (Data.magiskNoteLink.contains("forum.xda-developers")) {
|
||||
// Open forum links in browser
|
||||
Utils.openLink(activity, Uri.parse(Data.magiskNoteLink));
|
||||
} else {
|
||||
new MarkDownWindow(activity, null, Data.magiskNoteLink).exec();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ManagerInstallDialog extends CustomAlertDialog {
|
||||
|
||||
public ManagerInstallDialog(@NonNull BaseActivity activity) {
|
||||
super(activity);
|
||||
MagiskManager mm = Data.MM();
|
||||
String filename = Utils.fmt("MagiskManager-v%s(%d).apk",
|
||||
Data.remoteManagerVersionString, Data.remoteManagerVersionCode);
|
||||
setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.app_name)));
|
||||
setMessage(mm.getString(R.string.repo_install_msg, filename));
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.install, (d, i) -> activity.runWithPermission(
|
||||
new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, () -> {
|
||||
Intent intent = new Intent(mm, Data.classMap.get(ManagerUpdate.class));
|
||||
intent.putExtra(Const.Key.INTENT_SET_LINK, Data.managerLink);
|
||||
intent.putExtra(Const.Key.INTENT_SET_FILENAME, filename);
|
||||
mm.sendBroadcast(intent);
|
||||
}))
|
||||
.setNegativeButton(R.string.no_thanks, null);
|
||||
if (!TextUtils.isEmpty(Data.managerNoteLink)) {
|
||||
setNeutralButton(R.string.app_changelog, (d, i) ->
|
||||
new MarkDownWindow(activity, null, Data.managerNoteLink).exec());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
public class SnackbarMaker {
|
||||
|
||||
public static Snackbar make(Activity activity, CharSequence text, int duration) {
|
||||
View view = activity.findViewById(android.R.id.content);
|
||||
return make(view, text, duration);
|
||||
}
|
||||
|
||||
public static Snackbar make(Activity activity, @StringRes int resId, int duration) {
|
||||
return make(activity, activity.getString(resId), duration);
|
||||
}
|
||||
|
||||
public static Snackbar make(View view, CharSequence text, int duration) {
|
||||
Snackbar snack = Snackbar.make(view, text, duration);
|
||||
setup(snack);
|
||||
return snack;
|
||||
}
|
||||
|
||||
public static Snackbar make(View view, @StringRes int resId, int duration) {
|
||||
Snackbar snack = Snackbar.make(view, resId, duration);
|
||||
setup(snack);
|
||||
return snack;
|
||||
}
|
||||
|
||||
private static void setup(Snackbar snack) {
|
||||
TextView text = snack.getView().findViewById(com.google.android.material.R.id.snackbar_text);
|
||||
text.setMaxLines(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
public static void showUri(Activity activity, Uri uri) {
|
||||
make(activity, activity.getString(R.string.internal_storage,
|
||||
"/Download/" + Utils.getNameFromUri(activity, uri)),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.ok, (v)->{}).show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class UninstallDialog extends CustomAlertDialog {
|
||||
|
||||
public UninstallDialog(@NonNull Activity activity) {
|
||||
super(activity);
|
||||
MagiskManager mm = Data.MM();
|
||||
setTitle(R.string.uninstall_magisk_title);
|
||||
setMessage(R.string.uninstall_magisk_msg);
|
||||
setNeutralButton(R.string.restore_img, (d, i) -> {
|
||||
ProgressDialog dialog = ProgressDialog.show(activity,
|
||||
activity.getString(R.string.restore_img),
|
||||
activity.getString(R.string.restore_img_msg));
|
||||
Shell.su("restore_imgs").submit(result -> {
|
||||
dialog.cancel();
|
||||
if (result.isSuccess()) {
|
||||
Utils.toast(R.string.restore_done, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
Utils.toast(R.string.restore_fail, Toast.LENGTH_LONG);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!TextUtils.isEmpty(Data.uninstallerLink)) {
|
||||
setPositiveButton(R.string.complete_uninstall, (d, i) ->
|
||||
Download.receive(activity, new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
Intent intent = new Intent(context, Data.classMap.get(FlashActivity.class))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.setData(uri)
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.UNINSTALL);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}, Data.uninstallerLink, "magisk-uninstaller.zip"));
|
||||
}
|
||||
}
|
||||
}
|
||||
127
app/src/full/java/com/topjohnwu/magisk/container/BaseModule.java
Normal file
127
app/src/full/java/com/topjohnwu/magisk/container/BaseModule.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public abstract class BaseModule implements Comparable<BaseModule> {
|
||||
|
||||
private String mId, mName, mVersion, mAuthor, mDescription;
|
||||
private int mVersionCode = -1, minMagiskVersion = -1;
|
||||
|
||||
protected BaseModule() {
|
||||
mId = mName = mVersion = mAuthor = mDescription = "";
|
||||
}
|
||||
|
||||
protected BaseModule(Cursor c) {
|
||||
mId = nonNull(c.getString(c.getColumnIndex("id")));
|
||||
mName = nonNull(c.getString(c.getColumnIndex("name")));
|
||||
mVersion = nonNull(c.getString(c.getColumnIndex("version")));
|
||||
mVersionCode = c.getInt(c.getColumnIndex("versionCode"));
|
||||
mAuthor = nonNull(c.getString(c.getColumnIndex("author")));
|
||||
mDescription = nonNull(c.getString(c.getColumnIndex("description")));
|
||||
minMagiskVersion = c.getInt(c.getColumnIndex("minMagisk"));
|
||||
}
|
||||
|
||||
private String nonNull(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("id", mId);
|
||||
values.put("name", mName);
|
||||
values.put("version", mVersion);
|
||||
values.put("versionCode", mVersionCode);
|
||||
values.put("author", mAuthor);
|
||||
values.put("description", mDescription);
|
||||
values.put("minMagisk", minMagiskVersion);
|
||||
return values;
|
||||
}
|
||||
|
||||
protected void parseProps(List<String> props) { parseProps(props.toArray(new String[0])); }
|
||||
|
||||
protected void parseProps(String[] props) throws NumberFormatException {
|
||||
for (String line : props) {
|
||||
String[] prop = line.split("=", 2);
|
||||
if (prop.length != 2)
|
||||
continue;
|
||||
|
||||
String key = prop[0].trim();
|
||||
String value = prop[1].trim();
|
||||
if (key.isEmpty() || key.charAt(0) == '#')
|
||||
continue;
|
||||
|
||||
switch (key) {
|
||||
case "id":
|
||||
mId = value;
|
||||
break;
|
||||
case "name":
|
||||
mName = value;
|
||||
break;
|
||||
case "version":
|
||||
mVersion = value;
|
||||
break;
|
||||
case "versionCode":
|
||||
mVersionCode = Integer.parseInt(value);
|
||||
break;
|
||||
case "author":
|
||||
mAuthor = value;
|
||||
break;
|
||||
case "description":
|
||||
mDescription = value;
|
||||
break;
|
||||
case "minMagisk":
|
||||
case "template":
|
||||
minMagiskVersion = Integer.parseInt(value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return mVersion;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return mAuthor;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
mId = id;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
public int getVersionCode() {
|
||||
return mVersionCode;
|
||||
}
|
||||
|
||||
public int getMinMagiskVersion() {
|
||||
return minMagiskVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull BaseModule module) {
|
||||
return this.getName().toLowerCase().compareTo(module.getName().toLowerCase());
|
||||
}
|
||||
}
|
||||
67
app/src/full/java/com/topjohnwu/magisk/container/Module.java
Normal file
67
app/src/full/java/com/topjohnwu/magisk/container/Module.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
public class Module extends BaseModule {
|
||||
|
||||
private SuFile mRemoveFile, mDisableFile, mUpdateFile;
|
||||
private boolean mEnable, mRemove, mUpdated;
|
||||
|
||||
public Module(String path) {
|
||||
|
||||
try {
|
||||
parseProps(Shell.Sync.su("dos2unix < " + path + "/module.prop"));
|
||||
} catch (NumberFormatException ignored) {}
|
||||
|
||||
mRemoveFile = new SuFile(path, "remove");
|
||||
mDisableFile = new SuFile(path, "disable");
|
||||
mUpdateFile = new SuFile(path, "update");
|
||||
|
||||
if (getId() == null) {
|
||||
int sep = path.lastIndexOf('/');
|
||||
setId(path.substring(sep + 1));
|
||||
}
|
||||
|
||||
if (getName() == null) {
|
||||
setName(getId());
|
||||
}
|
||||
|
||||
mEnable = !mDisableFile.exists();
|
||||
mRemove = mRemoveFile.exists();
|
||||
mUpdated = mUpdateFile.exists();
|
||||
}
|
||||
|
||||
public void createDisableFile() {
|
||||
mEnable = false;
|
||||
mDisableFile.createNewFile();
|
||||
}
|
||||
|
||||
public void removeDisableFile() {
|
||||
mEnable = true;
|
||||
mDisableFile.delete();
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return mEnable;
|
||||
}
|
||||
|
||||
public void createRemoveFile() {
|
||||
mRemove = true;
|
||||
mRemoveFile.createNewFile();
|
||||
}
|
||||
|
||||
public void deleteRemoveFile() {
|
||||
mRemove = false;
|
||||
mRemoveFile.delete();
|
||||
}
|
||||
|
||||
public boolean willBeRemoved() {
|
||||
return mRemove;
|
||||
}
|
||||
|
||||
public boolean isUpdated() {
|
||||
return mUpdated;
|
||||
}
|
||||
|
||||
}
|
||||
60
app/src/full/java/com/topjohnwu/magisk/container/Policy.java
Normal file
60
app/src/full/java/com/topjohnwu/magisk/container/Policy.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
||||
public class Policy implements Comparable<Policy>{
|
||||
public static final int INTERACTIVE = 0;
|
||||
public static final int DENY = 1;
|
||||
public static final int ALLOW = 2;
|
||||
|
||||
public int uid, policy = INTERACTIVE;
|
||||
public long until;
|
||||
public boolean logging = true, notification = true;
|
||||
public String packageName, appName;
|
||||
public ApplicationInfo info;
|
||||
|
||||
public Policy(int uid, PackageManager pm) throws PackageManager.NameNotFoundException {
|
||||
String[] pkgs = pm.getPackagesForUid(uid);
|
||||
if (pkgs == null || pkgs.length == 0)
|
||||
throw new PackageManager.NameNotFoundException();
|
||||
this.uid = uid;
|
||||
packageName = pkgs[0];
|
||||
info = pm.getApplicationInfo(packageName, 0);
|
||||
appName = info.loadLabel(pm).toString();
|
||||
}
|
||||
|
||||
public Policy(Cursor c, PackageManager pm) throws PackageManager.NameNotFoundException {
|
||||
uid = c.getInt(c.getColumnIndex("uid"));
|
||||
packageName = c.getString(c.getColumnIndex("package_name"));
|
||||
policy = c.getInt(c.getColumnIndex("policy"));
|
||||
until = c.getLong(c.getColumnIndex("until"));
|
||||
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();
|
||||
}
|
||||
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("uid", uid);
|
||||
values.put("package_name", packageName);
|
||||
values.put("policy", policy);
|
||||
values.put("until", until);
|
||||
values.put("logging", logging ? 1 : 0);
|
||||
values.put("notification", notification ? 1 : 0);
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull Policy policy) {
|
||||
return appName.toLowerCase().compareTo(policy.appName.toLowerCase());
|
||||
}
|
||||
}
|
||||
96
app/src/full/java/com/topjohnwu/magisk/container/Repo.java
Normal file
96
app/src/full/java/com/topjohnwu/magisk/container/Repo.java
Normal file
@@ -0,0 +1,96 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class Repo extends BaseModule {
|
||||
|
||||
private String repoName;
|
||||
private Date mLastUpdate;
|
||||
|
||||
public Repo(String name) {
|
||||
repoName = name;
|
||||
}
|
||||
|
||||
public Repo(Cursor c) {
|
||||
super(c);
|
||||
repoName = c.getString(c.getColumnIndex("repo_name"));
|
||||
mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update")));
|
||||
}
|
||||
|
||||
public void update() throws IllegalRepoException {
|
||||
String props[] = Utils.dos2unix(WebService.getString(getManifestUrl())).split("\\n");
|
||||
try {
|
||||
parseProps(props);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] parse error: " + e.getMessage());
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(getId())) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] does not contain id");
|
||||
}
|
||||
if (getVersionCode() < 0) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] does not contain versionCode");
|
||||
}
|
||||
if (getMinMagiskVersion() < Const.MIN_MODULE_VER()) {
|
||||
Logger.debug("Repo [" + repoName + "] is outdated");
|
||||
}
|
||||
}
|
||||
|
||||
public void update(Date lastUpdate) throws IllegalRepoException {
|
||||
mLastUpdate = lastUpdate;
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = super.getContentValues();
|
||||
values.put("repo_name", repoName);
|
||||
values.put("last_update", mLastUpdate.getTime());
|
||||
return values;
|
||||
}
|
||||
|
||||
public String getRepoName() {
|
||||
return repoName;
|
||||
}
|
||||
|
||||
public String getZipUrl() {
|
||||
return String.format(Const.Url.ZIP_URL, repoName);
|
||||
}
|
||||
|
||||
public String getManifestUrl() {
|
||||
return String.format(Const.Url.FILE_URL, repoName, "module.prop");
|
||||
}
|
||||
|
||||
public String getDetailUrl() {
|
||||
return String.format(Const.Url.FILE_URL, repoName, "README.md");
|
||||
}
|
||||
|
||||
public String getLastUpdateString() {
|
||||
return DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(mLastUpdate);
|
||||
}
|
||||
|
||||
public Date getLastUpdate() {
|
||||
return mLastUpdate;
|
||||
}
|
||||
|
||||
public String getDownloadFilename() {
|
||||
return Download.getLegalFilename(getName() + "-" + getVersion() + ".zip");
|
||||
}
|
||||
|
||||
public class IllegalRepoException extends Exception {
|
||||
IllegalRepoException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class SuLogEntry {
|
||||
|
||||
public int fromUid, toUid, fromPid;
|
||||
public String packageName, appName, command;
|
||||
public boolean action;
|
||||
public Date date;
|
||||
|
||||
public SuLogEntry(Policy policy) {
|
||||
fromUid = policy.uid;
|
||||
packageName = policy.packageName;
|
||||
appName = policy.appName;
|
||||
}
|
||||
|
||||
public SuLogEntry(Cursor c) {
|
||||
fromUid = c.getInt(c.getColumnIndex("from_uid"));
|
||||
fromPid = c.getInt(c.getColumnIndex("from_pid"));
|
||||
toUid = c.getInt(c.getColumnIndex("to_uid"));
|
||||
packageName = c.getString(c.getColumnIndex("package_name"));
|
||||
appName = c.getString(c.getColumnIndex("app_name"));
|
||||
command = c.getString(c.getColumnIndex("command"));
|
||||
action = c.getInt(c.getColumnIndex("action")) != 0;
|
||||
date = new Date(c.getLong(c.getColumnIndex("time")));
|
||||
}
|
||||
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("from_uid", fromUid);
|
||||
values.put("package_name", packageName);
|
||||
values.put("app_name", appName);
|
||||
values.put("from_pid", fromPid);
|
||||
values.put("command", command);
|
||||
values.put("to_uid", toUid);
|
||||
values.put("action", action ? 1 : 0);
|
||||
values.put("time", date.getTime());
|
||||
return values;
|
||||
}
|
||||
|
||||
public String getDateString() {
|
||||
return DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date);
|
||||
}
|
||||
|
||||
public String getTimeString() {
|
||||
return new SimpleDateFormat("h:mm a", LocaleManager.locale).format(date);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import org.kamranzafar.jtar.TarHeader;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class TarEntry extends org.kamranzafar.jtar.TarEntry {
|
||||
|
||||
public TarEntry(File file, String entryName) {
|
||||
super(file, entryName);
|
||||
}
|
||||
|
||||
/*
|
||||
* Workaround missing java.nio.file.attribute.PosixFilePermission
|
||||
* Simply just assign a default permission to the file
|
||||
* */
|
||||
|
||||
@Override
|
||||
public void extractTarHeader(String entryName) {
|
||||
int permissions = file.isDirectory() ? 000755 : 000644;
|
||||
header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory(), permissions);
|
||||
header.userName = new StringBuffer("");
|
||||
header.groupName = header.userName;
|
||||
}
|
||||
|
||||
/*
|
||||
* Rewrite the header to GNU format
|
||||
* */
|
||||
|
||||
@Override
|
||||
public void writeEntryHeader(byte[] outbuf) {
|
||||
super.writeEntryHeader(outbuf);
|
||||
|
||||
System.arraycopy("ustar \0".getBytes(), 0, outbuf, 257, TarHeader.USTAR_MAGICLEN);
|
||||
getOctalBytes(header.mode, outbuf, 100, TarHeader.MODELEN);
|
||||
getOctalBytes(header.userId, outbuf, 108, TarHeader.UIDLEN);
|
||||
getOctalBytes(header.groupId, outbuf, 116, TarHeader.GIDLEN);
|
||||
getOctalBytes(header.size, outbuf, 124, TarHeader.SIZELEN);
|
||||
getOctalBytes(header.modTime, outbuf, 136, TarHeader.MODTIMELEN);
|
||||
Arrays.fill(outbuf, 148, 148 + TarHeader.CHKSUMLEN, (byte) ' ');
|
||||
Arrays.fill(outbuf, 329, 329 + TarHeader.USTAR_DEVLEN, (byte) '\0');
|
||||
Arrays.fill(outbuf, 337, 337 + TarHeader.USTAR_DEVLEN, (byte) '\0');
|
||||
|
||||
// Recalculate checksum
|
||||
getOctalBytes(computeCheckSum(outbuf), outbuf, 148, TarHeader.CHKSUMLEN);
|
||||
}
|
||||
|
||||
/*
|
||||
* Proper octal to ASCII conversion
|
||||
* */
|
||||
|
||||
private void getOctalBytes(long value, byte[] buf, int offset, int length) {
|
||||
int idx = length - 1;
|
||||
|
||||
buf[offset + idx] = 0;
|
||||
--idx;
|
||||
|
||||
for (long val = value; idx >= 0; --idx) {
|
||||
buf[offset + idx] = (byte) ((byte) '0' + (byte) (val & 7));
|
||||
val = val >> 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ValueSortedMap<K, V extends Comparable<? super V>> extends HashMap<K, V> {
|
||||
|
||||
private List<V> sorted = new ArrayList<>();
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
if (sorted.isEmpty()) {
|
||||
sorted.addAll(super.values());
|
||||
Collections.sort(sorted);
|
||||
}
|
||||
return sorted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
sorted.clear();
|
||||
return super.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends K, ? extends V> m) {
|
||||
sorted.clear();
|
||||
super.putAll(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
sorted.clear();
|
||||
return super.remove(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
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.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
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;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class MagiskDatabaseHelper {
|
||||
|
||||
private static final int DATABASE_VER = 6;
|
||||
private static final int OLD_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;
|
||||
private MagiskManager mm;
|
||||
|
||||
@NonNull
|
||||
public static MagiskDatabaseHelper getInstance(MagiskManager mm) {
|
||||
try {
|
||||
return new MagiskDatabaseHelper(mm);
|
||||
} catch (Exception e) {
|
||||
// Let's cleanup everything and try again
|
||||
Shell.su("db_clean '*'").exec();
|
||||
return new MagiskDatabaseHelper(mm);
|
||||
}
|
||||
}
|
||||
|
||||
private MagiskDatabaseHelper(MagiskManager context) {
|
||||
mm = context;
|
||||
pm = mm.getPackageManager();
|
||||
db = openDatabase(mm);
|
||||
db.disableWriteAheadLogging();
|
||||
int version = Data.magiskVersionCode >= Const.MAGISK_VER.DBVER_SIX ? DATABASE_VER : OLD_DATABASE_VER;
|
||||
int curVersion = db.getVersion();
|
||||
if (curVersion < version) {
|
||||
onUpgrade(db, curVersion);
|
||||
} else if (curVersion > DATABASE_VER) {
|
||||
/* Higher than we can possibly support */
|
||||
onDowngrade(db);
|
||||
}
|
||||
db.setVersion(version);
|
||||
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);
|
||||
}
|
||||
// Cleanup
|
||||
Shell.su("db_clean " + Const.USER_ID).exec();
|
||||
if (Data.magiskVersionCode < Const.MAGISK_VER.FBE_AWARE) {
|
||||
// Super old legacy mode
|
||||
return mm.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
|
||||
} else if (Data.magiskVersionCode < Const.MAGISK_VER.HIDDEN_PATH) {
|
||||
// Legacy mode with FBE aware
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
de.moveDatabaseFrom(mm, "su.db");
|
||||
}
|
||||
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 (Data.magiskVersionCode < Const.MAGISK_VER.SEPOL_REFACTOR) {
|
||||
// We need some additional policies on old versions
|
||||
Shell.su("db_sepatch").exec();
|
||||
}
|
||||
if (!GLOBAL_DB.exists()) {
|
||||
Shell.su("db_init").exec();
|
||||
SQLiteDatabase.openOrCreateDatabase(GLOBAL_DB, null).close();
|
||||
Shell.su("db_restore").exec();
|
||||
}
|
||||
}
|
||||
Shell.su("db_setup " + Process.myUid()).exec();
|
||||
}
|
||||
// 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));
|
||||
|
||||
Data.MM().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;
|
||||
}
|
||||
if (oldVersion == 5) {
|
||||
setSettings(Const.Key.SU_FINGERPRINT,
|
||||
mm.prefs.getBoolean(Const.Key.SU_FINGERPRINT, false) ? 1 : 0);
|
||||
++oldVersion;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove everything, we do not support downgrade
|
||||
public void onDowngrade(SQLiteDatabase db) {
|
||||
Utils.toast(R.string.su_db_corrupt, Toast.LENGTH_LONG);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + POLICY_TABLE);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + LOG_TABLE);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + SETTINGS_TABLE);
|
||||
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() - Data.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, LocaleManager.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.topjohnwu.magisk.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final int DATABASE_VER = 3;
|
||||
private static final String TABLE_NAME = "repos";
|
||||
|
||||
private SQLiteDatabase mDb;
|
||||
private MagiskManager mm;
|
||||
private ReposAdapter adapter;
|
||||
|
||||
public RepoDatabaseHelper(Context context) {
|
||||
super(context, "repo.db", null, DATABASE_VER);
|
||||
mm = Data.MM();
|
||||
mDb = getWritableDatabase();
|
||||
|
||||
// Remove outdated repos
|
||||
mDb.delete(TABLE_NAME, "minMagisk<?",
|
||||
new String[] { String.valueOf(Const.MIN_MODULE_VER()) });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
onUpgrade(db, 0, DATABASE_VER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
try {
|
||||
if (oldVersion < 3) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
|
||||
"(id TEXT, name TEXT, version TEXT, versionCode INT, minMagisk INT, " +
|
||||
"author TEXT, description TEXT, repo_name TEXT, last_update INT, " +
|
||||
"PRIMARY KEY(id))");
|
||||
mm.prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
||||
oldVersion = 3;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
// Reset database
|
||||
onDowngrade(db, DATABASE_VER, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
onUpgrade(db, 0, DATABASE_VER);
|
||||
}
|
||||
|
||||
public void clearRepo() {
|
||||
mDb.delete(TABLE_NAME, null, null);
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
|
||||
public void removeRepo(String id) {
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public void removeRepo(Repo repo) {
|
||||
mDb.delete(TABLE_NAME, "repo_name=?", new String[] { repo.getRepoName() });
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public void removeRepo(Iterable<String> list) {
|
||||
for (String id : list) {
|
||||
if (id == null) continue;
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||
}
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public void addRepo(Repo repo) {
|
||||
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public Repo getRepo(String id) {
|
||||
try (Cursor c = mDb.query(TABLE_NAME, null, "id=?", new String[] { id }, null, null, null)) {
|
||||
if (c.moveToNext()) {
|
||||
return new Repo(c);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Cursor getRawCursor() {
|
||||
return mDb.query(TABLE_NAME, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public Cursor getRepoCursor() {
|
||||
String orderBy = null;
|
||||
switch (Data.repoOrder) {
|
||||
case Const.Value.ORDER_NAME:
|
||||
orderBy = "name COLLATE NOCASE";
|
||||
break;
|
||||
case Const.Value.ORDER_DATE:
|
||||
orderBy = "last_update DESC";
|
||||
}
|
||||
return mDb.query(TABLE_NAME, null, "minMagisk<=? AND minMagisk>=?",
|
||||
new String[] { String.valueOf(Data.magiskVersionCode), String.valueOf(Const.MIN_MODULE_VER()) },
|
||||
null, null, orderBy);
|
||||
}
|
||||
|
||||
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()) {
|
||||
set.add(c.getString(c.getColumnIndex("id")));
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
public void registerAdapter(ReposAdapter a) {
|
||||
adapter = a;
|
||||
}
|
||||
|
||||
public void unregisterAdapter() {
|
||||
adapter = null;
|
||||
}
|
||||
|
||||
private void notifyAdapter() {
|
||||
if (adapter != null) {
|
||||
Data.mainHandler.post(adapter::notifyDBChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MainActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
import com.topjohnwu.magisk.adapters.TabFragmentAdapter;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
public class LogFragment extends BaseFragment {
|
||||
|
||||
public ViewPager viewPager;
|
||||
public TabLayout tab;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
View v = inflater.inflate(R.layout.fragment_log, container, false);
|
||||
ViewBinder.bind(this, v);
|
||||
|
||||
((MainActivity) requireActivity()).toolbar.setElevation(0);
|
||||
|
||||
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
|
||||
|
||||
if (!(Const.USER_ID > 0 && Data.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
adapter.addTab(new SuLogFragment(), getString(R.string.superuser));
|
||||
}
|
||||
adapter.addTab(new MagiskLogFragment(), getString(R.string.magisk));
|
||||
tab.setupWithViewPager(viewPager);
|
||||
tab.setVisibility(View.VISIBLE);
|
||||
|
||||
viewPager.setAdapter(adapter);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
ViewBinder.unbind(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MainActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
import com.topjohnwu.magisk.asyncs.CheckSafetyNet;
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.components.EnvFixDialog;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.components.MagiskInstallDialog;
|
||||
import com.topjohnwu.magisk.components.ManagerInstallDialog;
|
||||
import com.topjohnwu.magisk.components.UninstallDialog;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.ISafetyNetHelper;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
public class MagiskFragment extends BaseFragment
|
||||
implements SwipeRefreshLayout.OnRefreshListener, ExpandableView, Topic.Subscriber {
|
||||
|
||||
private Container expandableContainer = new Container();
|
||||
private static boolean shownDialog = false;
|
||||
|
||||
public SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
|
||||
public CardView coreOnlyNotice;
|
||||
|
||||
public RelativeLayout magiskUpdate;
|
||||
public ImageView magiskUpdateIcon;
|
||||
public TextView magiskUpdateText;
|
||||
public ProgressBar magiskUpdateProgress;
|
||||
public ImageView magiskStatusIcon;
|
||||
public TextView magiskVersionText;
|
||||
|
||||
public CardView safetyNetCard;
|
||||
public ImageView safetyNetRefreshIcon;
|
||||
public TextView safetyNetStatusText;
|
||||
public ProgressBar safetyNetProgress;
|
||||
public LinearLayout expandLayout;
|
||||
public ImageView ctsStatusIcon;
|
||||
public TextView ctsStatusText;
|
||||
public ImageView basicStatusIcon;
|
||||
public TextView basicStatusText;
|
||||
|
||||
public CardView installOptionCard;
|
||||
public CheckBox keepEncChkbox;
|
||||
public CheckBox keepVerityChkbox;
|
||||
public CardView installButton;
|
||||
public TextView installText;
|
||||
public CardView uninstallButton;
|
||||
|
||||
public int colorBad;
|
||||
public int colorOK;
|
||||
public int colorWarn;
|
||||
public int colorNeutral;
|
||||
public int colorInfo;
|
||||
|
||||
public void safetyNet() {
|
||||
Runnable task = () -> {
|
||||
safetyNetProgress.setVisibility(View.VISIBLE);
|
||||
safetyNetRefreshIcon.setVisibility(View.GONE);
|
||||
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
|
||||
new CheckSafetyNet(requireActivity()).exec();
|
||||
collapse();
|
||||
};
|
||||
if (!CheckSafetyNet.dexPath.exists()) {
|
||||
// Show dialog
|
||||
new CustomAlertDialog(requireActivity())
|
||||
.setTitle(R.string.proprietary_title)
|
||||
.setMessage(R.string.proprietary_notice)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.yes, (d, i) -> task.run())
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
} else {
|
||||
task.run();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void install() {
|
||||
shownDialog = true;
|
||||
|
||||
// Show Manager update first
|
||||
if (Data.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
new ManagerInstallDialog((BaseActivity) requireActivity()).show();
|
||||
return;
|
||||
}
|
||||
|
||||
((NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
|
||||
new MagiskInstallDialog((BaseActivity) getActivity()).show();
|
||||
}
|
||||
|
||||
public void uninstall() {
|
||||
new UninstallDialog(requireActivity()).show();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.fragment_magisk, container, false);
|
||||
ViewBinder.bind(this, v);
|
||||
requireActivity().setTitle(R.string.magisk);
|
||||
|
||||
expandableContainer.expandLayout = expandLayout;
|
||||
setupExpandable();
|
||||
|
||||
keepVerityChkbox.setChecked(Data.keepVerity);
|
||||
keepVerityChkbox.setOnCheckedChangeListener((view, checked) -> Data.keepVerity = checked);
|
||||
keepEncChkbox.setChecked(Data.keepEnc);
|
||||
keepEncChkbox.setOnCheckedChangeListener((view, checked) -> Data.keepEnc = checked);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(this);
|
||||
updateUI();
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
ViewBinder.unbind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
Data.loadMagiskInfo();
|
||||
updateUI();
|
||||
|
||||
magiskUpdateText.setText(R.string.checking_for_updates);
|
||||
magiskUpdateProgress.setVisibility(View.VISIBLE);
|
||||
magiskUpdateIcon.setVisibility(View.GONE);
|
||||
|
||||
safetyNetStatusText.setText(R.string.safetyNet_check_text);
|
||||
|
||||
Topic.reset(getSubscribedTopics());
|
||||
Data.remoteMagiskVersionString = null;
|
||||
Data.remoteMagiskVersionCode = -1;
|
||||
collapse();
|
||||
|
||||
shownDialog = false;
|
||||
|
||||
// Trigger state check
|
||||
if (Download.checkNetworkStatus(mm)) {
|
||||
CheckUpdates.check();
|
||||
} else {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.SNET_CHECK_DONE, Topic.UPDATE_CHECK_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
switch (topic) {
|
||||
case Topic.SNET_CHECK_DONE:
|
||||
updateSafetyNetUI((int) result[0]);
|
||||
break;
|
||||
case Topic.UPDATE_CHECK_DONE:
|
||||
updateCheckUI();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Container getContainer() {
|
||||
return expandableContainer;
|
||||
}
|
||||
|
||||
private boolean hasGms() {
|
||||
PackageManager pm = mm.getPackageManager();
|
||||
PackageInfo info;
|
||||
try {
|
||||
info = pm.getPackageInfo("com.google.android.gms", 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
return info.applicationInfo.enabled;
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
((MainActivity) requireActivity()).checkHideSection();
|
||||
|
||||
boolean hasNetwork = Download.checkNetworkStatus(mm);
|
||||
boolean hasRoot = Shell.rootAccess();
|
||||
boolean isUpToDate = Data.magiskVersionCode > Const.MAGISK_VER.UNIFIED;
|
||||
|
||||
magiskUpdate.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
installOptionCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
uninstallButton.setVisibility(isUpToDate && hasRoot ? View.VISIBLE : View.GONE);
|
||||
coreOnlyNotice.setVisibility(mm.prefs.getBoolean(Const.Key.COREONLY, false) ? View.VISIBLE : View.GONE);
|
||||
|
||||
int image, color;
|
||||
|
||||
if (Data.magiskVersionCode < 0) {
|
||||
color = colorBad;
|
||||
image = R.drawable.ic_cancel;
|
||||
magiskVersionText.setText(R.string.magisk_version_error);
|
||||
} else {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
magiskVersionText.setText(getString(R.string.current_magisk_title, "v" + Data.magiskVersionString));
|
||||
}
|
||||
|
||||
magiskStatusIcon.setImageResource(image);
|
||||
magiskStatusIcon.setColorFilter(color);
|
||||
}
|
||||
|
||||
private void updateCheckUI() {
|
||||
int image, color;
|
||||
|
||||
safetyNetCard.setVisibility(hasGms() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (Data.remoteMagiskVersionCode < 0) {
|
||||
color = colorNeutral;
|
||||
image = R.drawable.ic_help;
|
||||
magiskUpdateText.setText(R.string.invalid_update_channel);
|
||||
installButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
magiskUpdateText.setText(getString(R.string.install_magisk_title, "v" + Data.remoteMagiskVersionString));
|
||||
installButton.setVisibility(View.VISIBLE);
|
||||
if (Data.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
installText.setText(getString(R.string.update, getString(R.string.app_name)));
|
||||
} else if (Data.magiskVersionCode > 0 && Data.remoteMagiskVersionCode > Data.magiskVersionCode) {
|
||||
installText.setText(getString(R.string.update, getString(R.string.magisk)));
|
||||
} else {
|
||||
installText.setText(R.string.install);
|
||||
}
|
||||
}
|
||||
|
||||
magiskUpdateIcon.setImageResource(image);
|
||||
magiskUpdateIcon.setColorFilter(color);
|
||||
magiskUpdateIcon.setVisibility(View.VISIBLE);
|
||||
|
||||
magiskUpdateProgress.setVisibility(View.GONE);
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
|
||||
if (!shownDialog) {
|
||||
if (Data.remoteMagiskVersionCode > Data.magiskVersionCode
|
||||
|| Data.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
install();
|
||||
} else if (Data.remoteMagiskVersionCode >= Const.MAGISK_VER.FIX_ENV &&
|
||||
!ShellUtils.fastCmdResult("env_check")) {
|
||||
new EnvFixDialog(requireActivity()).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSafetyNetUI(int response) {
|
||||
safetyNetProgress.setVisibility(View.GONE);
|
||||
safetyNetRefreshIcon.setVisibility(View.VISIBLE);
|
||||
if ((response & 0x0F) == 0) {
|
||||
safetyNetStatusText.setText(R.string.safetyNet_check_success);
|
||||
|
||||
boolean b;
|
||||
b = (response & ISafetyNetHelper.CTS_PASS) != 0;
|
||||
ctsStatusText.setText("ctsProfile: " + b);
|
||||
ctsStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel);
|
||||
ctsStatusIcon.setColorFilter(b ? colorOK : colorBad);
|
||||
|
||||
b = (response & ISafetyNetHelper.BASIC_PASS) != 0;
|
||||
basicStatusText.setText("basicIntegrity: " + b);
|
||||
basicStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel);
|
||||
basicStatusIcon.setColorFilter(b ? colorOK : colorBad);
|
||||
|
||||
expand();
|
||||
} else {
|
||||
@StringRes int resid;
|
||||
switch (response) {
|
||||
case ISafetyNetHelper.RESPONSE_ERR:
|
||||
resid = R.string.safetyNet_res_invalid;
|
||||
break;
|
||||
case ISafetyNetHelper.CONNECTION_FAIL:
|
||||
default:
|
||||
resid = R.string.safetyNet_api_error;
|
||||
break;
|
||||
}
|
||||
safetyNetStatusText.setText(resid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SearchView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber {
|
||||
|
||||
public SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
public RecyclerView recyclerView;
|
||||
SearchView search;
|
||||
|
||||
private ApplicationAdapter appAdapter;
|
||||
|
||||
private SearchView.OnQueryTextListener searchListener;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
|
||||
ViewBinder.bind(this, view);
|
||||
|
||||
appAdapter = new ApplicationAdapter(requireActivity());
|
||||
recyclerView.setAdapter(appAdapter);
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(appAdapter::refresh);
|
||||
|
||||
searchListener = new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
appAdapter.filter(query);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
appAdapter.filter(newText);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
requireActivity().setTitle(R.string.magiskhide);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_magiskhide, menu);
|
||||
search = (SearchView) menu.findItem(R.id.app_search).getActionView();
|
||||
search.setOnQueryTextListener(searchListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
ViewBinder.unbind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.MAGISK_HIDE_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
appAdapter.filter(search.getQuery().toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.Manifest;
|
||||
import android.os.Bundle;
|
||||
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.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class MagiskLogFragment extends BaseFragment {
|
||||
|
||||
public TextView txtLog;
|
||||
public ScrollView svLog;
|
||||
public HorizontalScrollView hsvLog;
|
||||
public ProgressBar progressBar;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_magisk_log, container, false);
|
||||
ViewBinder.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();
|
||||
ViewBinder.unbind(this);
|
||||
}
|
||||
|
||||
@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.su("cat " + Const.MAGISK_LOG + " | tail -n 5000").submit(result -> {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (result.getOut().isEmpty())
|
||||
txtLog.setText(R.string.log_is_empty);
|
||||
else
|
||||
txtLog.setText(TextUtils.join("\n", result.getOut()));
|
||||
svLog.postDelayed(() -> svLog.fullScroll(ScrollView.FOCUS_DOWN), 100);
|
||||
hsvLog.postDelayed(() -> hsvLog.fullScroll(ScrollView.FOCUS_LEFT), 100);
|
||||
});
|
||||
}
|
||||
|
||||
public void saveLogs() {
|
||||
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 logFile = new File(Download.EXTERNAL_PATH, filename);
|
||||
try {
|
||||
logFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
Shell.su("cat " + Const.MAGISK_LOG + " > " + logFile)
|
||||
.submit(result ->
|
||||
SnackbarMaker.make(txtLog, logFile.getPath(), Snackbar.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
public void clearLogs() {
|
||||
Shell.su("echo -n > " + Const.MAGISK_LOG).submit();
|
||||
txtLog.setText(R.string.log_is_empty);
|
||||
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
import com.topjohnwu.magisk.adapters.ModulesAdapter;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
public class ModulesFragment extends BaseFragment implements Topic.Subscriber {
|
||||
|
||||
public SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
public RecyclerView recyclerView;
|
||||
public TextView emptyRv;
|
||||
|
||||
public void selectFile() {
|
||||
runWithPermission(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, () -> {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("application/zip");
|
||||
startActivityForResult(intent, Const.ID.FETCH_ZIP);
|
||||
});
|
||||
}
|
||||
|
||||
private List<Module> listModules = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_modules, container, false);
|
||||
ViewBinder.bind(this, view);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
Utils.loadModules();
|
||||
});
|
||||
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
mSwipeRefreshLayout.setEnabled(recyclerView.getChildAt(0).getTop() >= 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||
super.onScrollStateChanged(recyclerView, newState);
|
||||
}
|
||||
});
|
||||
|
||||
requireActivity().setTitle(R.string.modules);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.MODULE_LOAD_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
updateUI((Map<String, Module>) result[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
|
||||
// Get the URI of the selected file
|
||||
Intent intent = new Intent(getActivity(), Data.classMap.get(FlashActivity.class));
|
||||
intent.setData(data.getData()).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
ViewBinder.unbind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_reboot, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.reboot:
|
||||
Shell.su("/system/bin/reboot").submit();
|
||||
return true;
|
||||
case R.id.reboot_recovery:
|
||||
Shell.su("/system/bin/reboot recovery").submit();
|
||||
return true;
|
||||
case R.id.reboot_bootloader:
|
||||
Shell.su("/system/bin/reboot bootloader").submit();
|
||||
return true;
|
||||
case R.id.reboot_download:
|
||||
Shell.su("/system/bin/reboot download").submit();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUI(Map<String, Module> moduleMap) {
|
||||
listModules.clear();
|
||||
listModules.addAll(moduleMap.values());
|
||||
if (listModules.size() == 0) {
|
||||
emptyRv.setVisibility(View.VISIBLE);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
} else {
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
recyclerView.setAdapter(new ModulesAdapter(listModules));
|
||||
}
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
||||
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
public class ReposFragment extends BaseFragment implements Topic.Subscriber {
|
||||
|
||||
public RecyclerView recyclerView;
|
||||
public TextView emptyRv;
|
||||
public SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
|
||||
private ReposAdapter adapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_repos, container, false);
|
||||
ViewBinder.bind(this, view);
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
new UpdateRepos().exec(true);
|
||||
});
|
||||
|
||||
requireActivity().setTitle(R.string.downloads);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.MODULE_LOAD_DONE, Topic.REPO_LOAD_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
if (topic == Topic.MODULE_LOAD_DONE) {
|
||||
adapter = new ReposAdapter(mm.repoDB, (Map<String, Module>) result[0]);
|
||||
mm.repoDB.registerAdapter(adapter);
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
}
|
||||
if (Topic.isPublished(getSubscribedTopics())) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
recyclerView.setVisibility(adapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
|
||||
emptyRv.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_repo, menu);
|
||||
SearchView search = (SearchView) menu.findItem(R.id.repo_search).getActionView();
|
||||
search.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
adapter.filter(newText);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.repo_sort) {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.sorting_order)
|
||||
.setSingleChoiceItems(R.array.sorting_orders, Data.repoOrder, (d, which) -> {
|
||||
Data.repoOrder = which;
|
||||
mm.prefs.edit().putInt(Const.Key.REPO_ORDER, Data.repoOrder).apply();
|
||||
adapter.notifyDBChanged();
|
||||
d.dismiss();
|
||||
}).show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mm.repoDB.unregisterAdapter();
|
||||
ViewBinder.unbind(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.asyncs.PatchAPK;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceGroupAdapter;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
import androidx.preference.SwitchPreference;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragmentCompat
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
Topic.Subscriber, Topic.AutoSubscriber {
|
||||
|
||||
private PreferenceScreen prefScreen;
|
||||
|
||||
private ListPreference updateChannel, suAccess, autoRes, suNotification,
|
||||
requestTimeout, multiuserMode, namespaceMode;
|
||||
private MagiskManager mm;
|
||||
private PreferenceCategory generalCatagory;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.app_settings, rootKey);
|
||||
mm = Data.MM();
|
||||
prefScreen = getPreferenceScreen();
|
||||
|
||||
generalCatagory = (PreferenceCategory) findPreference("general");
|
||||
PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
|
||||
PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
|
||||
Preference hideManager = findPreference("hide");
|
||||
Preference restoreManager = findPreference("restore");
|
||||
findPreference("clear").setOnPreferenceClickListener((pref) -> {
|
||||
mm.prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
||||
mm.repoDB.clearRepo();
|
||||
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
|
||||
return true;
|
||||
});
|
||||
|
||||
updateChannel = (ListPreference) findPreference(Const.Key.UPDATE_CHANNEL);
|
||||
suAccess = (ListPreference) findPreference(Const.Key.ROOT_ACCESS);
|
||||
autoRes = (ListPreference) findPreference(Const.Key.SU_AUTO_RESPONSE);
|
||||
requestTimeout = (ListPreference) findPreference(Const.Key.SU_REQUEST_TIMEOUT);
|
||||
suNotification = (ListPreference) findPreference(Const.Key.SU_NOTIFICATION);
|
||||
multiuserMode = (ListPreference) findPreference(Const.Key.SU_MULTIUSER_MODE);
|
||||
namespaceMode = (ListPreference) findPreference(Const.Key.SU_MNT_NS);
|
||||
SwitchPreference reauth = (SwitchPreference) findPreference(Const.Key.SU_REAUTH);
|
||||
SwitchPreference fingerprint = (SwitchPreference) findPreference(Const.Key.SU_FINGERPRINT);
|
||||
|
||||
updateChannel.setOnPreferenceChangeListener((p, o) -> {
|
||||
String prev =String.valueOf(Data.updateChannel);
|
||||
int channel = Integer.parseInt((String) o);
|
||||
if (channel == Const.Value.CUSTOM_CHANNEL) {
|
||||
View v = LayoutInflater.from(requireActivity()).inflate(R.layout.custom_channel_dialog, null);
|
||||
EditText url = v.findViewById(R.id.custom_url);
|
||||
url.setText(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||
new AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.settings_update_custom)
|
||||
.setView(v)
|
||||
.setPositiveButton(R.string.ok, (d, i) ->
|
||||
mm.prefs.edit().putString(Const.Key.CUSTOM_CHANNEL,
|
||||
url.getText().toString()).apply())
|
||||
.setNegativeButton(R.string.close, (d, i) ->
|
||||
mm.prefs.edit().putString(Const.Key.UPDATE_CHANNEL, prev).apply())
|
||||
.setOnCancelListener(d ->
|
||||
mm.prefs.edit().putString(Const.Key.UPDATE_CHANNEL, prev).apply())
|
||||
.show();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
setSummary();
|
||||
|
||||
// Disable dangerous settings in secondary user
|
||||
if (Const.USER_ID > 0) {
|
||||
suCategory.removePreference(multiuserMode);
|
||||
}
|
||||
|
||||
// Disable re-authentication option on Android O, it will not work
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
reauth.setEnabled(false);
|
||||
reauth.setChecked(false);
|
||||
reauth.setSummary(R.string.android_o_not_support);
|
||||
}
|
||||
|
||||
// Disable fingerprint option if not possible
|
||||
if (!FingerprintHelper.canUseFingerprint()) {
|
||||
fingerprint.setEnabled(false);
|
||||
fingerprint.setChecked(false);
|
||||
fingerprint.setSummary(R.string.disable_fingerprint);
|
||||
}
|
||||
|
||||
if (Data.magiskVersionCode >= Const.MAGISK_VER.MANAGER_HIDE) {
|
||||
if (mm.getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||
PatchAPK.hideManager(requireActivity());
|
||||
return true;
|
||||
});
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
} else {
|
||||
if (Download.checkNetworkStatus(mm)) {
|
||||
restoreManager.setOnPreferenceClickListener((pref) -> {
|
||||
Download.receive(
|
||||
requireActivity(), new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
Data.exportPrefs();
|
||||
Shell.su("cp " + uri.getPath() + " /data/local/tmp/manager.apk").exec();
|
||||
if (ShellUtils.fastCmdResult("pm install /data/local/tmp/manager.apk")) {
|
||||
Shell.su("rm -f /data/local/tmp/manager.apk").exec();
|
||||
RootUtils.uninstallPkg(context.getPackageName());
|
||||
return;
|
||||
}
|
||||
Shell.su("rm -f /data/local/tmp/manager.apk").exec();
|
||||
}
|
||||
},
|
||||
Data.managerLink,
|
||||
Utils.fmt("MagiskManager-v%s.apk", Data.remoteManagerVersionString)
|
||||
);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
}
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
|
||||
if (!Shell.rootAccess() || (Const.USER_ID > 0 &&
|
||||
Data.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
prefScreen.removePreference(suCategory);
|
||||
}
|
||||
|
||||
if (!Shell.rootAccess()) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
} else if (Data.magiskVersionCode < Const.MAGISK_VER.UNIFIED) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
}
|
||||
}
|
||||
|
||||
private void setLocalePreference(ListPreference lp) {
|
||||
CharSequence[] entries = new CharSequence[LocaleManager.locales.size() + 1];
|
||||
CharSequence[] entryValues = new CharSequence[LocaleManager.locales.size() + 1];
|
||||
entries[0] = LocaleManager.getString(LocaleManager.defaultLocale, R.string.system_default);
|
||||
entryValues[0] = "";
|
||||
int i = 1;
|
||||
for (Locale locale : LocaleManager.locales) {
|
||||
entries[i] = locale.getDisplayName(locale);
|
||||
entryValues[i++] = locale.toLanguageTag();
|
||||
}
|
||||
lp.setEntries(entries);
|
||||
lp.setEntryValues(entryValues);
|
||||
lp.setSummary(LocaleManager.locale.getDisplayName(LocaleManager.locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
mm.prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
Topic.subscribe(this);
|
||||
requireActivity().setTitle(R.string.settings);
|
||||
return super.onCreateView(inflater, container, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
mm.prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
Topic.unsubscribe(this);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
switch (key) {
|
||||
case Const.Key.ROOT_ACCESS:
|
||||
case Const.Key.SU_MULTIUSER_MODE:
|
||||
case Const.Key.SU_MNT_NS:
|
||||
mm.mDB.setSettings(key, Utils.getPrefsInt(prefs, key));
|
||||
break;
|
||||
}
|
||||
Data.loadConfig();
|
||||
setSummary();
|
||||
switch (key) {
|
||||
case Const.Key.DARK_THEME:
|
||||
requireActivity().recreate();
|
||||
break;
|
||||
case Const.Key.COREONLY:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
try {
|
||||
Const.MAGISK_DISABLE_FILE.createNewFile();
|
||||
} catch (IOException ignored) {}
|
||||
} else {
|
||||
Const.MAGISK_DISABLE_FILE.delete();
|
||||
}
|
||||
Utils.toast(R.string.settings_reboot_toast, Toast.LENGTH_LONG);
|
||||
break;
|
||||
case Const.Key.MAGISKHIDE:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.su("magiskhide --enable").submit();
|
||||
} else {
|
||||
Shell.su("magiskhide --disable").submit();
|
||||
}
|
||||
break;
|
||||
case Const.Key.HOSTS:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.su("cp -af /system/etc/hosts " + Const.MAGISK_HOST_FILE,
|
||||
"mount -o bind " + Const.MAGISK_HOST_FILE + " /system/etc/hosts")
|
||||
.submit();
|
||||
} else {
|
||||
Shell.su("umount -l /system/etc/hosts",
|
||||
"rm -f " + Const.MAGISK_HOST_FILE)
|
||||
.submit();
|
||||
}
|
||||
break;
|
||||
case Const.Key.LOCALE:
|
||||
LocaleManager.setLocale(mm);
|
||||
requireActivity().recreate();
|
||||
break;
|
||||
case Const.Key.UPDATE_CHANNEL:
|
||||
case Const.Key.CUSTOM_CHANNEL:
|
||||
CheckUpdates.check();
|
||||
break;
|
||||
case Const.Key.CHECK_UPDATES:
|
||||
Utils.setupUpdateCheck();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(Preference preference) {
|
||||
String key = preference.getKey();
|
||||
switch (key) {
|
||||
case Const.Key.SU_FINGERPRINT:
|
||||
boolean checked = ((SwitchPreference) preference).isChecked();
|
||||
((SwitchPreference) preference).setChecked(!checked);
|
||||
CustomAlertDialog dialog = new CustomAlertDialog(requireActivity());
|
||||
CustomAlertDialog.ViewHolder vh = dialog.getViewHolder();
|
||||
Drawable fingerprint = getResources().getDrawable(R.drawable.ic_fingerprint);
|
||||
fingerprint.setBounds(0, 0, Utils.dpInPx(50), Utils.dpInPx(50));
|
||||
Resources.Theme theme = requireActivity().getTheme();
|
||||
TypedArray ta = theme.obtainStyledAttributes(new int[] {R.attr.imageColorTint});
|
||||
fingerprint.setTint(ta.getColor(0, Color.GRAY));
|
||||
ta.recycle();
|
||||
vh.messageView.setCompoundDrawables(null, null, null, fingerprint);
|
||||
vh.messageView.setCompoundDrawablePadding(Utils.dpInPx(20));
|
||||
vh.messageView.setGravity(Gravity.CENTER);
|
||||
try {
|
||||
FingerprintHelper helper = new FingerprintHelper() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(R.string.auth_fail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
dialog.dismiss();
|
||||
((SwitchPreference) preference).setChecked(checked);
|
||||
mm.mDB.setSettings(key, checked ? 1 : 0);
|
||||
|
||||
}
|
||||
};
|
||||
dialog.setMessage(R.string.auth_fingerprint)
|
||||
.setNegativeButton(R.string.close, (d, w) -> helper.cancel())
|
||||
.setOnCancelListener(d -> helper.cancel())
|
||||
.show();
|
||||
helper.authenticate();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setSummary() {
|
||||
updateChannel.setSummary(getResources()
|
||||
.getStringArray(R.array.update_channel)[Data.updateChannel]);
|
||||
suAccess.setSummary(getResources()
|
||||
.getStringArray(R.array.su_access)[Data.suAccessState]);
|
||||
autoRes.setSummary(getResources()
|
||||
.getStringArray(R.array.auto_response)[Data.suResponseType]);
|
||||
suNotification.setSummary(getResources()
|
||||
.getStringArray(R.array.su_notification)[Data.suNotificationType]);
|
||||
requestTimeout.setSummary(
|
||||
getString(R.string.request_timeout_summary,
|
||||
mm.prefs.getString(Const.Key.SU_REQUEST_TIMEOUT, "10")));
|
||||
multiuserMode.setSummary(getResources()
|
||||
.getStringArray(R.array.multiuser_summary)[Data.multiuserMode]);
|
||||
namespaceMode.setSummary(getResources()
|
||||
.getStringArray(R.array.namespace_summary)[Data.suNamespaceMode]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
setLocalePreference((ListPreference) findPreference(Const.Key.LOCALE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.LOCALE_FETCH_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
|
||||
return new PreferenceGroupAdapter(preferenceScreen) {
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder, int position) {
|
||||
super.onBindViewHolder(holder, position);
|
||||
Preference preference = getItem(position);
|
||||
if (preference instanceof PreferenceCategory)
|
||||
setZeroPaddingToLayoutChildren(holder.itemView);
|
||||
else {
|
||||
View iconFrame = holder.itemView.findViewById(R.id.icon_frame);
|
||||
if (iconFrame != null) {
|
||||
iconFrame.setVisibility(preference.getIcon() == null ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void setZeroPaddingToLayoutChildren(View view) {
|
||||
if (!(view instanceof ViewGroup))
|
||||
return;
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
int childCount = viewGroup.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
setZeroPaddingToLayoutChildren(viewGroup.getChildAt(i));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
|
||||
viewGroup.setPaddingRelative(0, viewGroup.getPaddingTop(), viewGroup.getPaddingEnd(), viewGroup.getPaddingBottom());
|
||||
else
|
||||
viewGroup.setPadding(0, viewGroup.getPaddingTop(), viewGroup.getPaddingRight(), viewGroup.getPaddingBottom());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
import com.topjohnwu.magisk.adapters.SuLogAdapter;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class SuLogFragment extends BaseFragment {
|
||||
|
||||
public TextView emptyRv;
|
||||
public RecyclerView recyclerView;
|
||||
|
||||
private SuLogAdapter adapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_log, menu);
|
||||
menu.findItem(R.id.menu_save).setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
View v = inflater.inflate(R.layout.fragment_su_log, container, false);
|
||||
ViewBinder.bind(this, v);
|
||||
adapter = new SuLogAdapter(mm.mDB);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
updateList();
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
private void updateList() {
|
||||
adapter.notifyDBChanged();
|
||||
|
||||
if (adapter.getSectionCount() == 0) {
|
||||
emptyRv.setVisibility(View.VISIBLE);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
} else {
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_refresh:
|
||||
updateList();
|
||||
return true;
|
||||
case R.id.menu_clear:
|
||||
mm.mDB.clearLogs();
|
||||
updateList();
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
ViewBinder.unbind(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.ViewBinder;
|
||||
import com.topjohnwu.magisk.adapters.PolicyAdapter;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class SuperuserFragment extends BaseFragment {
|
||||
|
||||
public RecyclerView recyclerView;
|
||||
public TextView emptyRv;
|
||||
|
||||
private PackageManager pm;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_superuser, container, false);
|
||||
ViewBinder.bind(this, view);
|
||||
|
||||
pm = getActivity().getPackageManager();
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
requireActivity().setTitle(getString(R.string.superuser));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
displayPolicyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
ViewBinder.unbind(this);
|
||||
}
|
||||
|
||||
private void displayPolicyList() {
|
||||
List<Policy> policyList = mm.mDB.getPolicyList(pm);
|
||||
|
||||
if (policyList.size() == 0) {
|
||||
emptyRv.setVisibility(View.VISIBLE);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
} else {
|
||||
recyclerView.setAdapter(new PolicyAdapter(policyList, mm.mDB, pm));
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.topjohnwu.magisk.receivers;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.SuRequestActivity;
|
||||
import com.topjohnwu.magisk.services.OnBootService;
|
||||
import com.topjohnwu.magisk.utils.SuConnector;
|
||||
|
||||
public class BootReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (TextUtils.equals(intent.getAction(), Intent.ACTION_BOOT_COMPLETED)) {
|
||||
switch (intent.getExtras().getString("action", "boot")) {
|
||||
case "request":
|
||||
Intent i = new Intent(context, Data.classMap.get(SuRequestActivity.class))
|
||||
.putExtra("socket", intent.getStringExtra("socket"))
|
||||
.putExtra("version", 2)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(i);
|
||||
break;
|
||||
case "log":
|
||||
SuConnector.handleLogs(intent, 2);
|
||||
break;
|
||||
case "boot":
|
||||
OnBootService.enqueueWork(context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
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.Const;
|
||||
import com.topjohnwu.magisk.asyncs.PatchAPK;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.utils.JarMap;
|
||||
import com.topjohnwu.utils.SignAPK;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
public class ManagerUpdate extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Download.receive(
|
||||
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 orig = uri.getPath();
|
||||
String patch = orig.substring(0, orig.lastIndexOf('.')) + "-patched.apk";
|
||||
try {
|
||||
JarMap apk = new JarMap(orig);
|
||||
PatchAPK.patchPackageID(apk, Const.ORIG_PKG_NAME, context.getPackageName());
|
||||
SignAPK.sign(apk, new BufferedOutputStream(new FileOutputStream(patch)));
|
||||
super.onDownloadDone(context, Uri.fromFile(new File(patch)));
|
||||
} catch (Exception ignored) { }
|
||||
});
|
||||
} else {
|
||||
super.onDownloadDone(context, uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.topjohnwu.magisk.receivers;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class PackageReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
String pkg = intent.getData().getEncodedSchemeSpecificPart();
|
||||
|
||||
switch (intent.getAction()) {
|
||||
case Intent.ACTION_PACKAGE_REPLACED:
|
||||
// This will only work pre-O
|
||||
if (mm.prefs.getBoolean(Const.Key.SU_REAUTH, false)) {
|
||||
mm.mDB.deletePolicy(pkg);
|
||||
}
|
||||
break;
|
||||
case Intent.ACTION_PACKAGE_FULLY_REMOVED:
|
||||
mm.mDB.deletePolicy(pkg);
|
||||
Shell.su("magiskhide --rm " + pkg).submit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.topjohnwu.magisk.receivers;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class RebootReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Shell.su("/system/bin/reboot").submit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
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 com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
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 = Data.MM();
|
||||
ShortcutManager manager = context.getSystemService(ShortcutManager.class);
|
||||
manager.setDynamicShortcuts(getShortCuts(mm));
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
|
||||
private ArrayList<ShortcutInfo> getShortCuts(MagiskManager mm) {
|
||||
ArrayList<ShortcutInfo> shortCuts = new ArrayList<>();
|
||||
boolean root = Shell.rootAccess();
|
||||
if (root && !(Const.USER_ID > 0 &&
|
||||
Data.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(mm, "superuser")
|
||||
.setShortLabel(mm.getString(R.string.superuser))
|
||||
.setIntent(new Intent(mm, Data.classMap.get(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 (root && Data.magiskVersionCode >= Const.MAGISK_VER.UNIFIED
|
||||
&& mm.prefs.getBoolean(Const.Key.MAGISKHIDE, false)) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(mm, "magiskhide")
|
||||
.setShortLabel(mm.getString(R.string.magiskhide))
|
||||
.setIntent(new Intent(mm, Data.classMap.get(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) && root && Data.magiskVersionCode >= 0) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(mm, "modules")
|
||||
.setShortLabel(mm.getString(R.string.modules))
|
||||
.setIntent(new Intent(mm, Data.classMap.get(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, Data.classMap.get(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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.topjohnwu.magisk.services;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.utils.NotificationMgr;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.JobIntentService;
|
||||
|
||||
public class OnBootService extends JobIntentService {
|
||||
|
||||
public static void enqueueWork(Context context) {
|
||||
enqueueWork(context, Data.classMap.get(OnBootService.class), Const.ID.ONBOOT_SERVICE_ID, new Intent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWork(@NonNull Intent intent) {
|
||||
/* Devices with DTBO might want to patch dtbo.img.
|
||||
* However, that is not possible if Magisk is installed by
|
||||
* patching boot image with Magisk Manager and flashed via
|
||||
* fastboot, since at that time we do not have root.
|
||||
* Check for dtbo status every boot time, and prompt user
|
||||
* to reboot if dtbo wasn't patched and patched by Magisk Manager.
|
||||
* */
|
||||
if (Shell.rootAccess() && ShellUtils.fastCmdResult("mm_patch_dtbo"))
|
||||
NotificationMgr.dtboPatched();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.topjohnwu.magisk.services;
|
||||
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobService;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class UpdateCheckService extends JobService {
|
||||
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters params) {
|
||||
Shell.getShell();
|
||||
CheckUpdates.check(() -> jobFinished(params, false));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters params) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.topjohnwu.magisk.superuser;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.SuRequestActivity;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
|
||||
public class RequestActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Intent intent = new Intent(this, Data.classMap.get(SuRequestActivity.class))
|
||||
.putExtra("socket", getIntent().getStringExtra("socket"))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.topjohnwu.magisk.superuser;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.utils.SuConnector;
|
||||
|
||||
public class SuReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
SuConnector.handleLogs(intent, 1);
|
||||
}
|
||||
}
|
||||
47
app/src/full/java/com/topjohnwu/magisk/utils/BootSigner.java
Normal file
47
app/src/full/java/com/topjohnwu/magisk/utils/BootSigner.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import com.topjohnwu.utils.SignBoot;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
public class BootSigner {
|
||||
|
||||
@Keep
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length > 0 && "-verify".equals(args[0])) {
|
||||
String certPath = "";
|
||||
if (args.length >= 2) {
|
||||
/* args[1] is the path to a public key certificate */
|
||||
certPath = args[1];
|
||||
}
|
||||
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 cert = null;
|
||||
InputStream key = null;
|
||||
|
||||
if (args.length >= 3) {
|
||||
cert = new FileInputStream(args[1]);
|
||||
key = new FileInputStream(args[2]);
|
||||
}
|
||||
|
||||
boolean success = SignBoot.doSignature("/boot", System.in, System.out, cert, key);
|
||||
System.exit(success ? 0 : 1);
|
||||
} else {
|
||||
System.err.println(
|
||||
"BootSigner <actions> [args]\n" +
|
||||
"Input from stdin, outputs to stdout\n" +
|
||||
"\n" +
|
||||
"Actions:\n" +
|
||||
" -verify [x509.pem]\n" +
|
||||
" verify image, cert is optional\n" +
|
||||
" -sign [x509.pem] [pk8]\n" +
|
||||
" sign image, cert and key pair is optional\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.KeyguardManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||
import android.security.keystore.KeyProperties;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
import java.security.KeyStore;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public abstract class FingerprintHelper {
|
||||
|
||||
private FingerprintManager manager;
|
||||
private Cipher cipher;
|
||||
private CancellationSignal cancel;
|
||||
|
||||
public static boolean canUseFingerprint() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
return false;
|
||||
MagiskManager mm = Data.MM();
|
||||
KeyguardManager km = mm.getSystemService(KeyguardManager.class);
|
||||
FingerprintManager fm = mm.getSystemService(FingerprintManager.class);
|
||||
return km.isKeyguardSecure() && fm != null && fm.isHardwareDetected() && fm.hasEnrolledFingerprints();
|
||||
}
|
||||
|
||||
protected FingerprintHelper() throws Exception {
|
||||
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
manager = Data.MM().getSystemService(FingerprintManager.class);
|
||||
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
||||
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
||||
keyStore.load(null);
|
||||
SecretKey key = (SecretKey) keyStore.getKey(Const.SU_KEYSTORE_KEY, null);
|
||||
if (key == null) {
|
||||
key = generateKey();
|
||||
}
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
} catch (KeyPermanentlyInvalidatedException e) {
|
||||
// Only happens on Marshmallow
|
||||
key = generateKey();
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void onAuthenticationError(int errorCode, CharSequence errString);
|
||||
|
||||
public abstract void onAuthenticationHelp(int helpCode, CharSequence helpString);
|
||||
|
||||
public abstract void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result);
|
||||
|
||||
public abstract void onAuthenticationFailed();
|
||||
|
||||
public void authenticate() {
|
||||
cancel = new CancellationSignal();
|
||||
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
|
||||
manager.authenticate(cryptoObject, cancel, 0, new Callback(), null);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if (cancel != null)
|
||||
cancel.cancel();
|
||||
}
|
||||
|
||||
private SecretKey generateKey() throws Exception {
|
||||
KeyGenerator keygen = KeyGenerator
|
||||
.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
|
||||
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
|
||||
Const.SU_KEYSTORE_KEY,
|
||||
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
||||
.setUserAuthenticationRequired(true)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
builder.setInvalidatedByBiometricEnrollment(false);
|
||||
}
|
||||
keygen.init(builder.build());
|
||||
return keygen.generateKey();
|
||||
}
|
||||
|
||||
private class Callback extends FingerprintManager.AuthenticationCallback {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
FingerprintHelper.this.onAuthenticationError(errorCode, errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
FingerprintHelper.this.onAuthenticationHelp(helpCode, helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
FingerprintHelper.this.onAuthenticationSucceeded(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
FingerprintHelper.this.onAuthenticationFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
public interface ISafetyNetHelper {
|
||||
|
||||
int RESPONSE_ERR = 0x01;
|
||||
int CONNECTION_FAIL = 0x02;
|
||||
|
||||
int BASIC_PASS = 0x10;
|
||||
int CTS_PASS = 0x20;
|
||||
|
||||
void attest();
|
||||
|
||||
int getVersion();
|
||||
|
||||
interface Callback {
|
||||
void onResponse(int responseCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
public class LocaleManager {
|
||||
public static Locale locale = Locale.getDefault();
|
||||
public final static Locale defaultLocale = Locale.getDefault();
|
||||
public static List<Locale> locales;
|
||||
|
||||
public static void setLocale(MagiskManager mm) {
|
||||
String localeConfig = mm.prefs.getString(Const.Key.LOCALE, "");
|
||||
if (localeConfig.isEmpty()) {
|
||||
locale = defaultLocale;
|
||||
} else {
|
||||
locale = Locale.forLanguageTag(localeConfig);
|
||||
}
|
||||
Locale.setDefault(locale);
|
||||
Resources res = mm.getResources();
|
||||
Configuration config = res.getConfiguration();
|
||||
config.setLocale(locale);
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
}
|
||||
|
||||
public static String getString(Locale locale, @StringRes int id) {
|
||||
Configuration config = new Configuration();
|
||||
config.setLocale(locale);
|
||||
return Data.MM().createConfigurationContext(config).getString(id);
|
||||
}
|
||||
|
||||
public static void loadAvailableLocales() {
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
locales = new ArrayList<>();
|
||||
HashSet<String> set = new HashSet<>();
|
||||
Resources res = Data.MM().getResources();
|
||||
Locale locale;
|
||||
|
||||
@StringRes int compareId = R.string.download_file_error;
|
||||
|
||||
// Add default locale
|
||||
locales.add(Locale.ENGLISH);
|
||||
set.add(getString(Locale.ENGLISH, compareId));
|
||||
|
||||
// Add some special locales
|
||||
locales.add(Locale.TAIWAN);
|
||||
set.add(getString(Locale.TAIWAN, compareId));
|
||||
locale = new Locale("pt", "BR");
|
||||
locales.add(locale);
|
||||
set.add(getString(locale, compareId));
|
||||
|
||||
// Other locales
|
||||
for (String s : res.getAssets().getLocales()) {
|
||||
locale = Locale.forLanguageTag(s);
|
||||
if (set.add(getString(locale, compareId))) {
|
||||
locales.add(locale);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(locales, (a, b) -> a.getDisplayName(a).compareTo(b.getDisplayName(b)));
|
||||
Topic.publish(Topic.LOCALE_FETCH_DONE);
|
||||
});
|
||||
}
|
||||
}
|
||||
26
app/src/full/java/com/topjohnwu/magisk/utils/Logger.java
Normal file
26
app/src/full/java/com/topjohnwu/magisk/utils/Logger.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
import com.topjohnwu.magisk.receivers.RebootReceiver;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.TaskStackBuilder;
|
||||
|
||||
public class NotificationMgr {
|
||||
|
||||
public static void magiskUpdate() {
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
Intent intent = new Intent(mm, Data.classMap.get(SplashActivity.class));
|
||||
intent.putExtra(Const.Key.OPEN_SECTION, "magisk");
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(mm);
|
||||
stackBuilder.addParentStack(Data.classMap.get(SplashActivity.class));
|
||||
stackBuilder.addNextIntent(intent);
|
||||
PendingIntent pendingIntent = stackBuilder.getPendingIntent(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle(mm.getString(R.string.magisk_update_title))
|
||||
.setContentText(mm.getString(R.string.magisk_update_available, Data.remoteMagiskVersionString))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void managerUpdate() {
|
||||
MagiskManager mm = Data.MM();
|
||||
String filename = Utils.fmt("MagiskManager-v%s(%d).apk",
|
||||
Data.remoteManagerVersionString, Data.remoteManagerVersionCode);
|
||||
|
||||
Intent intent = new Intent(mm, Data.classMap.get(ManagerUpdate.class));
|
||||
intent.putExtra(Const.Key.INTENT_SET_LINK, Data.managerLink);
|
||||
intent.putExtra(Const.Key.INTENT_SET_FILENAME, filename);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
|
||||
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle(mm.getString(R.string.manager_update_title))
|
||||
.setContentText(mm.getString(R.string.manager_download_install))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.APK_UPDATE_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void dtboPatched() {
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
Intent intent = new Intent(mm, Data.classMap.get(RebootReceiver.class));
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
|
||||
Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle(mm.getString(R.string.dtbo_patched_title))
|
||||
.setContentText(mm.getString(R.string.dtbo_patched_reboot))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.addAction(R.drawable.ic_refresh, mm.getString(R.string.reboot), pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.DTBO_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
}
|
||||
59
app/src/full/java/com/topjohnwu/magisk/utils/RootUtils.java
Normal file
59
app/src/full/java/com/topjohnwu/magisk/utils/RootUtils.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.superuser.BusyBox;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class RootUtils extends Shell.Initializer {
|
||||
|
||||
static {
|
||||
BusyBox.BB_PATH = new File(Const.BUSYBOX_PATH);
|
||||
}
|
||||
|
||||
public static void uninstallPkg(String pkg) {
|
||||
Shell.su("db_clean " + Const.USER_ID, "pm uninstall " + pkg).exec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInit(Context context, @NonNull Shell shell) {
|
||||
Shell.Job job = shell.newJob();
|
||||
if (shell.isRoot()) {
|
||||
InputStream magiskUtils = context.getResources().openRawResource(R.raw.util_functions);
|
||||
InputStream managerUtils = context.getResources().openRawResource(R.raw.utils);
|
||||
job.add(magiskUtils).add(managerUtils);
|
||||
|
||||
Const.MAGISK_DISABLE_FILE = new SuFile("/cache/.disable_magisk");
|
||||
SuFile file = new SuFile("/sbin/.core/img");
|
||||
if (file.exists()) {
|
||||
Const.MAGISK_PATH = file;
|
||||
} else if ((file = new SuFile("/dev/magisk/img")).exists()) {
|
||||
Const.MAGISK_PATH = file;
|
||||
} else {
|
||||
Const.MAGISK_PATH = new SuFile("/magisk");
|
||||
}
|
||||
Const.MAGISK_HOST_FILE = new SuFile(Const.MAGISK_PATH + "/.core/hosts");
|
||||
|
||||
Data.loadMagiskInfo();
|
||||
} else {
|
||||
InputStream nonroot = context.getResources().openRawResource(R.raw.nonroot_utils);
|
||||
job.add(nonroot);
|
||||
}
|
||||
|
||||
job.add("mount_partitions", "get_flags", "run_migrations", "export BOOTMODE=true").exec();
|
||||
|
||||
Data.keepVerity = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPVERITY"));
|
||||
Data.keepEnc = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPFORCEENCRYPT"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
118
app/src/full/java/com/topjohnwu/magisk/utils/SuConnector.java
Normal file
118
app/src/full/java/com/topjohnwu/magisk/utils/SuConnector.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.LocalSocket;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
public abstract class SuConnector {
|
||||
|
||||
protected LocalSocket socket = new LocalSocket();
|
||||
|
||||
private String readString(DataInputStream is) throws IOException {
|
||||
int len = is.readInt();
|
||||
byte[] buf = new byte[len];
|
||||
is.readFully(buf);
|
||||
return new String(buf);
|
||||
}
|
||||
|
||||
public Bundle readSocketInput() throws IOException {
|
||||
Bundle bundle = new Bundle();
|
||||
DataInputStream is = new DataInputStream(socket.getInputStream());
|
||||
while (true) {
|
||||
String name = readString(is);
|
||||
if (TextUtils.equals(name, "eof"))
|
||||
break;
|
||||
bundle.putString(name, readString(is));
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
protected DataOutputStream getOutputStream() throws IOException {
|
||||
return new DataOutputStream(socket.getOutputStream());
|
||||
}
|
||||
|
||||
public abstract void response();
|
||||
|
||||
public static void handleLogs(Intent intent, int version) {
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
if (intent == null) return;
|
||||
|
||||
int fromUid = intent.getIntExtra("from.uid", -1);
|
||||
if (fromUid < 0) return;
|
||||
if (fromUid == Process.myUid()) return;
|
||||
|
||||
Policy policy = mm.mDB.getPolicy(fromUid);
|
||||
if (policy == null) {
|
||||
try {
|
||||
policy = new Policy(fromUid, mm.getPackageManager());
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SuLogEntry log = new SuLogEntry(policy);
|
||||
if (version == 1) {
|
||||
String action = intent.getStringExtra("action");
|
||||
if (action == null) return;
|
||||
switch (action) {
|
||||
case "allow":
|
||||
log.action = true;
|
||||
break;
|
||||
case "deny":
|
||||
log.action = false;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
switch (intent.getIntExtra("policy", -1)) {
|
||||
case Policy.ALLOW:
|
||||
log.action = true;
|
||||
break;
|
||||
case Policy.DENY:
|
||||
log.action = false;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String message = mm.getString(log.action ?
|
||||
R.string.su_allow_toast : R.string.su_deny_toast, policy.appName);
|
||||
|
||||
if (policy.notification && Data.suNotificationType == Const.Value.NOTIFICATION_TOAST)
|
||||
Utils.toast(message, Toast.LENGTH_SHORT);
|
||||
|
||||
if (policy.logging) {
|
||||
int toUid = intent.getIntExtra("to.uid", -1);
|
||||
if (toUid < 0) return;
|
||||
int pid = intent.getIntExtra("pid", -1);
|
||||
if (pid < 0) return;
|
||||
String command = intent.getStringExtra("command");
|
||||
if (command == null) return;
|
||||
log.toUid = toUid;
|
||||
log.fromPid = pid;
|
||||
log.command = command;
|
||||
log.date = new Date();
|
||||
mm.mDB.addLog(log);
|
||||
}
|
||||
}
|
||||
}
|
||||
107
app/src/full/java/com/topjohnwu/magisk/utils/Topic.java
Normal file
107
app/src/full/java/com/topjohnwu/magisk/utils/Topic.java
Normal file
@@ -0,0 +1,107 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
public class Topic {
|
||||
|
||||
public static final int MAGISK_HIDE_DONE = 0;
|
||||
public static final int MODULE_LOAD_DONE = 1;
|
||||
public static final int REPO_LOAD_DONE = 2;
|
||||
public static final int UPDATE_CHECK_DONE = 3;
|
||||
public static final int SNET_CHECK_DONE = 4;
|
||||
public static final int LOCALE_FETCH_DONE = 5;
|
||||
|
||||
@IntDef({MAGISK_HIDE_DONE, MODULE_LOAD_DONE, REPO_LOAD_DONE,
|
||||
UPDATE_CHECK_DONE, SNET_CHECK_DONE, LOCALE_FETCH_DONE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface TopicID {}
|
||||
|
||||
// We will not dynamically add topics, so use arrays instead of hash tables
|
||||
private static Store[] topicList = new Store[6];
|
||||
|
||||
public static void subscribe(Subscriber sub, @TopicID int... topics) {
|
||||
for (int topic : topics) {
|
||||
if (topicList[topic] == null)
|
||||
topicList[topic] = new Store();
|
||||
topicList[topic].subscribers.add(sub);
|
||||
if (topicList[topic].published) {
|
||||
sub.onPublish(topic, topicList[topic].results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void subscribe(AutoSubscriber sub) {
|
||||
if (sub instanceof Subscriber)
|
||||
subscribe((Subscriber) sub, sub.getSubscribedTopics());
|
||||
}
|
||||
|
||||
public static void unsubscribe(Subscriber sub, @TopicID int... topics) {
|
||||
for (int topic : topics) {
|
||||
if (topicList[topic] == null)
|
||||
continue;
|
||||
topicList[topic].subscribers.remove(sub);
|
||||
}
|
||||
}
|
||||
|
||||
public static void unsubscribe(AutoSubscriber sub) {
|
||||
if (sub instanceof Subscriber)
|
||||
unsubscribe((Subscriber) sub, sub.getSubscribedTopics());
|
||||
}
|
||||
|
||||
public static void publish(@TopicID int topic, Object... results) {
|
||||
publish(true, topic, results);
|
||||
}
|
||||
|
||||
public static void publish(boolean persist, @TopicID int topic, Object... results) {
|
||||
if (topicList[topic] == null)
|
||||
topicList[topic] = new Store();
|
||||
if (persist) {
|
||||
topicList[topic].results = results;
|
||||
topicList[topic].published = true;
|
||||
}
|
||||
for (Subscriber sub : topicList[topic].subscribers) {
|
||||
Data.mainHandler.post(() -> sub.onPublish(topic, results));
|
||||
}
|
||||
}
|
||||
|
||||
public static void reset(@TopicID int... topics) {
|
||||
for (int topic : topics) {
|
||||
if (topicList[topic] == null)
|
||||
continue;
|
||||
topicList[topic].published = false;
|
||||
topicList[topic].results = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPublished(@TopicID int... topics) {
|
||||
for (int topic : topics) {
|
||||
if (topicList[topic] == null)
|
||||
return false;
|
||||
if (!topicList[topic].published)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class Store {
|
||||
boolean published = false;
|
||||
Set<Subscriber> subscribers = new HashSet<>();
|
||||
Object[] results;
|
||||
}
|
||||
|
||||
public interface Subscriber {
|
||||
void onPublish(int topic, Object[] result);
|
||||
}
|
||||
|
||||
public interface AutoSubscriber {
|
||||
@TopicID
|
||||
int[] getSubscribedTopics();
|
||||
}
|
||||
}
|
||||
126
app/src/full/java/com/topjohnwu/magisk/utils/Utils.java
Normal file
126
app/src/full/java/com/topjohnwu/magisk/utils/Utils.java
Normal file
@@ -0,0 +1,126 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.container.ValueSortedMap;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import a.n;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static int getPrefsInt(SharedPreferences prefs, String key, int def) {
|
||||
return Integer.parseInt(prefs.getString(key, String.valueOf(def)));
|
||||
}
|
||||
|
||||
public static int getPrefsInt(SharedPreferences prefs, String key) {
|
||||
return getPrefsInt(prefs, key, 0);
|
||||
}
|
||||
|
||||
public static String getNameFromUri(Context context, Uri uri) {
|
||||
String name = null;
|
||||
try (Cursor c = context.getContentResolver().query(uri, null, null, null, null)) {
|
||||
if (c != null) {
|
||||
int nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||
if (nameIndex != -1) {
|
||||
c.moveToFirst();
|
||||
name = c.getString(nameIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (name == null) {
|
||||
int idx = uri.getPath().lastIndexOf('/');
|
||||
name = uri.getPath().substring(idx + 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public static int dpInPx(int dp) {
|
||||
float scale = Data.MM().getResources().getDisplayMetrics().density;
|
||||
return (int) (dp * scale + 0.5);
|
||||
}
|
||||
|
||||
public static String fmt(String fmt, Object... args) {
|
||||
return String.format(Locale.US, fmt, args);
|
||||
}
|
||||
|
||||
public static String dos2unix(String s) {
|
||||
String newString = s.replace("\r\n", "\n");
|
||||
if(!newString.endsWith("\n")) {
|
||||
return newString + "\n";
|
||||
} else {
|
||||
return newString;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setupUpdateCheck() {
|
||||
MagiskManager mm = Data.MM();
|
||||
JobScheduler scheduler = (JobScheduler) mm.getSystemService(Context.JOB_SCHEDULER_SERVICE);
|
||||
|
||||
if (mm.prefs.getBoolean(Const.Key.CHECK_UPDATES, true)) {
|
||||
if (scheduler.getAllPendingJobs().isEmpty() ||
|
||||
Const.UPDATE_SERVICE_VER > mm.prefs.getInt(Const.Key.UPDATE_SERVICE_VER, -1)) {
|
||||
ComponentName service = new ComponentName(mm, n.class);
|
||||
JobInfo info = new JobInfo.Builder(Const.ID.UPDATE_SERVICE_ID, service)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
.setPersisted(true)
|
||||
.setPeriodic(8 * 60 * 60 * 1000)
|
||||
.build();
|
||||
scheduler.schedule(info);
|
||||
}
|
||||
} else {
|
||||
scheduler.cancel(Const.UPDATE_SERVICE_VER);
|
||||
}
|
||||
}
|
||||
|
||||
public static void openLink(Context context, Uri link) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, link);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
toast(R.string.open_link_failed_toast, Toast.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
public static void toast(CharSequence msg, int duration) {
|
||||
Data.mainHandler.post(() -> Toast.makeText(Data.MM(), msg, duration).show());
|
||||
}
|
||||
|
||||
public static void toast(int resId, int duration) {
|
||||
Data.mainHandler.post(() -> Toast.makeText(Data.MM(), resId, duration).show());
|
||||
}
|
||||
|
||||
public static void loadModules() {
|
||||
Topic.reset(Topic.MODULE_LOAD_DONE);
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
Map<String, Module> moduleMap = new ValueSortedMap<>();
|
||||
SuFile path = new SuFile(Const.MAGISK_PATH);
|
||||
String[] modules = path.list(
|
||||
(file, name) -> !name.equals("lost+found") && !name.equals(".core"));
|
||||
for (String name : modules) {
|
||||
Module module = new Module(Const.MAGISK_PATH + "/" + name);
|
||||
moduleMap.put(module.getId(), module);
|
||||
}
|
||||
Topic.publish(Topic.MODULE_LOAD_DONE, moduleMap);
|
||||
});
|
||||
}
|
||||
}
|
||||
65
app/src/full/java/com/topjohnwu/magisk/utils/ZipUtils.java
Normal file
65
app/src/full/java/com/topjohnwu/magisk/utils/ZipUtils.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
import com.topjohnwu.utils.JarMap;
|
||||
import com.topjohnwu.utils.SignAPK;
|
||||
|
||||
import java.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 {
|
||||
|
||||
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 IOException {
|
||||
try {
|
||||
ZipInputStream zipfile = new ZipInputStream(zip);
|
||||
ZipEntry entry;
|
||||
while ((entry = zipfile.getNextEntry()) != null) {
|
||||
if (!entry.getName().startsWith(path) || entry.isDirectory()){
|
||||
// Ignore directories, only create files
|
||||
continue;
|
||||
}
|
||||
String name;
|
||||
if (junkPath) {
|
||||
name = entry.getName().substring(entry.getName().lastIndexOf('/') + 1);
|
||||
} else {
|
||||
name = entry.getName();
|
||||
}
|
||||
File dest = new File(folder, name);
|
||||
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(IOException e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static void signZip(File input, File output) throws Exception {
|
||||
try (JarMap map = new JarMap(input, false);
|
||||
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(output))) {
|
||||
SignAPK.sign(map, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user