mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-14 07:17:25 +00:00
Compare commits
628 Commits
v18.1
...
manager-v7
Author | SHA1 | Date | |
---|---|---|---|
![]() |
120bd6cd68 | ||
![]() |
9aef06d1b8 | ||
![]() |
e6e9dd751c | ||
![]() |
5dd677756f | ||
![]() |
b77c590910 | ||
![]() |
7e5f2822ae | ||
![]() |
12bbc7fd6b | ||
![]() |
bf9ac8252b | ||
![]() |
4a3f5dc619 | ||
![]() |
ca156befbd | ||
![]() |
4db41e2ac4 | ||
![]() |
982a43fce1 | ||
![]() |
dd76a74e1c | ||
![]() |
70cb52b2c7 | ||
![]() |
5c7f69acaa | ||
![]() |
f1d9015e5f | ||
![]() |
e8d900c58e | ||
![]() |
a6241ae912 | ||
![]() |
4a697ca2ec | ||
![]() |
58bec7f2c9 | ||
![]() |
213f84985c | ||
![]() |
074b1f8c61 | ||
![]() |
326eee8c83 | ||
![]() |
00bff4912e | ||
![]() |
0ce1720516 | ||
![]() |
ee407472cf | ||
![]() |
f341f3b2dd | ||
![]() |
8513946e09 | ||
![]() |
8ebd9c8927 | ||
![]() |
1d54c5144e | ||
![]() |
e40d4318fa | ||
![]() |
7756e10779 | ||
![]() |
3e58d502d0 | ||
![]() |
1c8846dc57 | ||
![]() |
2f320c7239 | ||
![]() |
e799918ab6 | ||
![]() |
86c4928e0f | ||
![]() |
0293eb5c51 | ||
![]() |
1ee75b6aa6 | ||
![]() |
4b30b224b5 | ||
![]() |
16b232d2a3 | ||
![]() |
3f3b1f5b1d | ||
![]() |
cec017b7bf | ||
![]() |
3123cc1059 | ||
![]() |
caa9df86bc | ||
![]() |
f417389a7a | ||
![]() |
662a5c8ea6 | ||
![]() |
7edfbfb764 | ||
![]() |
c1602d2554 | ||
![]() |
9f8d4e1022 | ||
![]() |
d1dfda405f | ||
![]() |
28efded624 | ||
![]() |
06c86ee267 | ||
![]() |
5892780871 | ||
![]() |
4fcdcd9a8a | ||
![]() |
80d834fb55 | ||
![]() |
4122ebe18f | ||
![]() |
7d87777bf8 | ||
![]() |
4a73d634e0 | ||
![]() |
373dc10a40 | ||
![]() |
ed43ec8ea2 | ||
![]() |
7918fc3528 | ||
![]() |
bf58205b0a | ||
![]() |
c0d1ce96d1 | ||
![]() |
b31d3802eb | ||
![]() |
be1228c3b4 | ||
![]() |
15c94c6b34 | ||
![]() |
202d23426a | ||
![]() |
fc26de48b2 | ||
![]() |
76c88913f9 | ||
![]() |
a3a1aed723 | ||
![]() |
81aa56f60f | ||
![]() |
73bb850209 | ||
![]() |
8dfec12330 | ||
![]() |
ae24397793 | ||
![]() |
3b0f888407 | ||
![]() |
845d1e02b0 | ||
![]() |
5d357bc41f | ||
![]() |
6a54672b13 | ||
![]() |
3d9a15df44 | ||
![]() |
449c7fda2f | ||
![]() |
8b7b05da68 | ||
![]() |
92400ebcab | ||
![]() |
23d3e56967 | ||
![]() |
6785dc4967 | ||
![]() |
dad20f6a2d | ||
![]() |
bb15671046 | ||
![]() |
21984fac8b | ||
![]() |
f392afe87f | ||
![]() |
6a243ec7bc | ||
![]() |
8cd3b603df | ||
![]() |
6e1aefe6d8 | ||
![]() |
1c90b6eca3 | ||
![]() |
c33cf9f878 | ||
![]() |
27cb40eec9 | ||
![]() |
c06081b75d | ||
![]() |
a7eec2f0a0 | ||
![]() |
4fd0fe3194 | ||
![]() |
cc74593ddd | ||
![]() |
fdb7c5dba1 | ||
![]() |
77470c7cfa | ||
![]() |
f0a734fdab | ||
![]() |
75405b2b25 | ||
![]() |
90ed4b3c49 | ||
![]() |
290a17a764 | ||
![]() |
aaabd836e4 | ||
![]() |
076e5cea3b | ||
![]() |
8515971ccf | ||
![]() |
d86fb033ea | ||
![]() |
99d7d8ddbc | ||
![]() |
df78fd2d41 | ||
![]() |
dabe6267b9 | ||
![]() |
0119ebddbe | ||
![]() |
3216ef9f47 | ||
![]() |
b79d1bcded | ||
![]() |
17e234f9d5 | ||
![]() |
ea1f75f80e | ||
![]() |
8c40db5730 | ||
![]() |
6fe03d2795 | ||
![]() |
c595a87ccf | ||
![]() |
fac07c3913 | ||
![]() |
c63fdbbc6b | ||
![]() |
2ff5d9606b | ||
![]() |
ed43452c1a | ||
![]() |
8f28d4028f | ||
![]() |
b54543b18c | ||
![]() |
966d6593ca | ||
![]() |
ad95b1c9d1 | ||
![]() |
3bfa38c60a | ||
![]() |
0bdbcad8be | ||
![]() |
80855e89ec | ||
![]() |
0850401dc4 | ||
![]() |
337fda2023 | ||
![]() |
64f238191e | ||
![]() |
eb169cb133 | ||
![]() |
80cd85b061 | ||
![]() |
89275270f3 | ||
![]() |
e7339ba619 | ||
![]() |
d9ad7d522c | ||
![]() |
92789c3113 | ||
![]() |
c1c677e161 | ||
![]() |
2fe917ff82 | ||
![]() |
0e6c205732 | ||
![]() |
125ae0a173 | ||
![]() |
0245e13591 | ||
![]() |
d546733287 | ||
![]() |
c275326d59 | ||
![]() |
d4561507b8 | ||
![]() |
ef0e22cc41 | ||
![]() |
62db65bf18 | ||
![]() |
d5371f752c | ||
![]() |
a5f5e94115 | ||
![]() |
2624706c69 | ||
![]() |
d39d885ec2 | ||
![]() |
d83c744725 | ||
![]() |
843995cdb9 | ||
![]() |
9491ba77e0 | ||
![]() |
58a449d437 | ||
![]() |
7f55e0f05b | ||
![]() |
67c3f40adb | ||
![]() |
ff7a0ba599 | ||
![]() |
b152c63102 | ||
![]() |
415ff23be5 | ||
![]() |
b0d6de783e | ||
![]() |
ac28e6e5ca | ||
![]() |
4f9e8d2e8a | ||
![]() |
21be2f46f3 | ||
![]() |
a6e7680212 | ||
![]() |
e79e744e08 | ||
![]() |
7abdac72a4 | ||
![]() |
90d85eaf7d | ||
![]() |
e65f9740fb | ||
![]() |
7538f89b56 | ||
![]() |
7c755a3991 | ||
![]() |
10e903c9fc | ||
![]() |
b018124226 | ||
![]() |
a9350f50c9 | ||
![]() |
ed7babcbf1 | ||
![]() |
61ebc335c4 | ||
![]() |
0167bd76f1 | ||
![]() |
79d704008b | ||
![]() |
0a703585b0 | ||
![]() |
5d632d0d90 | ||
![]() |
4eecaea601 | ||
![]() |
63055818ec | ||
![]() |
0beb08b687 | ||
![]() |
b27801a27c | ||
![]() |
a0cfce7cbc | ||
![]() |
8b7144c986 | ||
![]() |
d3f5f5ee59 | ||
![]() |
a2a3c7f438 | ||
![]() |
4496f82d5b | ||
![]() |
09d531557d | ||
![]() |
7fee82f731 | ||
![]() |
475054c48a | ||
![]() |
a743d05751 | ||
![]() |
d1ed502e03 | ||
![]() |
37744c7ab6 | ||
![]() |
bbc9e60a12 | ||
![]() |
6c975ecc4c | ||
![]() |
23e8a4ce4b | ||
![]() |
50134a2f9b | ||
![]() |
628b37c4fa | ||
![]() |
1b4ae70a43 | ||
![]() |
b25c49725f | ||
![]() |
b245782c7e | ||
![]() |
a9f32baae0 | ||
![]() |
e7ef71865d | ||
![]() |
88c4f72b37 | ||
![]() |
abbcdf91a5 | ||
![]() |
b876df6e21 | ||
![]() |
4bb81f35d7 | ||
![]() |
ff20267b3f | ||
![]() |
2c9586d811 | ||
![]() |
2813d2031a | ||
![]() |
4040a0242f | ||
![]() |
781ec810d9 | ||
![]() |
9e90a71c04 | ||
![]() |
5571714b26 | ||
![]() |
e0d1f02ef5 | ||
![]() |
1b729e5ff2 | ||
![]() |
51e587d4e8 | ||
![]() |
ac9c55dbc1 | ||
![]() |
065051a360 | ||
![]() |
0893ac3141 | ||
![]() |
fb40e96917 | ||
![]() |
4ca25f74c6 | ||
![]() |
7fda917b86 | ||
![]() |
e6bd5f2c40 | ||
![]() |
8a904ee384 | ||
![]() |
00a9f18a1e | ||
![]() |
8d68ebb074 | ||
![]() |
5f53cfb4a9 | ||
![]() |
a2fa8d8be1 | ||
![]() |
70a3c78ebb | ||
![]() |
db218407b0 | ||
![]() |
d52210dd90 | ||
![]() |
f3cd9a096a | ||
![]() |
e426090a18 | ||
![]() |
cbe64fd559 | ||
![]() |
63ea7a70bd | ||
![]() |
fb0998f7a2 | ||
![]() |
a9b00dd537 | ||
![]() |
52eb059515 | ||
![]() |
7640246255 | ||
![]() |
52c83b2916 | ||
![]() |
d9cded0fc9 | ||
![]() |
750c42caf1 | ||
![]() |
bbf650c6cf | ||
![]() |
a25dace7e0 | ||
![]() |
14ff22fbcd | ||
![]() |
07eb7dda2d | ||
![]() |
54d1207f92 | ||
![]() |
003e44fb84 | ||
![]() |
515f346dcc | ||
![]() |
d4058175b4 | ||
![]() |
2de984ae24 | ||
![]() |
761a8bf2a9 | ||
![]() |
6df7006b36 | ||
![]() |
aceb3ee863 | ||
![]() |
11d716a3c8 | ||
![]() |
7cc8c014eb | ||
![]() |
f21241d944 | ||
![]() |
a181fa0652 | ||
![]() |
3f748b4d2a | ||
![]() |
683450f9c6 | ||
![]() |
6050c4e8ba | ||
![]() |
158af8819a | ||
![]() |
7787bb31fa | ||
![]() |
a1fe3e7ccd | ||
![]() |
4316028b23 | ||
![]() |
f2b52755d6 | ||
![]() |
adbd47a36c | ||
![]() |
ce693aa5e9 | ||
![]() |
ad80804461 | ||
![]() |
2d55632430 | ||
![]() |
e81f00ef1a | ||
![]() |
93fb0e3d74 | ||
![]() |
71ce0de606 | ||
![]() |
0407062c1d | ||
![]() |
f315c4416b | ||
![]() |
cda14af208 | ||
![]() |
258f170cd7 | ||
![]() |
f76015d714 | ||
![]() |
7e5e14163c | ||
![]() |
bcd1064e94 | ||
![]() |
8a8441c875 | ||
![]() |
15aa813416 | ||
![]() |
605faccffd | ||
![]() |
79f2d08c81 | ||
![]() |
0568ae5391 | ||
![]() |
5330dda9f8 | ||
![]() |
ebab126579 | ||
![]() |
0e5417a13e | ||
![]() |
9a968e0584 | ||
![]() |
ffec64d209 | ||
![]() |
f332746188 | ||
![]() |
b2fa5b551e | ||
![]() |
36e83edddc | ||
![]() |
6b045eadef | ||
![]() |
147264822c | ||
![]() |
36e4ccd800 | ||
![]() |
796c16237d | ||
![]() |
861ad9881c | ||
![]() |
3101c538e9 | ||
![]() |
42adc7382f | ||
![]() |
9bb4dfad13 | ||
![]() |
4e7dafb0e4 | ||
![]() |
bd00ae8ede | ||
![]() |
f309522268 | ||
![]() |
a6395d35db | ||
![]() |
a028cd5cec | ||
![]() |
540000d26e | ||
![]() |
888c656aa8 | ||
![]() |
0efaddff23 | ||
![]() |
94ba7cb0c5 | ||
![]() |
2d58c725e0 | ||
![]() |
e035523eb8 | ||
![]() |
bea5308ab7 | ||
![]() |
f006a85fec | ||
![]() |
ea93013ebc | ||
![]() |
8d4c407201 | ||
![]() |
fdeede23f7 | ||
![]() |
53c5ca59b6 | ||
![]() |
679db97209 | ||
![]() |
fbdd72273e | ||
![]() |
0165602515 | ||
![]() |
96127f8bd1 | ||
![]() |
0dbdf336d6 | ||
![]() |
48879df2da | ||
![]() |
b067a5bb13 | ||
![]() |
4b54cf1288 | ||
![]() |
6128c24f96 | ||
![]() |
d9c58f307f | ||
![]() |
b521fbeeda | ||
![]() |
d00a3b89f2 | ||
![]() |
3d15518191 | ||
![]() |
9b6535fdf5 | ||
![]() |
e0424fdba3 | ||
![]() |
7481c53451 | ||
![]() |
7219947237 | ||
![]() |
b72004e9cc | ||
![]() |
f187213568 | ||
![]() |
fc0df84edd | ||
![]() |
f24df4f43d | ||
![]() |
dab32e1599 | ||
![]() |
bc286fd4d3 | ||
![]() |
befe1a83b5 | ||
![]() |
82ea9db9fd | ||
![]() |
c5758b3f2d | ||
![]() |
ace3708c9c | ||
![]() |
fc5026d268 | ||
![]() |
77fd0e54be | ||
![]() |
24490e0ff5 | ||
![]() |
da3937ff4e | ||
![]() |
ebe1ab982e | ||
![]() |
98590cb00d | ||
![]() |
ff95f634f0 | ||
![]() |
ced9b4a8ee | ||
![]() |
7af7910e78 | ||
![]() |
a4f5d47e72 | ||
![]() |
6953cc2411 | ||
![]() |
6a0b2ddee9 | ||
![]() |
24f5bc98d8 | ||
![]() |
5203886f0b | ||
![]() |
c10b376575 | ||
![]() |
ceb21ced2b | ||
![]() |
86789a8694 | ||
![]() |
ca2235aee7 | ||
![]() |
a385e5cd92 | ||
![]() |
0c7a95bdf6 | ||
![]() |
036b5acf42 | ||
![]() |
056dafc59f | ||
![]() |
a9c90718d6 | ||
![]() |
cc77a24502 | ||
![]() |
71a91ac7a7 | ||
![]() |
08a70f033a | ||
![]() |
1b0c36dbd5 | ||
![]() |
91da1cf817 | ||
![]() |
c577a9525d | ||
![]() |
0149b1368d | ||
![]() |
cd6bcb97ef | ||
![]() |
df4161ffcc | ||
![]() |
7a133eaf03 | ||
![]() |
1cd45b53b1 | ||
![]() |
5b30c77403 | ||
![]() |
8248480d56 | ||
![]() |
345d992d39 | ||
![]() |
a7f6afa4bc | ||
![]() |
d22c7de79a | ||
![]() |
3eae9494ce | ||
![]() |
be7e737253 | ||
![]() |
b6eb912dba | ||
![]() |
8049b08918 | ||
![]() |
d1fa5be210 | ||
![]() |
fdbb1af02c | ||
![]() |
c85a5cae88 | ||
![]() |
649ef53409 | ||
![]() |
e784212283 | ||
![]() |
66eb1078fe | ||
![]() |
1c09b3642f | ||
![]() |
d08b1a6639 | ||
![]() |
10f50e2401 | ||
![]() |
8e9a7b25a1 | ||
![]() |
4859ee2da9 | ||
![]() |
b45db44ad9 | ||
![]() |
e25ce63872 | ||
![]() |
162eeaa0a6 | ||
![]() |
f36ce905aa | ||
![]() |
8ac3aaf36c | ||
![]() |
a199b0ace1 | ||
![]() |
2f2108e4e8 | ||
![]() |
f5f7fd9132 | ||
![]() |
f9ae4ab475 | ||
![]() |
8de03eef3f | ||
![]() |
8df942f96e | ||
![]() |
9bb2243b56 | ||
![]() |
db06038548 | ||
![]() |
ecb33d3176 | ||
![]() |
eae1c17738 | ||
![]() |
ea55532e33 | ||
![]() |
2a40cb60a9 | ||
![]() |
d371d017b7 | ||
![]() |
1d9359d563 | ||
![]() |
945f88105f | ||
![]() |
957feca626 | ||
![]() |
c0447009db | ||
![]() |
8893cbd64a | ||
![]() |
f0240b1f06 | ||
![]() |
e476c18c99 | ||
![]() |
a1b5185ecb | ||
![]() |
981e90cc32 | ||
![]() |
da0a72e8b0 | ||
![]() |
b7e2e972c7 | ||
![]() |
650b2ce6b1 | ||
![]() |
ecf3d30349 | ||
![]() |
15ddd0e284 | ||
![]() |
18ac6b270f | ||
![]() |
3e35de9b39 | ||
![]() |
1e24c72c11 | ||
![]() |
217564963d | ||
![]() |
f2f4649ab0 | ||
![]() |
4395ffec5f | ||
![]() |
9a7a26407a | ||
![]() |
5072a67807 | ||
![]() |
dce0b6c05a | ||
![]() |
a4a661bf34 | ||
![]() |
771e500468 | ||
![]() |
7e3ff03109 | ||
![]() |
a1827fd680 | ||
![]() |
9ce334feac | ||
![]() |
ed11e0bff6 | ||
![]() |
5111086637 | ||
![]() |
20f204810e | ||
![]() |
4581354e7a | ||
![]() |
faf4d76388 | ||
![]() |
a46e255709 | ||
![]() |
63e2bbb4d1 | ||
![]() |
c3dabae237 | ||
![]() |
f1abcbb7fb | ||
![]() |
70efddb90f | ||
![]() |
f24a5dfd45 | ||
![]() |
081074ad9d | ||
![]() |
ab0cc78d2c | ||
![]() |
de5c902fdb | ||
![]() |
cf65169c99 | ||
![]() |
745865ee53 | ||
![]() |
c134fb1939 | ||
![]() |
0204d05316 | ||
![]() |
c345633d80 | ||
![]() |
a57a94040e | ||
![]() |
1bde78d121 | ||
![]() |
bbd014ad1b | ||
![]() |
1287372f5a | ||
![]() |
d2cb638fcd | ||
![]() |
bbe4b69c8d | ||
![]() |
7f08c06943 | ||
![]() |
8f4a6415cd | ||
![]() |
0442d6d509 | ||
![]() |
a3fc6d2a27 | ||
![]() |
7db05ac927 | ||
![]() |
8bed93b3c5 | ||
![]() |
915b49014f | ||
![]() |
c699f30831 | ||
![]() |
3e73e3a906 | ||
![]() |
32c65d8a88 | ||
![]() |
a49328edd3 | ||
![]() |
9a15365a57 | ||
![]() |
82c864d57e | ||
![]() |
6226f875ff | ||
![]() |
370015a853 | ||
![]() |
6597b7adc0 | ||
![]() |
4e53ebfe44 | ||
![]() |
04ef1e6405 | ||
![]() |
b278d07b05 | ||
![]() |
6c3896079d | ||
![]() |
e73fa57d54 | ||
![]() |
eaa9c7e2a0 | ||
![]() |
14ae29d907 | ||
![]() |
e8f35b02ca | ||
![]() |
dee3c3e7ba | ||
![]() |
d8cd2031c7 | ||
![]() |
7203e7df5c | ||
![]() |
b51feffe80 | ||
![]() |
b1afd554fc | ||
![]() |
885e3c574b | ||
![]() |
05dd5f3396 | ||
![]() |
ec3c43faf1 | ||
![]() |
e72c6685ed | ||
![]() |
99d6bd8efc | ||
![]() |
4c8587a9f2 | ||
![]() |
54a8a05dae | ||
![]() |
164a99681b | ||
![]() |
0eef4eacd6 | ||
![]() |
5764f0c839 | ||
![]() |
f28e425542 | ||
![]() |
d1a4f046e9 | ||
![]() |
2ce1dc4afe | ||
![]() |
37ac249fd7 | ||
![]() |
f152bea8d8 | ||
![]() |
7b089b888a | ||
![]() |
68f0e1fe39 | ||
![]() |
8032bd0bac | ||
![]() |
0c227f2917 | ||
![]() |
c9fa8118d1 | ||
![]() |
63b18246d8 | ||
![]() |
16ec37a226 | ||
![]() |
bd4e5bfc1a | ||
![]() |
621fd0ee29 | ||
![]() |
6ca8db2f0c | ||
![]() |
ea129fb206 | ||
![]() |
3356d7b6ff | ||
![]() |
c84023bdc2 | ||
![]() |
86f778c0aa | ||
![]() |
defbbdfe21 | ||
![]() |
0f46493477 | ||
![]() |
340bac7e42 | ||
![]() |
1d3ce9fef1 | ||
![]() |
4a398642b8 | ||
![]() |
9c89e56c56 | ||
![]() |
267c59b1f1 | ||
![]() |
2ab17204c6 | ||
![]() |
75939047d1 | ||
![]() |
2d7f130d2c | ||
![]() |
f7ae72a36c | ||
![]() |
391783e268 | ||
![]() |
6f12c08204 | ||
![]() |
cb8fe70734 | ||
![]() |
69d10b747a | ||
![]() |
da3394f34e | ||
![]() |
b4c2a9f49f | ||
![]() |
7cee77f57a | ||
![]() |
f28bd1972f | ||
![]() |
0f92d1de1b | ||
![]() |
e59c5c8780 | ||
![]() |
86d8026301 | ||
![]() |
d67b827338 | ||
![]() |
660e0dc09a | ||
![]() |
3ebc886f8a | ||
![]() |
5b54ef840a | ||
![]() |
c08b0d4974 | ||
![]() |
7d652afd87 | ||
![]() |
0f61c627b1 | ||
![]() |
7126648404 | ||
![]() |
4a5e2dc9c7 | ||
![]() |
10613686ed | ||
![]() |
17ab55115a | ||
![]() |
2708c74ebe | ||
![]() |
50ff11405f | ||
![]() |
31a27838f5 | ||
![]() |
2f1b0fe57f | ||
![]() |
692f893e1f | ||
![]() |
14aa6041ec | ||
![]() |
fb55fe184c | ||
![]() |
6412bfc7b5 | ||
![]() |
3c56f38229 | ||
![]() |
f4f2274c60 | ||
![]() |
19ee189468 | ||
![]() |
a19c7215d2 | ||
![]() |
8b84039f1f | ||
![]() |
9430dbb96c | ||
![]() |
4872df6a46 | ||
![]() |
014105f0a0 | ||
![]() |
b106d1c501 | ||
![]() |
99db0672b4 | ||
![]() |
d584360de2 | ||
![]() |
4eed6794c7 | ||
![]() |
c66cabd80f | ||
![]() |
24da3485bd | ||
![]() |
7384d2d330 | ||
![]() |
e5940168fe | ||
![]() |
6855baf0f8 | ||
![]() |
dfd16e8fef | ||
![]() |
98a36819bc | ||
![]() |
de8bc9ca9d | ||
![]() |
c137f2de4f | ||
![]() |
0f55fcafe8 | ||
![]() |
ed027ec3ee | ||
![]() |
b3fd79cbb9 | ||
![]() |
ed4df87b57 | ||
![]() |
1321f097b8 | ||
![]() |
cfa28f0c4a | ||
![]() |
ab47b717b1 | ||
![]() |
65ebb0d2f8 | ||
![]() |
49640ce03a | ||
![]() |
e05cdc83f3 | ||
![]() |
992a9ea2f9 | ||
![]() |
228351fc13 | ||
![]() |
8a5b6f2b86 | ||
![]() |
71ecbb3af3 | ||
![]() |
5746614ccf | ||
![]() |
3a422c3f15 | ||
![]() |
b3242322fd | ||
![]() |
9826640ae6 | ||
![]() |
1f5267204b | ||
![]() |
ed25e1bbd6 | ||
![]() |
c8491d008f | ||
![]() |
08e3405394 | ||
![]() |
4ebfa07186 | ||
![]() |
6698c189fc | ||
![]() |
f0639390aa | ||
![]() |
bbdfed2d5a | ||
![]() |
7f4daa2c50 | ||
![]() |
baf9b67b35 | ||
![]() |
caf73b0b36 | ||
![]() |
acf87c2794 | ||
![]() |
7f5f6b54fb | ||
![]() |
a08eb8a446 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -16,3 +16,4 @@ chromeos/** binary
|
||||
*.exe binary
|
||||
*.apk binary
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,4 +15,3 @@ native/out
|
||||
/.idea
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
|
80
README.MD
80
README.MD
@@ -1,41 +1,75 @@
|
||||
# Magisk
|
||||
[Downloads](https://github.com/topjohnwu/Magisk/releases) | [Documentation](https://topjohnwu.github.io/Magisk/) | [XDA Thread](https://forum.xda-developers.com/apps/magisk/official-magisk-v7-universal-systemless-t3473445)
|
||||
|
||||
[Downloads](https://github.com/topjohnwu/Magisk/releases) \| [Documentation](https://topjohnwu.github.io/Magisk/) \| [XDA Thread](https://forum.xda-developers.com/apps/magisk/official-magisk-v7-universal-systemless-t3473445)
|
||||
|
||||
## Introduction
|
||||
|
||||
Magisk is a suite of open source tools for customizing Android, supporting devices higher than Android 4.2 (API 17). It covers the fundamental parts for Android customization: root, boot scripts, SELinux patches, AVB2.0 / dm-verity / forceencrypt removals etc.
|
||||
|
||||
Furthermore, Magisk provides a **Systemless Interface** to alter the system (or vendor) arbitrarily while the actual partitions stay completely intact. With its systemless nature along with several other hacks, Magisk can hide modifications from nearly any system integrity verifications used in banking apps, corporation monitoring apps, game cheat detections, and most importantly [Google's SafetyNet API](https://developer.android.com/training/safetynet/index.html).
|
||||
|
||||
## 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!** **DO NOT** report bugs that is already fixed upstream. 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.
|
||||
|
||||
## Building Environment Requirements
|
||||
1. Python 3: 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
|
||||
|
||||
- Python 3: run `build.py` script
|
||||
- Java Development Kit (JDK) 8: Compile Magisk Manager and sign zips
|
||||
- Latest Android SDK: set `ANDROID_HOME` environment variable to the path to Android SDK
|
||||
- Android NDK: Install NDK along with SDK (`$ANDROID_HOME/ndk-bundle`), or optionally specify a custom path `ANDROID_NDK_HOME`
|
||||
- (Windows Only) Python package Colorama: Install with `pip install colorama`, used for ANSI color codes
|
||||
|
||||
## 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).
|
||||
|
||||
- Clone sources with submodules: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`
|
||||
- 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.
|
||||
- Set configurations in `config.prop`. A sample file `config.prop.sample` is provided as an example.
|
||||
- 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`
|
||||
- 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).
|
||||
|
||||
## Translations
|
||||
|
||||
Default string resources for Magisk Manager are scattered throughout
|
||||
|
||||
- `app/src/main/res/values/strings.xml`
|
||||
- `stub/src/main/res/values/strings.xml`
|
||||
- `shared/src/main/res/values/strings.xml`
|
||||
|
||||
Translate each and place them in the respective locations (`<module>/src/main/res/values-<lang>/strings.xml`).
|
||||
|
||||
## Signature Verification
|
||||
|
||||
Official release zips and APKs are signed with my personal private key. You can verify the key certificate to make sure the binaries you downloaded are not manipulated in anyway.
|
||||
|
||||
``` bash
|
||||
# Use the keytool command from JDK to print certificates
|
||||
keytool -printcert -jarfile <APK or Magisk zip>
|
||||
|
||||
# The output should contain the following signature
|
||||
Owner: CN=John Wu, L=Taipei, C=TW
|
||||
Issuer: CN=John Wu, L=Taipei, C=TW
|
||||
Serial number: 50514879
|
||||
Valid from: Sun Aug 14 13:23:44 EDT 2016 until: Tue Jul 21 13:23:44 EDT 2116
|
||||
Certificate fingerprints:
|
||||
MD5: CE:DA:68:C1:E1:74:71:0A:EF:58:89:7D:AE:6E:AB:4F
|
||||
SHA1: DC:0F:2B:61:CB:D7:E9:D3:DB:BE:06:0B:2B:87:0D:46:BB:06:02:11
|
||||
SHA256: B4:CB:83:B4:DA:D9:9F:99:7D:BE:87:2F:01:3A:A1:6C:14:EE:C4:1D:16:70:21:F3:71:F7:E1:33:0F:27:3E:E6
|
||||
Signature algorithm name: SHA256withRSA
|
||||
Version: 3
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
```
|
||||
Magisk, including all git submodules are free software:
|
||||
you can redistribute it and/or modify it under the terms of the
|
||||
GNU General Public License as published by the Free Software Foundation,
|
||||
either version 3 of the License, or (at your option) any later version.
|
||||
Magisk, including all git submodules are free software:
|
||||
you can redistribute it and/or modify it under the terms of the
|
||||
GNU General Public License as published by the Free Software Foundation,
|
||||
either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
```
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
2
app-core/.gitignore
vendored
2
app-core/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/build
|
||||
src/main/res/raw/util_functions.sh
|
@@ -1,21 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
api project(':net')
|
||||
api project(':signing')
|
||||
api 'org.kamranzafar:jtar:2.3'
|
||||
|
||||
def libsuVersion = '2.3.0'
|
||||
api "com.github.topjohnwu.libsu:core:${libsuVersion}"
|
||||
api "com.github.topjohnwu.libsu:io:${libsuVersion}"
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.topjohnwu.magisk.core" >
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
</manifest>
|
@@ -1,61 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.topjohnwu.magisk.core.BuildConfig;
|
||||
import com.topjohnwu.magisk.database.MagiskDB;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
public class App extends Application {
|
||||
|
||||
public static App self;
|
||||
public static ThreadPoolExecutor THREAD_POOL;
|
||||
|
||||
// Global resources
|
||||
public SharedPreferences prefs;
|
||||
public MagiskDB mDB;
|
||||
public RepoDatabaseHelper repoDB;
|
||||
|
||||
static {
|
||||
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER | Shell.FLAG_USE_MAGISK_BUSYBOX);
|
||||
Shell.Config.verboseLogging(BuildConfig.DEBUG);
|
||||
Shell.Config.addInitializers(RootUtils.class);
|
||||
Shell.Config.setTimeout(2);
|
||||
THREAD_POOL = (ThreadPoolExecutor) AsyncTask.THREAD_POOL_EXECUTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
self = this;
|
||||
|
||||
Context de = this;
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
de = createDeviceProtectedStorageContext();
|
||||
de.moveSharedPreferencesFrom(this, PreferenceManager.getDefaultSharedPreferencesName(base));
|
||||
}
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(de);
|
||||
mDB = new MagiskDB(this);
|
||||
|
||||
Networking.init(this);
|
||||
LocaleManager.setLocale(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
LocaleManager.setLocale(this);
|
||||
}
|
||||
}
|
@@ -1,372 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Xml;
|
||||
|
||||
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;
|
||||
|
||||
public class Config {
|
||||
|
||||
// Current status
|
||||
public static String magiskVersionString;
|
||||
public static int magiskVersionCode = -1;
|
||||
private 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;
|
||||
|
||||
// Install flags
|
||||
public static boolean keepVerity = false;
|
||||
public static boolean keepEnc = false;
|
||||
public static boolean recovery = false;
|
||||
|
||||
public static int suLogTimeout = 14;
|
||||
|
||||
public static class Key {
|
||||
// su configs
|
||||
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";
|
||||
|
||||
// prefs
|
||||
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 MAGISKHIDE = "magiskhide";
|
||||
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 REPO_ORDER = "repo_order";
|
||||
public static final String SHOW_SYSTEM_APP = "show_system";
|
||||
}
|
||||
|
||||
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 int[] TIMEOUT_LIST = {0, -1, 10, 20, 30, 60};
|
||||
public static final int ORDER_NAME = 0;
|
||||
public static final int ORDER_DATE = 1;
|
||||
}
|
||||
|
||||
private static Bundle defs = new Bundle();
|
||||
|
||||
static {
|
||||
/* Set default configurations */
|
||||
|
||||
// prefs int
|
||||
defs.putInt(Key.REPO_ORDER, Value.ORDER_DATE);
|
||||
|
||||
// prefs string int
|
||||
defs.putInt(Key.SU_REQUEST_TIMEOUT, 10);
|
||||
defs.putInt(Key.SU_AUTO_RESPONSE, Value.SU_PROMPT);
|
||||
defs.putInt(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST);
|
||||
defs.putInt(Key.UPDATE_CHANNEL, Value.STABLE_CHANNEL);
|
||||
|
||||
// prefs bool
|
||||
defs.putBoolean(Key.CHECK_UPDATES, true);
|
||||
// defs.putBoolean(Key.DARK_THEME, false);
|
||||
// defs.putBoolean(Key.SU_REAUTH, false);
|
||||
// defs.putBoolean(Key.MAGISKHIDE, false);
|
||||
// defs.putBoolean(Key.COREONLY, false);
|
||||
// defs.putBoolean(Key.SHOW_SYSTEM_APP, false);
|
||||
|
||||
// prefs string
|
||||
defs.putString(Key.CUSTOM_CHANNEL, "");
|
||||
defs.putString(Key.BOOT_FORMAT, ".img");
|
||||
defs.putString(Key.LOCALE, "");
|
||||
// defs.putString(Key.ETAG_KEY, null);
|
||||
|
||||
// db int
|
||||
defs.putInt(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB);
|
||||
defs.putInt(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER);
|
||||
defs.putInt(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY);
|
||||
|
||||
// db bool
|
||||
// defs.putBoolean(Key.SU_FINGERPRINT, false);
|
||||
|
||||
// db strings
|
||||
// defs.putString(Key.SU_MANAGER, null);
|
||||
}
|
||||
|
||||
public static void loadMagiskInfo() {
|
||||
try {
|
||||
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
|
||||
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
|
||||
magiskHide = Shell.su("magiskhide --status").exec().isSuccess();
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
public static void export() {
|
||||
// Flush prefs to disk
|
||||
App app = App.self;
|
||||
app.prefs.edit().commit();
|
||||
File xml = new File(app.getFilesDir().getParent() + "/shared_prefs",
|
||||
app.getPackageName() + "_preferences.xml");
|
||||
Shell.su(Utils.fmt("cat %s > /data/user/0/%s", xml, Const.MANAGER_CONFIGS)).exec();
|
||||
}
|
||||
|
||||
public static void initialize() {
|
||||
SharedPreferences pref = App.self.prefs;
|
||||
SharedPreferences.Editor editor = pref.edit();
|
||||
SuFile config = new SuFile("/data/user/0/" + Const.MANAGER_CONFIGS);
|
||||
if (config.exists()) {
|
||||
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(Key.ETAG_KEY);
|
||||
editor.apply();
|
||||
editor = pref.edit();
|
||||
config.delete();
|
||||
}
|
||||
|
||||
// Set to defaults if not set
|
||||
setDefs(pref, editor,
|
||||
Key.SU_REQUEST_TIMEOUT, Key.SU_AUTO_RESPONSE, Key.ROOT_ACCESS,
|
||||
Key.SU_MNT_NS, Key.SU_NOTIFICATION, Key.DARK_THEME,
|
||||
Key.CHECK_UPDATES, Key.UPDATE_CHANNEL, Key.REPO_ORDER);
|
||||
|
||||
// These settings are from actual device state
|
||||
editor.putBoolean(Key.MAGISKHIDE, magiskHide)
|
||||
.putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
|
||||
.putInt(Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
|
||||
.apply();
|
||||
}
|
||||
|
||||
private static final int PREF_INT = 0;
|
||||
private static final int PREF_STR_INT = 1;
|
||||
private static final int PREF_BOOL = 2;
|
||||
private static final int PREF_STR = 3;
|
||||
private static final int DB_INT = 4;
|
||||
private static final int DB_BOOL = 5;
|
||||
private static final int DB_STR = 6;
|
||||
|
||||
private static int getConfigType(String key) {
|
||||
switch (key) {
|
||||
case Key.REPO_ORDER:
|
||||
return PREF_INT;
|
||||
|
||||
case Key.SU_REQUEST_TIMEOUT:
|
||||
case Key.SU_AUTO_RESPONSE:
|
||||
case Key.SU_NOTIFICATION:
|
||||
case Key.UPDATE_CHANNEL:
|
||||
return PREF_STR_INT;
|
||||
|
||||
case Key.DARK_THEME:
|
||||
case Key.SU_REAUTH:
|
||||
case Key.CHECK_UPDATES:
|
||||
case Key.MAGISKHIDE:
|
||||
case Key.COREONLY:
|
||||
case Key.SHOW_SYSTEM_APP:
|
||||
return PREF_BOOL;
|
||||
|
||||
case Key.CUSTOM_CHANNEL:
|
||||
case Key.BOOT_FORMAT:
|
||||
case Key.LOCALE:
|
||||
case Key.ETAG_KEY:
|
||||
return PREF_STR;
|
||||
|
||||
case Key.ROOT_ACCESS:
|
||||
case Key.SU_MNT_NS:
|
||||
case Key.SU_MULTIUSER_MODE:
|
||||
return DB_INT;
|
||||
|
||||
case Key.SU_FINGERPRINT:
|
||||
return DB_BOOL;
|
||||
|
||||
case Key.SU_MANAGER:
|
||||
return DB_STR;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T get(String key) {
|
||||
App app = App.self;
|
||||
switch (getConfigType(key)) {
|
||||
case PREF_INT:
|
||||
return (T) (Integer) app.prefs.getInt(key, defs.getInt(key));
|
||||
case PREF_STR_INT:
|
||||
return (T) (Integer) Utils.getPrefsInt(app.prefs, key, defs.getInt(key));
|
||||
case PREF_BOOL:
|
||||
return (T) (Boolean) app.prefs.getBoolean(key, defs.getBoolean(key));
|
||||
case PREF_STR:
|
||||
return (T) app.prefs.getString(key, defs.getString(key));
|
||||
case DB_INT:
|
||||
return (T) (Integer) app.mDB.getSettings(key, defs.getInt(key));
|
||||
case DB_BOOL:
|
||||
return (T) (Boolean) (app.mDB.getSettings(key, defs.getBoolean(key) ? 1 : 0) != 0);
|
||||
case DB_STR:
|
||||
return (T) app.mDB.getStrings(key, defs.getString(key));
|
||||
}
|
||||
/* Will never get here (IllegalArgumentException in getConfigType) */
|
||||
return (T) new Object();
|
||||
}
|
||||
|
||||
public static void set(String key, Object val) {
|
||||
App app = App.self;
|
||||
switch (getConfigType(key)) {
|
||||
case PREF_INT:
|
||||
app.prefs.edit().putInt(key, (int) val).apply();
|
||||
break;
|
||||
case PREF_STR_INT:
|
||||
app.prefs.edit().putString(key, String.valueOf(val)).apply();
|
||||
break;
|
||||
case PREF_BOOL:
|
||||
app.prefs.edit().putBoolean(key, (boolean) val).apply();
|
||||
break;
|
||||
case PREF_STR:
|
||||
app.prefs.edit().putString(key, (String) val).apply();
|
||||
break;
|
||||
case DB_INT:
|
||||
app.mDB.setSettings(key, (int) val);
|
||||
break;
|
||||
case DB_BOOL:
|
||||
app.mDB.setSettings(key, (boolean) val ? 1 : 0);
|
||||
break;
|
||||
case DB_STR:
|
||||
app.mDB.setStrings(key, (String) val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void remove(String key) {
|
||||
App app = App.self;
|
||||
int def;
|
||||
switch (getConfigType(key)) {
|
||||
case PREF_INT:
|
||||
case PREF_STR_INT:
|
||||
case PREF_BOOL:
|
||||
case PREF_STR:
|
||||
app.prefs.edit().remove(key).apply();
|
||||
break;
|
||||
case DB_INT:
|
||||
def = defs.getInt(key);
|
||||
app.mDB.setSettings(key, def);
|
||||
break;
|
||||
case DB_BOOL:
|
||||
def = defs.getBoolean(key) ? 1 : 0;
|
||||
app.mDB.setSettings(key, def);
|
||||
break;
|
||||
case DB_STR:
|
||||
app.mDB.setStrings(key, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void setDefs(SharedPreferences pref, SharedPreferences.Editor editor, String... keys) {
|
||||
for (String key : keys) {
|
||||
if (pref.contains(key))
|
||||
continue;
|
||||
switch (getConfigType(key)) {
|
||||
case PREF_INT:
|
||||
editor.putInt(key, defs.getInt(key));
|
||||
break;
|
||||
case DB_INT:
|
||||
case PREF_STR_INT:
|
||||
editor.putString(key, String.valueOf(defs.getInt(key)));
|
||||
break;
|
||||
case PREF_STR:
|
||||
case DB_STR:
|
||||
editor.putString(key, defs.getString(key));
|
||||
break;
|
||||
case PREF_BOOL:
|
||||
case DB_BOOL:
|
||||
editor.putBoolean(key, defs.getBoolean(key));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,110 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.os.Environment;
|
||||
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";
|
||||
|
||||
// APK content
|
||||
public static final String ANDROID_MANIFEST = "AndroidManifest.xml";
|
||||
|
||||
public static final String SU_KEYSTORE_KEY = "su_key";
|
||||
|
||||
// Paths
|
||||
public static final String MAGISK_PATH = "/sbin/.magisk/img";
|
||||
public static final File EXTERNAL_PATH;
|
||||
public static File MAGISK_DISABLE_FILE;
|
||||
|
||||
static {
|
||||
MAGISK_DISABLE_FILE = new File("xxx");
|
||||
EXTERNAL_PATH = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
EXTERNAL_PATH.mkdirs();
|
||||
}
|
||||
|
||||
public static final String BUSYBOX_PATH = "/sbin/.magisk/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 final int MIN_MODULE_VER = 1500;
|
||||
public static final int SNET_EXT_VER = 12;
|
||||
|
||||
/* A list of apps that should not be shown as hide-able */
|
||||
public static final List<String> HIDE_BLACKLIST = Arrays.asList(
|
||||
App.self.getPackageName(),
|
||||
"com.google.android.gms"
|
||||
);
|
||||
|
||||
public static final int USER_ID = Process.myUid() / 100000;
|
||||
|
||||
public static final class MAGISK_VER {
|
||||
public static final int MIN_SUPPORT = 18000;
|
||||
}
|
||||
|
||||
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 int HIDE_MANAGER_NOTIFICATION_ID = 8;
|
||||
public static final String UPDATE_NOTIFICATION_CHANNEL = "update";
|
||||
public static final String PROGRESS_NOTIFICATION_CHANNEL = "progress";
|
||||
public static final String CHECK_MAGISK_UPDATE_WORKER_ID = "magisk_update";
|
||||
}
|
||||
|
||||
public static class Url {
|
||||
private static String getRaw(String where, String name) {
|
||||
return String.format("https://raw.githubusercontent.com/topjohnwu/magisk_files/%s/%s", where, name);
|
||||
}
|
||||
public static final String STABLE_URL = getRaw("master", "stable.json");
|
||||
public static final String BETA_URL = getRaw("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.me/topjohnwu";
|
||||
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 final String SNET_URL = getRaw("b66b1a914978e5f4c4bbfd74a59f4ad371bac107", "snet.apk");
|
||||
public static final String BOOTCTL_URL = getRaw("9c5dfc1b8245c0b5b524901ef0ff0f8335757b77", "bootctl");
|
||||
}
|
||||
|
||||
public static class Key {
|
||||
// others
|
||||
public static final String LINK_KEY = "Link";
|
||||
public static final String IF_NONE_MATCH = "If-None-Match";
|
||||
// intents
|
||||
public static final String FROM_SPLASH = "splash";
|
||||
public static final String OPEN_SECTION = "section";
|
||||
public static final String INTENT_SET_NAME = "filename";
|
||||
public static final String INTENT_SET_LINK = "link";
|
||||
public static final String FLASH_ACTION = "action";
|
||||
public static final String FLASH_SET_BOOT = "boot";
|
||||
public static final String BROADCAST_MANAGER_UPDATE = "manager_update";
|
||||
public static final String BROADCAST_REBOOT = "reboot";
|
||||
}
|
||||
|
||||
public static class Value {
|
||||
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";
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,186 +0,0 @@
|
||||
package com.topjohnwu.magisk.database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
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 java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MagiskDB {
|
||||
|
||||
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;
|
||||
|
||||
public MagiskDB(Context context) {
|
||||
pm = context.getPackageManager();
|
||||
}
|
||||
|
||||
public void deletePolicy(Policy policy) {
|
||||
deletePolicy(policy.uid);
|
||||
}
|
||||
|
||||
private List<String> rawSQL(String fmt, Object... args) {
|
||||
return Shell.su("magisk --sqlite '" + Utils.fmt(fmt, args) + "'").exec().getOut();
|
||||
}
|
||||
|
||||
private List<ContentValues> SQL(String fmt, Object... args) {
|
||||
List<ContentValues> list = new ArrayList<>();
|
||||
for (String raw : rawSQL(fmt, args)) {
|
||||
ContentValues values = new ContentValues();
|
||||
String[] cols = raw.split("\\|");
|
||||
for (String col : cols) {
|
||||
String[] pair = col.split("=", 2);
|
||||
if (pair.length != 2)
|
||||
continue;
|
||||
values.put(pair[0], pair[1]);
|
||||
}
|
||||
list.add(values);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private String toSQL(ContentValues values) {
|
||||
StringBuilder keys = new StringBuilder(), vals = new StringBuilder();
|
||||
keys.append('(');
|
||||
vals.append("VALUES(");
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, Object> entry : values.valueSet()) {
|
||||
if (!first) {
|
||||
keys.append(',');
|
||||
vals.append(',');
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
keys.append(entry.getKey());
|
||||
vals.append('"');
|
||||
vals.append(entry.getValue());
|
||||
vals.append('"');
|
||||
}
|
||||
keys.append(')');
|
||||
vals.append(')');
|
||||
keys.append(vals);
|
||||
return keys.toString();
|
||||
}
|
||||
|
||||
public void clearOutdated() {
|
||||
rawSQL(
|
||||
"DELETE FROM %s WHERE until > 0 AND until < %d;" +
|
||||
"DELETE FROM %s WHERE time < %d",
|
||||
POLICY_TABLE, System.currentTimeMillis() / 1000,
|
||||
LOG_TABLE, System.currentTimeMillis() - Config.suLogTimeout * 86400000
|
||||
);
|
||||
}
|
||||
|
||||
public void deletePolicy(String pkg) {
|
||||
rawSQL("DELETE FROM %s WHERE package_name=\"%s\"", POLICY_TABLE, pkg);
|
||||
}
|
||||
|
||||
public void deletePolicy(int uid) {
|
||||
rawSQL("DELETE FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
||||
}
|
||||
|
||||
public Policy getPolicy(int uid) {
|
||||
List<ContentValues> res =
|
||||
SQL("SELECT * FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
||||
if (!res.isEmpty()) {
|
||||
try {
|
||||
return new Policy(res.get(0), pm);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
deletePolicy(uid);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void updatePolicy(Policy policy) {
|
||||
rawSQL("REPLACE INTO %s %s", POLICY_TABLE, toSQL(policy.getContentValues()));
|
||||
}
|
||||
|
||||
public List<Policy> getPolicyList() {
|
||||
List<Policy> list = new ArrayList<>();
|
||||
for (ContentValues values : SQL("SELECT * FROM %s WHERE uid/100000=%d", POLICY_TABLE, Const.USER_ID)) {
|
||||
try {
|
||||
list.add(new Policy(values, pm));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
deletePolicy(values.getAsInteger("uid"));
|
||||
}
|
||||
}
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public List<List<SuLogEntry>> getLogs() {
|
||||
List<List<SuLogEntry>> ret = new ArrayList<>();
|
||||
List<SuLogEntry> list = null;
|
||||
String dateString = null, newString;
|
||||
for (ContentValues values : SQL("SELECT * FROM %s ORDER BY time DESC", LOG_TABLE)) {
|
||||
Date date = new Date(values.getAsLong("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(new SuLogEntry(values));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void addLog(SuLogEntry log) {
|
||||
rawSQL("INSERT INTO %s %s", LOG_TABLE, toSQL(log.getContentValues()));
|
||||
}
|
||||
|
||||
public void clearLogs() {
|
||||
rawSQL("DELETE FROM %s", LOG_TABLE);
|
||||
}
|
||||
|
||||
public void setSettings(String key, int value) {
|
||||
ContentValues data = new ContentValues();
|
||||
data.put("key", key);
|
||||
data.put("value", value);
|
||||
rawSQL("REPLACE INTO %s %s", SETTINGS_TABLE, toSQL(data));
|
||||
}
|
||||
|
||||
public int getSettings(String key, int defaultValue) {
|
||||
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
|
||||
if (res.isEmpty())
|
||||
return defaultValue;
|
||||
return res.get(0).getAsInteger("value");
|
||||
}
|
||||
|
||||
public void setStrings(String key, String value) {
|
||||
if (value == null) {
|
||||
rawSQL("DELETE FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
||||
return;
|
||||
}
|
||||
ContentValues data = new ContentValues();
|
||||
data.put("key", key);
|
||||
data.put("value", value);
|
||||
rawSQL("REPLACE INTO %s %s", STRINGS_TABLE, toSQL(data));
|
||||
}
|
||||
|
||||
public String getStrings(String key, String defaultValue) {
|
||||
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
||||
if (res.isEmpty())
|
||||
return defaultValue;
|
||||
return res.get(0).getAsString("value");
|
||||
}
|
||||
}
|
@@ -1,113 +0,0 @@
|
||||
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.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
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 = 4;
|
||||
private static final String TABLE_NAME = "repos";
|
||||
|
||||
private SQLiteDatabase mDb;
|
||||
|
||||
public RepoDatabaseHelper(Context context) {
|
||||
super(context, "repo.db", null, DATABASE_VER);
|
||||
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) {
|
||||
if (oldVersion != newVersion) {
|
||||
// Nuke old DB and create new table
|
||||
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, last_update INT, PRIMARY KEY(id))");
|
||||
Config.remove(Config.Key.ETAG_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
onUpgrade(db, 0, DATABASE_VER);
|
||||
}
|
||||
|
||||
public void clearRepo() {
|
||||
mDb.delete(TABLE_NAME, null, null);
|
||||
}
|
||||
|
||||
|
||||
public void removeRepo(String id) {
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||
}
|
||||
|
||||
public void removeRepo(Repo repo) {
|
||||
removeRepo(repo.getId());
|
||||
}
|
||||
|
||||
public void removeRepo(Iterable<String> list) {
|
||||
for (String id : list) {
|
||||
if (id == null) continue;
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||
}
|
||||
}
|
||||
|
||||
public void addRepo(Repo repo) {
|
||||
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
||||
}
|
||||
|
||||
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 ((int) Config.get(Config.Key.REPO_ORDER)) {
|
||||
case Config.Value.ORDER_NAME:
|
||||
orderBy = "name COLLATE NOCASE";
|
||||
break;
|
||||
case Config.Value.ORDER_DATE:
|
||||
orderBy = "last_update DESC";
|
||||
}
|
||||
return mDb.query(TABLE_NAME, null, "minMagisk<=? AND minMagisk>=?",
|
||||
new String[] { String.valueOf(Config.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;
|
||||
}
|
||||
}
|
@@ -1,108 +0,0 @@
|
||||
package com.topjohnwu.magisk.tasks;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.net.Request;
|
||||
import com.topjohnwu.net.ResponseListener;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class CheckUpdates {
|
||||
|
||||
private static Request getRequest() {
|
||||
String url;
|
||||
switch ((int) Config.get(Config.Key.UPDATE_CHANNEL)) {
|
||||
case Config.Value.BETA_CHANNEL:
|
||||
url = Const.Url.BETA_URL;
|
||||
break;
|
||||
case Config.Value.CUSTOM_CHANNEL:
|
||||
url = Config.get(Config.Key.CUSTOM_CHANNEL);
|
||||
break;
|
||||
case Config.Value.STABLE_CHANNEL:
|
||||
default:
|
||||
url = Const.Url.STABLE_URL;
|
||||
break;
|
||||
}
|
||||
return Networking.get(url);
|
||||
}
|
||||
|
||||
public static void check() {
|
||||
getRequest().getAsJSONObject(new UpdateListener(null));
|
||||
}
|
||||
|
||||
public static void checkNow(Runnable cb) {
|
||||
JSONObject json = getRequest().execForJSONObject().getResult();
|
||||
if (json != null)
|
||||
new UpdateListener(cb).onResponse(json);
|
||||
}
|
||||
|
||||
private static class UpdateListener implements ResponseListener<JSONObject> {
|
||||
|
||||
private Runnable cb;
|
||||
private long start;
|
||||
|
||||
UpdateListener(Runnable callback) {
|
||||
cb = callback;
|
||||
start = SystemClock.uptimeMillis();
|
||||
}
|
||||
|
||||
private int getInt(JSONObject json, String name, int defValue) {
|
||||
if (json == null)
|
||||
return defValue;
|
||||
try {
|
||||
return json.getInt(name);
|
||||
} catch (JSONException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
private String getString(JSONObject json, String name, String defValue) {
|
||||
if (json == null)
|
||||
return defValue;
|
||||
try {
|
||||
return json.getString(name);
|
||||
} catch (JSONException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject getJson(JSONObject json, String name) {
|
||||
try {
|
||||
return json.getJSONObject(name);
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(JSONObject json) {
|
||||
JSONObject magisk = getJson(json, "magisk");
|
||||
Config.remoteMagiskVersionString = getString(magisk, "version", null);
|
||||
Config.remoteMagiskVersionCode = getInt(magisk, "versionCode", -1);
|
||||
Config.magiskLink = getString(magisk, "link", null);
|
||||
Config.magiskNoteLink = getString(magisk, "note", null);
|
||||
Config.magiskMD5 = getString(magisk, "md5", null);
|
||||
|
||||
JSONObject manager = getJson(json, "app");
|
||||
Config.remoteManagerVersionString = getString(manager, "version", null);
|
||||
Config.remoteManagerVersionCode = getInt(manager, "versionCode", -1);
|
||||
Config.managerLink = getString(manager, "link", null);
|
||||
Config.managerNoteLink = getString(manager, "note", null);
|
||||
|
||||
JSONObject uninstaller = getJson(json, "uninstaller");
|
||||
Config.uninstallerLink = getString(uninstaller, "link", null);
|
||||
|
||||
UiThreadHandler.handler.postAtTime(() -> Topic.publish(Topic.UPDATE_CHECK_DONE),
|
||||
start + 1000 /* Add artificial delay to let UI behave correctly */);
|
||||
|
||||
if (cb != null)
|
||||
cb.run();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,85 +0,0 @@
|
||||
package com.topjohnwu.magisk.tasks;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
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 abstract class FlashZip {
|
||||
|
||||
private Uri mUri;
|
||||
private File tmpFile;
|
||||
private List<String> console, logs;
|
||||
|
||||
public FlashZip(Uri uri, List<String> out, List<String> err) {
|
||||
mUri = uri;
|
||||
console = out;
|
||||
logs = err;
|
||||
tmpFile = new File(App.self.getCacheDir(), "install.zip");
|
||||
}
|
||||
|
||||
private boolean unzipAndCheck() throws IOException {
|
||||
ZipUtils.unzip(tmpFile, tmpFile.getParentFile(), "META-INF/com/google/android", true);
|
||||
return Shell.su("grep -q '#MAGISK' " + new File(tmpFile.getParentFile(), "updater-script"))
|
||||
.exec().isSuccess();
|
||||
}
|
||||
|
||||
private boolean flash() throws IOException {
|
||||
console.add("- Copying zip to temp directory");
|
||||
try (InputStream in = App.self.getContentResolver().openInputStream(mUri);
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(tmpFile))) {
|
||||
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;
|
||||
}
|
||||
try {
|
||||
if (!unzipAndCheck()) {
|
||||
console.add("! This zip is not a Magisk Module!");
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
console.add("! Unzip error");
|
||||
throw e;
|
||||
}
|
||||
console.add("- Installing " + Utils.getNameFromUri(App.self, mUri));
|
||||
return Shell.su("cd " + tmpFile.getParent(),
|
||||
"BOOTMODE=true sh update-binary dummy 1 " + tmpFile)
|
||||
.to(console, logs)
|
||||
.exec().isSuccess();
|
||||
}
|
||||
|
||||
public void exec() {
|
||||
App.THREAD_POOL.execute(() -> {
|
||||
boolean success = false;
|
||||
try {
|
||||
success = flash();
|
||||
} catch (IOException ignored) {}
|
||||
Shell.su("cd /", "rm -rf " + tmpFile.getParent() + " " + Const.TMP_FOLDER_PATH).submit();
|
||||
boolean finalSuccess = success;
|
||||
UiThreadHandler.run(() -> onResult(finalSuccess));
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract void onResult(boolean success);
|
||||
}
|
@@ -1,123 +0,0 @@
|
||||
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.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
|
||||
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 useFingerprint() {
|
||||
boolean fp = Config.get(Config.Key.SU_FINGERPRINT);
|
||||
if (fp && !canUseFingerprint()) {
|
||||
Config.set(Config.Key.SU_FINGERPRINT, false);
|
||||
fp = false;
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
public static boolean canUseFingerprint() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
return false;
|
||||
KeyguardManager km = App.self.getSystemService(KeyguardManager.class);
|
||||
FingerprintManager fm = App.self.getSystemService(FingerprintManager.class);
|
||||
return km.isKeyguardSecure() && fm != null && fm.isHardwareDetected() && fm.hasEnrolledFingerprints();
|
||||
}
|
||||
|
||||
protected FingerprintHelper() throws Exception {
|
||||
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
manager = App.self.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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,152 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.InternalUtils;
|
||||
|
||||
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 Locale forLanguageTag(String tag) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
return Locale.forLanguageTag(tag);
|
||||
} else {
|
||||
String[] tok = tag.split("-");
|
||||
if (tok.length == 0) {
|
||||
return new Locale("");
|
||||
}
|
||||
String language;
|
||||
switch (tok[0]) {
|
||||
case "und":
|
||||
language = ""; // Undefined
|
||||
break;
|
||||
case "fil":
|
||||
language = "tl"; // Filipino
|
||||
break;
|
||||
default:
|
||||
language = tok[0];
|
||||
}
|
||||
if ((language.length() != 2 && language.length() != 3))
|
||||
return new Locale("");
|
||||
if (tok.length == 1)
|
||||
return new Locale(language);
|
||||
String country = tok[1];
|
||||
if (country.length() != 2 && country.length() != 3)
|
||||
return new Locale(language);
|
||||
return new Locale(language, country);
|
||||
}
|
||||
}
|
||||
|
||||
public static String toLanguageTag(Locale loc) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
return loc.toLanguageTag();
|
||||
} else {
|
||||
String language = loc.getLanguage();
|
||||
String country = loc.getCountry();
|
||||
String variant = loc.getVariant();
|
||||
if (language.isEmpty() || !language.matches("\\p{Alpha}{2,8}")) {
|
||||
language = "und"; // Follow the Locale#toLanguageTag() implementation
|
||||
} else if (language.equals("iw")) {
|
||||
language = "he"; // correct deprecated "Hebrew"
|
||||
} else if (language.equals("in")) {
|
||||
language = "id"; // correct deprecated "Indonesian"
|
||||
} else if (language.equals("ji")) {
|
||||
language = "yi"; // correct deprecated "Yiddish"
|
||||
}
|
||||
// ensure valid country code, if not well formed, it's omitted
|
||||
if (!country.matches("\\p{Alpha}{2}|\\p{Digit}{3}")) {
|
||||
country = "";
|
||||
}
|
||||
|
||||
// variant subtags that begin with a letter must be at least 5 characters long
|
||||
if (!variant.matches("\\p{Alnum}{5,8}|\\p{Digit}\\p{Alnum}{3}")) {
|
||||
variant = "";
|
||||
}
|
||||
StringBuilder tag = new StringBuilder(language);
|
||||
if (!country.isEmpty())
|
||||
tag.append('-').append(country);
|
||||
if (!variant.isEmpty())
|
||||
tag.append('-').append(variant);
|
||||
return tag.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLocale(ContextWrapper wrapper) {
|
||||
String localeConfig = Config.get(Config.Key.LOCALE);
|
||||
if (localeConfig.isEmpty()) {
|
||||
locale = defaultLocale;
|
||||
} else {
|
||||
locale = forLanguageTag(localeConfig);
|
||||
}
|
||||
Locale.setDefault(locale);
|
||||
InternalUtils.replaceBaseContext(wrapper, getLocaleContext(locale));
|
||||
}
|
||||
|
||||
public static Context getLocaleContext(Context context, Locale locale) {
|
||||
if (Build.VERSION.SDK_INT >= 17) {
|
||||
Configuration config = new Configuration(context.getResources().getConfiguration());
|
||||
config.setLocale(locale);
|
||||
return context.createConfigurationContext(config);
|
||||
} else {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
public static Context getLocaleContext(Locale locale) {
|
||||
return getLocaleContext(App.self.getBaseContext(), locale);
|
||||
}
|
||||
|
||||
public static String getString(Locale locale, @StringRes int id) {
|
||||
return getLocaleContext(locale).getString(id);
|
||||
}
|
||||
|
||||
public static void loadAvailableLocales(@StringRes int compareId) {
|
||||
if (Build.VERSION.SDK_INT < 17)
|
||||
return;
|
||||
Shell.EXECUTOR.execute(() -> {
|
||||
locales = new ArrayList<>();
|
||||
HashSet<String> set = new HashSet<>();
|
||||
Resources res = App.self.getResources();
|
||||
Locale locale;
|
||||
|
||||
// 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 = 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);
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,42 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.core.R;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class RootUtils extends Shell.Initializer {
|
||||
|
||||
public static void rmAndLaunch(String rm, String launch) {
|
||||
Shell.su(Utils.fmt("(rm_launch %d %s %s)&", Const.USER_ID, rm, launch)).exec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInit(Context context, @NonNull Shell shell) {
|
||||
Shell.Job job = shell.newJob();
|
||||
if (shell.isRoot()) {
|
||||
job.add(context.getResources().openRawResource(R.raw.util_functions))
|
||||
.add(context.getResources().openRawResource(R.raw.utils));
|
||||
Const.MAGISK_DISABLE_FILE = new SuFile("/cache/.disable_magisk");
|
||||
Config.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();
|
||||
|
||||
Config.keepVerity = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPVERITY"));
|
||||
Config.keepEnc = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPFORCEENCRYPT"));
|
||||
Config.recovery = Boolean.parseBoolean(ShellUtils.fastCmd("echo $RECOVERYMODE"));
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,86 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public abstract class SuLogger {
|
||||
|
||||
public void handleLogs(Intent intent) {
|
||||
|
||||
int fromUid = intent.getIntExtra("from.uid", -1);
|
||||
if (fromUid < 0) return;
|
||||
if (fromUid == Process.myUid()) return;
|
||||
|
||||
App app = App.self;
|
||||
PackageManager pm = app.getPackageManager();
|
||||
Policy policy;
|
||||
|
||||
boolean notify;
|
||||
Bundle data = intent.getExtras();
|
||||
if (data.containsKey("notify")) {
|
||||
notify = data.getBoolean("notify");
|
||||
try {
|
||||
policy = new Policy(fromUid, pm);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Doesn't report whether notify or not, check database ourselves
|
||||
policy = app.mDB.getPolicy(fromUid);
|
||||
if (policy == null)
|
||||
return;
|
||||
notify = policy.notification;
|
||||
}
|
||||
|
||||
policy.policy = data.getInt("policy", -1);
|
||||
if (policy.policy < 0)
|
||||
return;
|
||||
|
||||
if (notify)
|
||||
handleNotify(policy);
|
||||
|
||||
SuLogEntry log = new SuLogEntry(policy);
|
||||
|
||||
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();
|
||||
app.mDB.addLog(log);
|
||||
}
|
||||
|
||||
private void handleNotify(Policy policy) {
|
||||
if (policy.notification &&
|
||||
(int) Config.get(Config.Key.SU_NOTIFICATION) == Config.Value.NOTIFICATION_TOAST)
|
||||
Utils.toast(getMessage(policy), Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
public void handleNotify(Intent intent) {
|
||||
int fromUid = intent.getIntExtra("from.uid", -1);
|
||||
if (fromUid < 0) return;
|
||||
if (fromUid == Process.myUid()) return;
|
||||
try {
|
||||
Policy policy = new Policy(fromUid, App.self.getPackageManager());
|
||||
policy.policy = intent.getIntExtra("policy", -1);
|
||||
if (policy.policy >= 0)
|
||||
handleNotify(policy);
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
}
|
||||
|
||||
public abstract String getMessage(Policy policy);
|
||||
}
|
@@ -1,108 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
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 LOCALE_FETCH_DONE = 4;
|
||||
|
||||
@IntDef({MAGISK_HIDE_DONE, MODULE_LOAD_DONE, REPO_LOAD_DONE,
|
||||
UPDATE_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[5];
|
||||
|
||||
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) {
|
||||
subscribe(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) {
|
||||
unsubscribe(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) {
|
||||
UiThreadHandler.run(() -> 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;
|
||||
}
|
||||
|
||||
public static boolean isPublished(AutoSubscriber sub) {
|
||||
return isPublished(sub.getSubscribedTopics());
|
||||
}
|
||||
|
||||
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 extends Subscriber {
|
||||
@TopicID
|
||||
int[] getSubscribedTopics();
|
||||
}
|
||||
}
|
@@ -1,127 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.container.ValueSortedMap;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static void toast(CharSequence msg, int duration) {
|
||||
UiThreadHandler.run(() -> Toast.makeText(App.self, msg, duration).show());
|
||||
}
|
||||
|
||||
public static void toast(int resId, int duration) {
|
||||
UiThreadHandler.run(() -> Toast.makeText(App.self, resId, duration).show());
|
||||
}
|
||||
|
||||
public static String dlString(String url) {
|
||||
String s = Networking.get(url).execForString().getResult();
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
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 = App.self.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 getAppLabel(ApplicationInfo info, PackageManager pm) {
|
||||
try {
|
||||
if (info.labelRes > 0 && Build.VERSION.SDK_INT >= 17) {
|
||||
Resources res = pm.getResourcesForApplication(info);
|
||||
Configuration config = new Configuration();
|
||||
config.setLocale(LocaleManager.locale);
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
return res.getString(info.labelRes);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
return info.loadLabel(pm).toString();
|
||||
}
|
||||
|
||||
public static String getLegalFilename(CharSequence filename) {
|
||||
return filename.toString().replace(" ", "_").replace("'", "").replace("\"", "")
|
||||
.replace("$", "").replace("`", "").replace("*", "").replace("/", "_")
|
||||
.replace("#", "").replace("@", "").replace("\\", "_");
|
||||
}
|
||||
|
||||
public static void loadModules() {
|
||||
Topic.reset(Topic.MODULE_LOAD_DONE);
|
||||
App.THREAD_POOL.execute(() -> {
|
||||
Map<String, Module> moduleMap = new ValueSortedMap<>();
|
||||
SuFile path = new SuFile(Const.MAGISK_PATH);
|
||||
SuFile[] modules = path.listFiles(
|
||||
(file, name) -> !name.equals("lost+found") && !name.equals(".core"));
|
||||
for (SuFile file : modules) {
|
||||
if (file.isFile()) continue;
|
||||
Module module = new Module(Const.MAGISK_PATH + "/" + file.getName());
|
||||
moduleMap.put(module.getId(), module);
|
||||
}
|
||||
Topic.publish(Topic.MODULE_LOAD_DONE, moduleMap);
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean showSuperUser() {
|
||||
return Shell.rootAccess() && (Const.USER_ID == 0 ||
|
||||
(int) Config.get(Config.Key.SU_MULTIUSER_MODE) !=
|
||||
Config.Value.MULTIUSER_MODE_OWNER_MANAGED);
|
||||
}
|
||||
|
||||
public static Context getDEContext() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
|
||||
App.self.createDeviceProtectedStorageContext() : App.self;
|
||||
}
|
||||
|
||||
public static void reboot() {
|
||||
Shell.su("/system/bin/reboot" + (Config.recovery ? " recovery" : "")).submit();
|
||||
}
|
||||
}
|
@@ -1,65 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import com.topjohnwu.signing.JarMap;
|
||||
import com.topjohnwu.signing.SignAPK;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
# 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.
|
141
app/build.gradle
141
app/build.gradle
@@ -1,81 +1,114 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
def configProps = new Properties()
|
||||
def configPath = project.hasProperty('configPath') ? project.configPath : rootProject.file('config.prop')
|
||||
configProps.load(new FileInputStream(configPath))
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
useBuildCache = true
|
||||
mapDiagnosticLocations = true
|
||||
javacOptions {
|
||||
option("-Xmaxerrs", 1000)
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
applicationId 'com.topjohnwu.magisk'
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
config {
|
||||
storeFile rootProject.file('release-key.jks')
|
||||
storePassword configProps['keyStorePass']
|
||||
keyAlias configProps['keyAlias']
|
||||
keyPassword configProps['keyPass']
|
||||
}
|
||||
multiDexEnabled true
|
||||
versionName configProps['appVersion']
|
||||
versionCode configProps['appVersionCode'] as Integer
|
||||
}
|
||||
|
||||
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
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
|
||||
'proguard-rules.pro', 'proguard-kotlin.pro'
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions 'mode'
|
||||
|
||||
productFlavors {
|
||||
full {
|
||||
versionName configProps['appVersion']
|
||||
versionCode configProps['appVersionCode'] as Integer
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
argument('butterknife.debuggable', 'false')
|
||||
}
|
||||
}
|
||||
}
|
||||
stub {
|
||||
versionCode 1
|
||||
versionName 'stub'
|
||||
}
|
||||
dataBinding {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
packagingOptions {
|
||||
exclude '/META-INF/*.version'
|
||||
exclude '/META-INF/*.kotlin_module'
|
||||
exclude '/META-INF/rxkotlin.properties'
|
||||
exclude '/androidsupportmultidexversion.txt'
|
||||
exclude '/org/**'
|
||||
exclude '/kotlin/**'
|
||||
exclude '/kotlinx/**'
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
androidExtensions {
|
||||
experimental = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation project(':net')
|
||||
fullImplementation project(':app-core')
|
||||
fullImplementation 'ru.noties:markwon:2.0.1'
|
||||
fullImplementation 'com.caverock:androidsvg-aar:1.3'
|
||||
implementation project(':shared')
|
||||
implementation project(':signing')
|
||||
|
||||
def androidXVersion = "1.0.0"
|
||||
implementation 'androidx.core:core:1.0.1'
|
||||
fullImplementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
fullImplementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
fullImplementation "androidx.preference:preference:${androidXVersion}"
|
||||
fullImplementation "androidx.recyclerview:recyclerview:${androidXVersion}"
|
||||
fullImplementation "androidx.cardview:cardview:${androidXVersion}"
|
||||
fullImplementation "com.google.android.material:material:${androidXVersion}"
|
||||
fullImplementation 'android.arch.work:work-runtime:1.0.0-beta03'
|
||||
fullImplementation 'androidx.room:room-runtime:2.0.0'
|
||||
fullImplementation 'androidx.transition:transition:1.0.1'
|
||||
implementation 'com.github.topjohnwu:jtar:1.0.0'
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
implementation 'com.github.skoumalcz:teanity:0.3.3'
|
||||
implementation 'com.ncapdevi:frag-nav:3.2.0'
|
||||
|
||||
def butterKnifeVersion = '10.0.0'
|
||||
fullImplementation "com.jakewharton:butterknife-runtime:${butterKnifeVersion}"
|
||||
fullAnnotationProcessor "com.jakewharton:butterknife-compiler:${butterKnifeVersion}"
|
||||
def vMarkwon = '3.0.1'
|
||||
implementation "ru.noties.markwon:core:${vMarkwon}"
|
||||
implementation "ru.noties.markwon:html:${vMarkwon}"
|
||||
implementation "ru.noties.markwon:image-svg:${vMarkwon}"
|
||||
|
||||
def vLibsu = '2.5.0'
|
||||
implementation "com.github.topjohnwu.libsu:core:${vLibsu}"
|
||||
implementation "com.github.topjohnwu.libsu:io:${vLibsu}"
|
||||
|
||||
def vKoin = "2.0.1"
|
||||
implementation "org.koin:koin-core:${vKoin}"
|
||||
implementation "org.koin:koin-android:${vKoin}"
|
||||
implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
|
||||
|
||||
def vRetrofit = "2.6.0"
|
||||
implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
|
||||
implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
|
||||
implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
|
||||
|
||||
def vOkHttp = "3.12.3"
|
||||
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
|
||||
|
||||
def vMoshi = "1.8.0"
|
||||
implementation "com.squareup.moshi:moshi:${vMoshi}"
|
||||
|
||||
def vKotshi = "2.0.1"
|
||||
implementation "se.ansman.kotshi:api:${vKotshi}"
|
||||
kapt "se.ansman.kotshi:compiler:${vKotshi}"
|
||||
|
||||
modules {
|
||||
module('androidx.room:room-runtime') {
|
||||
replacedBy('com.github.topjohnwu:room-runtime')
|
||||
}
|
||||
}
|
||||
def vRoom = "2.1.0"
|
||||
implementation "com.github.topjohnwu:room-runtime:${vRoom}"
|
||||
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.browser:browser:1.0.0'
|
||||
implementation 'androidx.preference:preference:1.0.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha06'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.work:work-runtime:2.0.1'
|
||||
implementation 'androidx.transition:transition:1.2.0-alpha01'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'com.google.android.material:material:1.1.0-alpha07'
|
||||
}
|
||||
|
20
app/proguard-kotlin.pro
Normal file
20
app/proguard-kotlin.pro
Normal file
@@ -0,0 +1,20 @@
|
||||
## So every class is case insensitive to avoid some bizare problems
|
||||
-dontusemixedcaseclassnames
|
||||
|
||||
## If reflection issues come up uncomment this, that should temporarily fix it
|
||||
#-keep class kotlin.** { *; }
|
||||
#-keep class kotlin.Metadata { *; }
|
||||
#-keepclassmembers class kotlin.Metadata {
|
||||
# public <methods>;
|
||||
#}
|
||||
|
||||
## Never warn about Kotlin, it should work as-is
|
||||
-dontwarn kotlin.**
|
||||
|
||||
## Removes runtime null checks - doesn't really matter if it crashes on kotlin or java NPE
|
||||
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
||||
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
|
||||
}
|
||||
|
||||
## Useless option for dex
|
||||
-dontpreverify
|
26
app/proguard-rules.pro
vendored
26
app/proguard-rules.pro
vendored
@@ -16,12 +16,6 @@
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# BouncyCastle
|
||||
-keep,allowoptimization class org.bouncycastle.jcajce.provider.asymmetric.rsa.**SHA1** { *; }
|
||||
-keep,allowoptimization class org.bouncycastle.jcajce.provider.asymmetric.RSA** { *; }
|
||||
-keep,allowoptimization class org.bouncycastle.jcajce.provider.digest.SHA1** { *; }
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
# Snet
|
||||
-keepclassmembers class com.topjohnwu.magisk.utils.ISafetyNetHelper { *; }
|
||||
-keep,allowobfuscation interface com.topjohnwu.magisk.utils.ISafetyNetHelper$Callback
|
||||
@@ -29,13 +23,19 @@
|
||||
void onResponse(int);
|
||||
}
|
||||
|
||||
# BootSigner
|
||||
-keepclassmembers class com.topjohnwu.signer.BootSigner { *; }
|
||||
# Keep all fragment constructors
|
||||
-keepclassmembers class * extends androidx.fragment.app.Fragment {
|
||||
public <init>(...);
|
||||
}
|
||||
|
||||
# SVG
|
||||
-dontwarn com.caverock.androidsvg.SVGAndroidRenderer
|
||||
# DelegateWorker
|
||||
-keep,allowobfuscation class * extends com.topjohnwu.magisk.model.worker.DelegateWorker
|
||||
|
||||
# BootSigner
|
||||
-keepclassmembers class com.topjohnwu.signing.BootSigner { *; }
|
||||
|
||||
# Strip logging
|
||||
-assumenosideeffects class timber.log.Timber.Tree { *; }
|
||||
-assumenosideeffects class com.topjohnwu.magisk.utils.Logger {
|
||||
public *** debug(...);
|
||||
}
|
||||
@@ -43,4 +43,8 @@
|
||||
# Excessive obfuscation
|
||||
-repackageclasses 'a'
|
||||
-allowaccessmodification
|
||||
-optimizationpasses 6
|
||||
|
||||
# QOL
|
||||
-dontnote **
|
||||
-dontwarn com.caverock.androidsvg.**
|
||||
-dontwarn ru.noties.markwon.**
|
||||
|
@@ -1,75 +0,0 @@
|
||||
<?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" />
|
||||
|
||||
<application
|
||||
android:name="a.e"
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:ignore="UnusedAttribute,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.f"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:screenOrientation="nosensor"
|
||||
android:theme="@style/AppTheme.NoDrawer" />
|
||||
|
||||
<!-- Superuser -->
|
||||
|
||||
<activity
|
||||
android:name="a.m"
|
||||
android:directBootAware="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="a.m"
|
||||
android:theme="@style/SuRequest" />
|
||||
|
||||
<!-- Receiver -->
|
||||
|
||||
<receiver
|
||||
android:name="a.h"
|
||||
android:directBootAware="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
</intent-filter>
|
||||
<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>
|
||||
|
||||
<!-- Service -->
|
||||
|
||||
<service android:name="a.j" />
|
||||
|
||||
<!-- Hardcode GMS version -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="12451000" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
@@ -1,27 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import com.topjohnwu.magisk.components.DownloadModuleService;
|
||||
import com.topjohnwu.magisk.components.GeneralReceiver;
|
||||
import com.topjohnwu.magisk.components.UpdateCheckService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ClassMap {
|
||||
private static Map<Class, Class> classMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
classMap.put(App.class, a.e.class);
|
||||
classMap.put(MainActivity.class, a.b.class);
|
||||
classMap.put(SplashActivity.class, a.c.class);
|
||||
classMap.put(FlashActivity.class, a.f.class);
|
||||
classMap.put(UpdateCheckService.class, a.g.class);
|
||||
classMap.put(GeneralReceiver.class, a.h.class);
|
||||
classMap.put(DownloadModuleService.class, a.j.class);
|
||||
classMap.put(SuRequestActivity.class, a.m.class);
|
||||
}
|
||||
|
||||
public static <T> Class<T> get(Class c) {
|
||||
return classMap.get(c);
|
||||
}
|
||||
}
|
@@ -1,270 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.adapters.StringListAdapter;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.tasks.FlashZip;
|
||||
import com.topjohnwu.magisk.tasks.MagiskInstaller;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.CallbackList;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindColor;
|
||||
import butterknife.BindView;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class FlashActivity extends BaseActivity {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.button_panel) LinearLayout buttonPanel;
|
||||
@BindView(R.id.reboot) Button reboot;
|
||||
@BindView(R.id.recyclerView) RecyclerView rv;
|
||||
@BindColor(android.R.color.white) int white;
|
||||
|
||||
private List<String> console, logs;
|
||||
|
||||
@OnClick(R.id.reboot)
|
||||
void reboot() {
|
||||
Utils.reboot();
|
||||
}
|
||||
|
||||
@OnClick(R.id.save_logs)
|
||||
void saveLogs() {
|
||||
runWithExternalRW(() -> {
|
||||
Calendar now = Calendar.getInstance();
|
||||
String filename = String.format(Locale.US,
|
||||
"magisk_install_log_%04d%02d%02d_%02d%02d%02d.log",
|
||||
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
|
||||
|
||||
File logFile = new File(Const.EXTERNAL_PATH, 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);
|
||||
});
|
||||
}
|
||||
|
||||
@OnClick(R.id.close)
|
||||
public void close() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Prevent user accidentally press back button
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_NoDrawer_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_flash);
|
||||
new FlashActivity_ViewBinding(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 = Collections.synchronizedList(new ArrayList<>());
|
||||
console = new ConsoleList();
|
||||
rv.setAdapter(new ConsoleAdapter());
|
||||
|
||||
Intent intent = getIntent();
|
||||
Uri uri = intent.getData();
|
||||
|
||||
switch (intent.getStringExtra(Const.Key.FLASH_ACTION)) {
|
||||
case Const.Value.FLASH_ZIP:
|
||||
new FlashModule(uri).exec();
|
||||
break;
|
||||
case Const.Value.UNINSTALL:
|
||||
new Uninstall(uri).exec();
|
||||
break;
|
||||
case Const.Value.FLASH_MAGISK:
|
||||
new DirectInstall().exec();
|
||||
break;
|
||||
case Const.Value.FLASH_INACTIVE_SLOT:
|
||||
new SecondSlot().exec();
|
||||
break;
|
||||
case Const.Value.PATCH_BOOT:
|
||||
new PatchBoot(uri).exec();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private class ConsoleAdapter extends StringListAdapter<ConsoleAdapter.ViewHolder> {
|
||||
|
||||
ConsoleAdapter() {
|
||||
super(console, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int itemLayoutRes() {
|
||||
return R.layout.list_item_console;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder createViewHolder(@NonNull View v) {
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
class ViewHolder extends StringListAdapter.ViewHolder {
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
txt.setTextColor(white);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int textViewResId() {
|
||||
return R.id.txt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ConsoleList extends CallbackList<String> {
|
||||
|
||||
ConsoleList() {
|
||||
super(new ArrayList<>());
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
rv.getAdapter().notifyItemChanged(size() - 1);
|
||||
rv.postDelayed(() -> rv.smoothScrollToPosition(size() - 1), 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);
|
||||
UiThreadHandler.run(this::updateUI);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
private class FlashModule extends FlashZip {
|
||||
|
||||
FlashModule(Uri uri) {
|
||||
super(uri, console, logs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResult(boolean success) {
|
||||
if (success) {
|
||||
Utils.loadModules();
|
||||
} else {
|
||||
console.add("! Installation failed");
|
||||
reboot.setVisibility(View.GONE);
|
||||
}
|
||||
buttonPanel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private class Uninstall extends FlashModule {
|
||||
|
||||
Uninstall(Uri uri) {
|
||||
super(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResult(boolean success) {
|
||||
if (success)
|
||||
UiThreadHandler.handler.postDelayed(Shell.su("pm uninstall " + getPackageName())::exec, 3000);
|
||||
else
|
||||
super.onResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class BaseInstaller extends MagiskInstaller {
|
||||
BaseInstaller() {
|
||||
super(console, logs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResult(boolean success) {
|
||||
if (success) {
|
||||
console.add("- All done!");
|
||||
} else {
|
||||
Shell.sh("rm -rf " + installDir).submit();
|
||||
console.add("! Installation failed");
|
||||
reboot.setVisibility(View.GONE);
|
||||
}
|
||||
buttonPanel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private class DirectInstall extends BaseInstaller {
|
||||
|
||||
@Override
|
||||
protected boolean operations() {
|
||||
return findImage() && extractZip() && patchBoot() && flashBoot();
|
||||
}
|
||||
}
|
||||
|
||||
private class SecondSlot extends BaseInstaller {
|
||||
|
||||
@Override
|
||||
protected boolean operations() {
|
||||
return findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA();
|
||||
}
|
||||
}
|
||||
|
||||
private class PatchBoot extends BaseInstaller {
|
||||
|
||||
private Uri uri;
|
||||
|
||||
PatchBoot(Uri u) {
|
||||
uri = u;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean operations() {
|
||||
return copyBoot(uri) && extractZip() && patchBoot() && storeBoot();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,201 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
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.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
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;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class MainActivity extends BaseActivity
|
||||
implements NavigationView.OnNavigationItemSelectedListener, Topic.Subscriber {
|
||||
|
||||
private final Handler mDrawerHandler = new Handler();
|
||||
private int mDrawerItem;
|
||||
private static boolean fromShortcut = false;
|
||||
|
||||
@BindView(R.id.toolbar) public Toolbar toolbar;
|
||||
@BindView(R.id.drawer_layout) DrawerLayout drawer;
|
||||
@BindView(R.id.nav_view) NavigationView navigationView;
|
||||
|
||||
private float toolbarElevation;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
if (!getIntent().getBooleanExtra(Const.Key.FROM_SPLASH, false)) {
|
||||
startActivity(new Intent(this, ClassMap.get(SplashActivity.class)));
|
||||
finish();
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
new MainActivity_ViewBinding(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
|
||||
}
|
||||
};
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
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() &&
|
||||
(boolean) Config.get(Config.Key.MAGISKHIDE));
|
||||
menu.findItem(R.id.modules).setVisible(Shell.rootAccess() && Config.magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.downloads).setVisible(Networking.checkNetworkStatus(this)
|
||||
&& Shell.rootAccess() && Config.magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.log).setVisible(Shell.rootAccess());
|
||||
menu.findItem(R.id.superuser).setVisible(Utils.showSuperUser());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
navigate(itemId);
|
||||
}
|
||||
|
||||
public void navigate(int itemId) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void displayFragment(@NonNull Fragment navFragment, boolean setElevation) {
|
||||
supportInvalidateOptionsMenu();
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
.replace(R.id.content_frame, navFragment)
|
||||
.commitNow();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
toolbar.setElevation(setElevation ? toolbarElevation : 0);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,90 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.tasks.UpdateRepos;
|
||||
import com.topjohnwu.magisk.uicomponents.Notifications;
|
||||
import com.topjohnwu.magisk.uicomponents.Shortcuts;
|
||||
import com.topjohnwu.magisk.utils.AppUtils;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public class SplashActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Shell.getShell(shell -> {
|
||||
if (Config.magiskVersionCode > 0 &&
|
||||
Config.magiskVersionCode < Const.MAGISK_VER.MIN_SUPPORT) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.unsupport_magisk_title)
|
||||
.setMessage(R.string.unsupport_magisk_message)
|
||||
.setNegativeButton(R.string.ok, null)
|
||||
.setOnDismissListener(dialog -> finish())
|
||||
.show();
|
||||
} else {
|
||||
initAndStart();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initAndStart() {
|
||||
String pkg = Config.get(Config.Key.SU_MANAGER);
|
||||
if (pkg != null && getPackageName().equals(BuildConfig.APPLICATION_ID)) {
|
||||
Config.remove(Config.Key.SU_MANAGER);
|
||||
Shell.su("pm uninstall " + pkg).submit();
|
||||
}
|
||||
if (TextUtils.equals(pkg, getPackageName())) {
|
||||
try {
|
||||
// We are the manager, remove com.topjohnwu.magisk as it could be malware
|
||||
getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
|
||||
Shell.su("pm uninstall " + BuildConfig.APPLICATION_ID).submit();
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
}
|
||||
|
||||
// Dynamic detect all locales
|
||||
LocaleManager.loadAvailableLocales(R.string.app_changelog);
|
||||
|
||||
// Set default configs
|
||||
Config.initialize();
|
||||
|
||||
// Create notification channel on Android O
|
||||
Notifications.setup(this);
|
||||
|
||||
// Schedule periodic update checks
|
||||
AppUtils.scheduleUpdateCheck();
|
||||
|
||||
// Setup shortcuts
|
||||
Shortcuts.setup(this);
|
||||
|
||||
// Create repo database
|
||||
app.repoDB = new RepoDatabaseHelper(this);
|
||||
|
||||
// Magisk working as expected
|
||||
if (Shell.rootAccess() && Config.magiskVersionCode > 0) {
|
||||
// Load modules
|
||||
Utils.loadModules();
|
||||
// Load repos
|
||||
if (Networking.checkNetworkStatus(this))
|
||||
new UpdateRepos().exec();
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, ClassMap.get(MainActivity.class));
|
||||
intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION));
|
||||
intent.putExtra(Const.Key.FROM_SPLASH, true);
|
||||
intent.putExtra(BaseActivity.INTENT_PERM, getIntent().getStringExtra(BaseActivity.INTENT_PERM));
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
@@ -1,235 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
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 com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class SuRequestActivity extends BaseActivity {
|
||||
@BindView(R.id.su_popup) LinearLayout suPopup;
|
||||
@BindView(R.id.timeout) Spinner timeout;
|
||||
@BindView(R.id.app_icon) ImageView appIcon;
|
||||
@BindView(R.id.app_name) TextView appNameView;
|
||||
@BindView(R.id.package_name) TextView packageNameView;
|
||||
@BindView(R.id.grant_btn) Button grant_btn;
|
||||
@BindView(R.id.deny_btn) Button deny_btn;
|
||||
@BindView(R.id.fingerprint) ImageView fingerprintImg;
|
||||
@BindView(R.id.warning) TextView warning;
|
||||
|
||||
private SuConnector connector;
|
||||
private Policy policy;
|
||||
private CountDownTimer timer;
|
||||
private FingerprintHelper fingerprintHelper;
|
||||
private SharedPreferences timeoutPrefs;
|
||||
|
||||
@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();
|
||||
app.mDB.clearOutdated();
|
||||
timeoutPrefs = Utils.getDEContext().getSharedPreferences("su_timeout", 0);
|
||||
|
||||
// Get policy
|
||||
Intent intent = getIntent();
|
||||
try {
|
||||
String socketName = intent.getStringExtra("socket");
|
||||
connector = new SuConnector(socketName) {
|
||||
@Override
|
||||
protected void onResponse() throws IOException {
|
||||
out.writeInt(policy.policy);
|
||||
}
|
||||
};
|
||||
Bundle bundle = connector.readSocketInput();
|
||||
int uid = Integer.parseInt(bundle.getString("uid"));
|
||||
policy = app.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, BuildConfig.APPLICATION_ID)) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
switch ((int) Config.get(Config.Key.SU_AUTO_RESPONSE)) {
|
||||
case Config.Value.SU_AUTO_DENY:
|
||||
handleAction(Policy.DENY, 0);
|
||||
return;
|
||||
case Config.Value.SU_AUTO_ALLOW:
|
||||
handleAction(Policy.ALLOW, 0);
|
||||
return;
|
||||
case Config.Value.SU_PROMPT:
|
||||
default:
|
||||
}
|
||||
|
||||
// If not interactive, response directly
|
||||
if (policy.policy != Policy.INTERACTIVE) {
|
||||
handleAction();
|
||||
return;
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_request);
|
||||
new SuRequestActivity_ViewBinding(this);
|
||||
|
||||
appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
||||
appNameView.setText(policy.appName);
|
||||
packageNameView.setText(policy.packageName);
|
||||
if (Build.VERSION.SDK_INT >= 17) {
|
||||
warning.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
AppCompatResources.getDrawable(this, R.drawable.ic_warning), null, null, null);
|
||||
} else {
|
||||
warning.setCompoundDrawablesWithIntrinsicBounds(
|
||||
AppCompatResources.getDrawable(this, R.drawable.ic_warning), null, null, null);
|
||||
}
|
||||
|
||||
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);
|
||||
timeout.setSelection(timeoutPrefs.getInt(policy.packageName, 0));
|
||||
|
||||
timer = new CountDownTimer((int) Config.get(Config.Key.SU_REQUEST_TIMEOUT) * 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 useFP = FingerprintHelper.useFingerprint();
|
||||
|
||||
if (useFP) {
|
||||
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();
|
||||
useFP = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!useFP) {
|
||||
grant_btn.setOnClickListener(v -> {
|
||||
handleAction(Policy.ALLOW);
|
||||
timer.cancel();
|
||||
});
|
||||
grant_btn.requestFocus();
|
||||
}
|
||||
|
||||
grant_btn.setVisibility(useFP ? View.GONE : View.VISIBLE);
|
||||
fingerprintImg.setVisibility(useFP ? 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) {
|
||||
int pos = timeout.getSelectedItemPosition();
|
||||
timeoutPrefs.edit().putInt(policy.packageName, pos).apply();
|
||||
handleAction(action, Config.Value.TIMEOUT_LIST[pos]);
|
||||
}
|
||||
|
||||
private void handleAction(int action, int time) {
|
||||
policy.policy = action;
|
||||
if (time >= 0) {
|
||||
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
|
||||
app.mDB.updatePolicy(policy);
|
||||
}
|
||||
handleAction();
|
||||
}
|
||||
}
|
@@ -1,169 +0,0 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.AsyncTask;
|
||||
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.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> {
|
||||
|
||||
private static PackageInfo PLATFORM;
|
||||
|
||||
private List<PackageInfo> fullList, showList;
|
||||
private List<String> hideList;
|
||||
private PackageManager pm;
|
||||
private boolean showSystem;
|
||||
|
||||
public ApplicationAdapter(Context context) {
|
||||
fullList = showList = Collections.emptyList();
|
||||
hideList = Collections.emptyList();
|
||||
pm = context.getPackageManager();
|
||||
showSystem = false;
|
||||
if (PLATFORM == null) {
|
||||
try {
|
||||
PLATFORM = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES);
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
}
|
||||
AsyncTask.SERIAL_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);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void loadApps() {
|
||||
fullList = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
|
||||
hideList = Shell.su("magiskhide --ls").exec().getOut();
|
||||
for (Iterator<PackageInfo> i = fullList.iterator(); i.hasNext(); ) {
|
||||
PackageInfo info = i.next();
|
||||
if (Const.HIDE_BLACKLIST.contains(info.packageName) ||
|
||||
/* Do not show disabled apps */
|
||||
!info.applicationInfo.enabled ||
|
||||
/* Never show platform apps */
|
||||
PLATFORM.signatures[0].equals(info.signatures[0])) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
Collections.sort(fullList, (a, b) -> {
|
||||
boolean ah = hideList.contains(a.packageName);
|
||||
boolean bh = hideList.contains(b.packageName);
|
||||
if (ah == bh) {
|
||||
return Utils.getAppLabel(a.applicationInfo, pm)
|
||||
.compareToIgnoreCase(Utils.getAppLabel(b.applicationInfo, pm));
|
||||
} else if (ah) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
Topic.publish(false, Topic.MAGISK_HIDE_DONE);
|
||||
}
|
||||
|
||||
public void setShowSystem(boolean b) {
|
||||
showSystem = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
ApplicationInfo info = showList.get(position).applicationInfo;
|
||||
|
||||
holder.appIcon.setImageDrawable(info.loadIcon(pm));
|
||||
holder.appName.setText(Utils.getAppLabel(info, pm));
|
||||
holder.appPackage.setText(info.packageName);
|
||||
|
||||
holder.checkBox.setOnCheckedChangeListener(null);
|
||||
holder.checkBox.setChecked(hideList.contains(info.packageName));
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if (isChecked) {
|
||||
Shell.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();
|
||||
}
|
||||
|
||||
private boolean contains(String s, String filter) {
|
||||
return s.toLowerCase().contains(filter);
|
||||
}
|
||||
|
||||
// Show if have launch intent or not system app
|
||||
private boolean systemFilter(PackageInfo info) {
|
||||
if (showSystem)
|
||||
return true;
|
||||
return (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
|
||||
pm.getLaunchIntentForPackage(info.packageName) != null;
|
||||
}
|
||||
|
||||
public void filter(String constraint) {
|
||||
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
|
||||
showList = new ArrayList<>();
|
||||
if (constraint == null || constraint.length() == 0) {
|
||||
for (PackageInfo info : fullList) {
|
||||
if (systemFilter(info))
|
||||
showList.add(info);
|
||||
}
|
||||
} else {
|
||||
String filter = constraint.toLowerCase();
|
||||
for (PackageInfo info : fullList) {
|
||||
if ((contains(Utils.getAppLabel(info.applicationInfo, pm), filter) ||
|
||||
contains(info.packageName, filter)) && systemFilter(info)) {
|
||||
showList.add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
UiThreadHandler.run(this::notifyDataSetChanged);
|
||||
});
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
AsyncTask.SERIAL_EXECUTOR.execute(this::loadApps);
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.app_icon) ImageView appIcon;
|
||||
@BindView(R.id.app_name) TextView appName;
|
||||
@BindView(R.id.package_name) TextView appPackage;
|
||||
@BindView(R.id.checkbox) CheckBox checkBox;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
new ApplicationAdapter$ViewHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,126 +0,0 @@
|
||||
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.container.Module;
|
||||
import com.topjohnwu.magisk.uicomponents.SnackbarMaker;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHolder> {
|
||||
|
||||
private final List<Module> mList;
|
||||
|
||||
public ModulesAdapter(List<Module> list) {
|
||||
mList = list;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@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();
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.title) TextView title;
|
||||
@BindView(R.id.version_name) TextView versionName;
|
||||
@BindView(R.id.description) TextView description;
|
||||
@BindView(R.id.notice) TextView notice;
|
||||
@BindView(R.id.checkbox) CheckBox checkBox;
|
||||
@BindView(R.id.author) TextView author;
|
||||
@BindView(R.id.delete) ImageView delete;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
new ModulesAdapter$ViewHolder_ViewBinding(this, itemView);
|
||||
|
||||
if (!Shell.rootAccess()) {
|
||||
checkBox.setEnabled(false);
|
||||
delete.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,161 +0,0 @@
|
||||
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.TextView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.database.MagiskDB;
|
||||
import com.topjohnwu.magisk.dialogs.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.dialogs.FingerprintAuthDialog;
|
||||
import com.topjohnwu.magisk.uicomponents.ArrowExpandedViewHolder;
|
||||
import com.topjohnwu.magisk.uicomponents.ExpandableViewHolder;
|
||||
import com.topjohnwu.magisk.uicomponents.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder> {
|
||||
|
||||
private List<Policy> policyList;
|
||||
private MagiskDB dbHelper;
|
||||
private PackageManager pm;
|
||||
private boolean[] expandList;
|
||||
|
||||
public PolicyAdapter(List<Policy> list, MagiskDB db, PackageManager pm) {
|
||||
policyList = list;
|
||||
expandList = new boolean[policyList.size()];
|
||||
dbHelper = db;
|
||||
this.pm = pm;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@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.settings.setExpanded(expandList[position]);
|
||||
holder.trigger.setOnClickListener(view -> {
|
||||
if (holder.settings.isExpanded()) {
|
||||
holder.settings.collapse();
|
||||
expandList[position] = false;
|
||||
} else {
|
||||
holder.settings.expand();
|
||||
expandList[position] = true;
|
||||
}
|
||||
});
|
||||
|
||||
holder.appName.setText(policy.appName);
|
||||
holder.packageName.setText(policy.packageName);
|
||||
holder.appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
||||
|
||||
holder.notificationSwitch.setOnCheckedChangeListener(null);
|
||||
holder.loggingSwitch.setOnCheckedChangeListener(null);
|
||||
|
||||
holder.masterSwitch.setChecked(policy.policy == Policy.ALLOW);
|
||||
holder.notificationSwitch.setChecked(policy.notification);
|
||||
holder.loggingSwitch.setChecked(policy.logging);
|
||||
|
||||
holder.masterSwitch.setOnClickListener(v -> {
|
||||
boolean isChecked = holder.masterSwitch.isChecked();
|
||||
Runnable r = () -> {
|
||||
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);
|
||||
}
|
||||
};
|
||||
if (FingerprintHelper.useFingerprint()) {
|
||||
holder.masterSwitch.setChecked(!isChecked);
|
||||
new FingerprintAuthDialog((Activity) v.getContext(), () -> {
|
||||
holder.masterSwitch.setChecked(isChecked);
|
||||
r.run();
|
||||
}).show();
|
||||
} else {
|
||||
r.run();
|
||||
}
|
||||
});
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return policyList.size();
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.app_name) TextView appName;
|
||||
@BindView(R.id.package_name) TextView packageName;
|
||||
@BindView(R.id.app_icon) ImageView appIcon;
|
||||
@BindView(R.id.master_switch) SwitchCompat masterSwitch;
|
||||
@BindView(R.id.notification_switch) SwitchCompat notificationSwitch;
|
||||
@BindView(R.id.logging_switch) SwitchCompat loggingSwitch;
|
||||
@BindView(R.id.expand_layout) ViewGroup expandLayout;
|
||||
@BindView(R.id.arrow) ImageView arrow;
|
||||
@BindView(R.id.trigger) View trigger;
|
||||
@BindView(R.id.delete) ImageView delete;
|
||||
@BindView(R.id.more_info) ImageView moreInfo;
|
||||
|
||||
ExpandableViewHolder settings;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
new PolicyAdapter$ViewHolder_ViewBinding(this, itemView);
|
||||
settings = new ArrowExpandedViewHolder(expandLayout, arrow);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,205 +0,0 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Build;
|
||||
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.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.DownloadModuleService;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.dialogs.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.uicomponents.MarkDownWindow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
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 ->
|
||||
MarkDownWindow.show((BaseActivity) context, null, repo.getDetailUrl()));
|
||||
|
||||
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) ->
|
||||
startDownload((BaseActivity) context, repo, true))
|
||||
.setNeutralButton(R.string.download, (d, i) ->
|
||||
startDownload((BaseActivity) context, repo, false))
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
private void startDownload(BaseActivity activity, Repo repo, Boolean install) {
|
||||
activity.runWithExternalRW(() -> {
|
||||
Intent intent = new Intent(activity, ClassMap.get(DownloadModuleService.class))
|
||||
.putExtra("repo", repo).putExtra("install", install);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
activity.startForegroundService(intent);
|
||||
} else {
|
||||
activity.startService(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void notifyDBChanged() {
|
||||
if (repoCursor != null)
|
||||
repoCursor.close();
|
||||
repoCursor = repoDB.getRepoCursor();
|
||||
filter("");
|
||||
}
|
||||
|
||||
public void filter(String s) {
|
||||
List<Repo> updates = new ArrayList<>();
|
||||
List<Repo> installed = new ArrayList<>();
|
||||
List<Repo> others = new ArrayList<>();
|
||||
|
||||
repoPairs.clear();
|
||||
while (repoCursor.moveToNext()) {
|
||||
Repo repo = new Repo(repoCursor);
|
||||
if (repo.getName().toLowerCase().contains(s.toLowerCase())
|
||||
|| repo.getAuthor().toLowerCase().contains(s.toLowerCase())
|
||||
|| repo.getDescription().toLowerCase().contains(s.toLowerCase())
|
||||
) {
|
||||
// Passed the repoFilter
|
||||
Module module = moduleMap.get(repo.getId());
|
||||
if (module != null) {
|
||||
if (repo.getVersionCode() > module.getVersionCode()) {
|
||||
// Updates
|
||||
updates.add(repo);
|
||||
} else {
|
||||
installed.add(repo);
|
||||
}
|
||||
} else {
|
||||
others.add(repo);
|
||||
}
|
||||
}
|
||||
}
|
||||
repoCursor.moveToFirst();
|
||||
|
||||
if (!updates.isEmpty())
|
||||
repoPairs.add(new Pair<>(UPDATES, updates));
|
||||
if (!installed.isEmpty())
|
||||
repoPairs.add(new Pair<>(INSTALLED, installed));
|
||||
if (!others.isEmpty())
|
||||
repoPairs.add(new Pair<>(OTHERS, others));
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
static class SectionHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.section_text) TextView sectionText;
|
||||
|
||||
SectionHolder(View itemView) {
|
||||
super(itemView);
|
||||
new ReposAdapter$SectionHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
static class RepoHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.title) TextView title;
|
||||
@BindView(R.id.version_name) TextView versionName;
|
||||
@BindView(R.id.description) TextView description;
|
||||
@BindView(R.id.author) TextView author;
|
||||
@BindView(R.id.info_layout) View infoLayout;
|
||||
@BindView(R.id.download) ImageView downloadImage;
|
||||
@BindView(R.id.update_time) TextView updateTime;
|
||||
|
||||
RepoHolder(View itemView) {
|
||||
super(itemView);
|
||||
new ReposAdapter$RepoHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -1,96 +0,0 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
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;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
final public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
if (viewType == SECTION_TYPE)
|
||||
return onCreateSectionViewHolder(parent);
|
||||
return onCreateItemViewHolder(parent, viewType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
final public void onBindViewHolder(@NonNull 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);
|
||||
}
|
@@ -1,103 +0,0 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public abstract class StringListAdapter<VH extends StringListAdapter.ViewHolder>
|
||||
extends RecyclerView.Adapter<VH> {
|
||||
|
||||
private RecyclerView rv;
|
||||
private boolean dynamic;
|
||||
private int screenWidth;
|
||||
private int txtWidth = -1;
|
||||
private int padding;
|
||||
|
||||
protected List<String> mList;
|
||||
|
||||
public StringListAdapter(List<String> list) {
|
||||
this(list, false);
|
||||
}
|
||||
|
||||
public StringListAdapter(List<String> list, boolean isDynamic) {
|
||||
mList = list;
|
||||
dynamic = isDynamic;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public final VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(itemLayoutRes(), parent, false);
|
||||
VH vh = createViewHolder(v);
|
||||
if (txtWidth < 0)
|
||||
onUpdateTextWidth(vh);
|
||||
return vh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull VH holder, int position) {
|
||||
holder.txt.setText(mList.get(position));
|
||||
holder.txt.getLayoutParams().width = txtWidth;
|
||||
if (dynamic)
|
||||
onUpdateTextWidth(holder);
|
||||
}
|
||||
|
||||
protected void onUpdateTextWidth(VH vh) {
|
||||
if (txtWidth < 0) {
|
||||
txtWidth = screenWidth - padding;
|
||||
} else {
|
||||
vh.txt.measure(0, 0);
|
||||
int width = vh.txt.getMeasuredWidth();
|
||||
if (width > txtWidth) {
|
||||
txtWidth = width;
|
||||
vh.txt.getLayoutParams().width = txtWidth;
|
||||
}
|
||||
}
|
||||
if (rv.getWidth() != txtWidth + padding)
|
||||
rv.requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToRecyclerView(@NonNull RecyclerView rv) {
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
((Activity) rv.getContext()).getWindowManager()
|
||||
.getDefaultDisplay().getMetrics(displayMetrics);
|
||||
screenWidth = displayMetrics.widthPixels;
|
||||
padding = rv.getPaddingLeft() + rv.getPaddingRight();
|
||||
this.rv = rv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getItemCount() {
|
||||
return mList.size();
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
protected abstract int itemLayoutRes();
|
||||
|
||||
@NonNull
|
||||
public abstract VH createViewHolder(@NonNull View v);
|
||||
|
||||
public static abstract class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public TextView txt;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
txt = itemView.findViewById(textViewResId());
|
||||
}
|
||||
|
||||
@IdRes
|
||||
protected abstract int textViewResId();
|
||||
}
|
||||
}
|
@@ -1,143 +0,0 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
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.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.database.MagiskDB;
|
||||
import com.topjohnwu.magisk.uicomponents.ExpandableViewHolder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, SuLogAdapter.LogViewHolder> {
|
||||
|
||||
private List<List<SuLogEntry>> logEntries;
|
||||
private Set<Integer> itemExpanded, sectionExpanded;
|
||||
private MagiskDB suDB;
|
||||
|
||||
public SuLogAdapter(MagiskDB db) {
|
||||
suDB = db;
|
||||
logEntries = Collections.emptyList();
|
||||
sectionExpanded = new HashSet<>();
|
||||
itemExpanded = new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSectionCount() {
|
||||
return logEntries.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(int section) {
|
||||
return sectionExpanded.contains(section) ? logEntries.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) {
|
||||
SuLogEntry entry = logEntries.get(section).get(0);
|
||||
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), logEntries.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), logEntries.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) {
|
||||
SuLogEntry entry = logEntries.get(section).get(position);
|
||||
int realIdx = getItemPosition(section, position);
|
||||
holder.expandable.setExpanded(itemExpanded.contains(realIdx));
|
||||
holder.itemView.setOnClickListener(view -> {
|
||||
if (holder.expandable.isExpanded()) {
|
||||
holder.expandable.collapse();
|
||||
itemExpanded.remove(realIdx);
|
||||
} else {
|
||||
holder.expandable.expand();
|
||||
itemExpanded.add(realIdx);
|
||||
}
|
||||
});
|
||||
Context context = holder.itemView.getContext();
|
||||
holder.appName.setText(entry.appName);
|
||||
holder.action.setText(entry.action ? R.string.grant : R.string.deny);
|
||||
holder.pid.setText(context.getString(R.string.pid, entry.fromPid));
|
||||
holder.uid.setText(context.getString(R.string.target_uid, entry.toUid));
|
||||
holder.command.setText(context.getString(R.string.command, entry.command));
|
||||
holder.time.setText(entry.getTimeString());
|
||||
}
|
||||
|
||||
public void notifyDBChanged() {
|
||||
logEntries = suDB.getLogs();
|
||||
itemExpanded.clear();
|
||||
sectionExpanded.clear();
|
||||
sectionExpanded.add(0);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
static class SectionHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.date) TextView date;
|
||||
@BindView(R.id.arrow) ImageView arrow;
|
||||
|
||||
SectionHolder(View itemView) {
|
||||
super(itemView);
|
||||
new SuLogAdapter$SectionHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
static class LogViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.app_name) TextView appName;
|
||||
@BindView(R.id.action) TextView action;
|
||||
@BindView(R.id.time) TextView time;
|
||||
@BindView(R.id.pid) TextView pid;
|
||||
@BindView(R.id.uid) TextView uid;
|
||||
@BindView(R.id.cmd) TextView command;
|
||||
@BindView(R.id.expand_layout) ViewGroup expandLayout;
|
||||
|
||||
ExpandableViewHolder expandable;
|
||||
|
||||
LogViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
new SuLogAdapter$LogViewHolder_ViewBinding(this, itemView);
|
||||
expandable = new ExpandableViewHolder(expandLayout);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@@ -1,157 +0,0 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
public abstract class BaseActivity extends AppCompatActivity implements Topic.AutoSubscriber {
|
||||
|
||||
public static final String INTENT_PERM = "perm_dialog";
|
||||
private static Runnable grantCallback;
|
||||
|
||||
static int[] EMPTY_INT_ARRAY = new int[0];
|
||||
|
||||
private ActivityResultListener activityResultListener;
|
||||
public App app = App.self;
|
||||
|
||||
static {
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return EMPTY_INT_ARRAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {}
|
||||
|
||||
@StyleRes
|
||||
public int getDarkTheme() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(LocaleManager.getLocaleContext(base, LocaleManager.locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Topic.subscribe(this);
|
||||
if (getDarkTheme() != -1 && (boolean) Config.get(Config.Key.DARK_THEME)) {
|
||||
setTheme(getDarkTheme());
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
String[] perms = getIntent().getStringArrayExtra(INTENT_PERM);
|
||||
if (perms != null)
|
||||
ActivityCompat.requestPermissions(this, perms, 0);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
public void runWithExternalRW(Runnable callback) {
|
||||
runWithPermission(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, callback);
|
||||
}
|
||||
|
||||
public void runWithPermission(String[] permissions, Runnable callback) {
|
||||
runWithPermission(this, permissions, callback);
|
||||
}
|
||||
|
||||
public static void runWithPermission(Context context, String[] permissions, Runnable callback) {
|
||||
boolean granted = true;
|
||||
for (String perm : permissions) {
|
||||
if (ContextCompat.checkSelfPermission(context, perm) != PackageManager.PERMISSION_GRANTED)
|
||||
granted = false;
|
||||
}
|
||||
if (granted) {
|
||||
Const.EXTERNAL_PATH.mkdirs();
|
||||
callback.run();
|
||||
} else {
|
||||
// Passed in context should be an activity if not granted, need to show dialog!
|
||||
if (context instanceof BaseActivity) {
|
||||
grantCallback = callback;
|
||||
ActivityCompat.requestPermissions((BaseActivity) context, permissions, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
boolean grant = true;
|
||||
for (int result : grantResults) {
|
||||
if (result != PackageManager.PERMISSION_GRANTED)
|
||||
grant = false;
|
||||
}
|
||||
if (grant) {
|
||||
if (grantCallback != null) {
|
||||
grantCallback.run();
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, R.string.no_rw_storage, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
grantCallback = null;
|
||||
}
|
||||
|
||||
public interface ActivityResultListener {
|
||||
void onActivityResult(int requestCode, int resultCode, Intent data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedPreferences getSharedPreferences(String name, int mode) {
|
||||
if (TextUtils.equals(name, getPackageName() + "_preferences"))
|
||||
return app.prefs;
|
||||
return super.getSharedPreferences(name, mode);
|
||||
}
|
||||
}
|
@@ -1,55 +0,0 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public abstract class BaseFragment extends Fragment implements Topic.AutoSubscriber {
|
||||
|
||||
public App app = App.self;
|
||||
protected Unbinder unbinder = null;
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Topic.subscribe(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
Topic.unsubscribe(this);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
if (unbinder != null)
|
||||
unbinder.unbind();
|
||||
}
|
||||
|
||||
@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 BaseActivity.EMPTY_INT_ARRAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {}
|
||||
}
|
@@ -1,81 +0,0 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
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.recyclerview.widget.RecyclerView;
|
||||
|
||||
public abstract class BasePreferenceFragment extends PreferenceFragmentCompat
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener, Topic.AutoSubscriber {
|
||||
|
||||
public App app = App.self;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = super.onCreateView(inflater, container, savedInstanceState);
|
||||
app.prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
Topic.subscribe(this);
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
app.prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
Topic.unsubscribe(this);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return BaseActivity.EMPTY_INT_ARRAY;
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,100 +0,0 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.uicomponents.ProgressNotification;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class DownloadModuleService extends Service {
|
||||
|
||||
private boolean running = false;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (flags == 0 && running) {
|
||||
Utils.toast(R.string.dl_one_module, Toast.LENGTH_LONG);
|
||||
} else {
|
||||
running = true;
|
||||
Shell.EXECUTOR.execute(() -> {
|
||||
Repo repo = intent.getParcelableExtra("repo");
|
||||
boolean install = intent.getBooleanExtra("install", false);
|
||||
dlProcessInstall(repo, install);
|
||||
stopSelf();
|
||||
});
|
||||
}
|
||||
return START_REDELIVER_INTENT;
|
||||
}
|
||||
|
||||
private void dlProcessInstall(Repo repo, boolean install) {
|
||||
File output = new File(Const.EXTERNAL_PATH, repo.getDownloadFilename());
|
||||
ProgressNotification progress = new ProgressNotification(output.getName());
|
||||
startForeground(progress.hashCode(), progress.getNotification());
|
||||
try {
|
||||
InputStream in = Networking.get(repo.getZipUrl())
|
||||
.setDownloadProgressListener(progress)
|
||||
.execForInputStream().getResult();
|
||||
removeTopFolder(in, new BufferedOutputStream(new FileOutputStream(output)));
|
||||
if (install) {
|
||||
progress.dismiss();
|
||||
Intent intent = new Intent(this, ClassMap.get(FlashActivity.class));
|
||||
intent.setData(Uri.fromFile(output))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||
startActivity(intent);
|
||||
} else {
|
||||
progress.dlDone();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
progress.dlFail();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeTopFolder(InputStream in, OutputStream out) throws IOException {
|
||||
try (ZipInputStream zin = new ZipInputStream(in);
|
||||
ZipOutputStream zout = new ZipOutputStream(out)) {
|
||||
ZipEntry entry;
|
||||
int off = -1;
|
||||
while ((entry = zin.getNextEntry()) != null) {
|
||||
if (off < 0)
|
||||
off = entry.getName().indexOf('/') + 1;
|
||||
String path = entry.getName().substring(off);
|
||||
if (path.isEmpty())
|
||||
continue;
|
||||
zout.putNextEntry(new ZipEntry(path));
|
||||
if (!entry.isDirectory())
|
||||
ShellUtils.pump(zin, zout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,99 +0,0 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SuRequestActivity;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.uicomponents.Notifications;
|
||||
import com.topjohnwu.magisk.uicomponents.Shortcuts;
|
||||
import com.topjohnwu.magisk.utils.DownloadApp;
|
||||
import com.topjohnwu.magisk.utils.SuLogger;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class GeneralReceiver extends BroadcastReceiver {
|
||||
|
||||
private static SuLogger SU_LOGGER = new SuLogger() {
|
||||
@Override
|
||||
public String getMessage(Policy policy) {
|
||||
return App.self.getString(policy.policy == Policy.ALLOW ?
|
||||
R.string.su_allow_toast : R.string.su_deny_toast, policy.appName);
|
||||
}
|
||||
};
|
||||
|
||||
private String getPkg(Intent i) {
|
||||
return i.getData() == null ? "" : i.getData().getEncodedSchemeSpecificPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
App app = App.self;
|
||||
if (intent == null)
|
||||
return;
|
||||
String action = intent.getAction();
|
||||
if (action == null)
|
||||
return;
|
||||
switch (action) {
|
||||
case Intent.ACTION_BOOT_COMPLETED:
|
||||
String bootAction = intent.getStringExtra("action");
|
||||
if (bootAction == null)
|
||||
bootAction = "boot";
|
||||
switch (bootAction) {
|
||||
case "request":
|
||||
Intent i = new Intent(app, ClassMap.get(SuRequestActivity.class))
|
||||
.putExtra("socket", intent.getStringExtra("socket"))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
app.startActivity(i);
|
||||
break;
|
||||
case "log":
|
||||
SU_LOGGER.handleLogs(intent);
|
||||
break;
|
||||
case "notify":
|
||||
SU_LOGGER.handleNotify(intent);
|
||||
break;
|
||||
case "boot":
|
||||
default:
|
||||
/* 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.
|
||||
* */
|
||||
Shell.su("mm_patch_dtbo").submit(result -> {
|
||||
if (result.isSuccess())
|
||||
Notifications.dtboPatched();
|
||||
});
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Intent.ACTION_PACKAGE_REPLACED:
|
||||
// This will only work pre-O
|
||||
if (Config.get(Config.Key.SU_REAUTH)) {
|
||||
app.mDB.deletePolicy(getPkg(intent));
|
||||
}
|
||||
break;
|
||||
case Intent.ACTION_PACKAGE_FULLY_REMOVED:
|
||||
String pkg = getPkg(intent);
|
||||
app.mDB.deletePolicy(pkg);
|
||||
Shell.su("magiskhide --rm " + pkg).submit();
|
||||
break;
|
||||
case Intent.ACTION_LOCALE_CHANGED:
|
||||
Shortcuts.setup(context);
|
||||
break;
|
||||
case Const.Key.BROADCAST_MANAGER_UPDATE:
|
||||
Config.managerLink = intent.getStringExtra(Const.Key.INTENT_SET_LINK);
|
||||
DownloadApp.upgrade(intent.getStringExtra(Const.Key.INTENT_SET_NAME));
|
||||
break;
|
||||
case Const.Key.BROADCAST_REBOOT:
|
||||
Shell.su("/system/bin/reboot").submit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.tasks.CheckUpdates;
|
||||
import com.topjohnwu.magisk.uicomponents.Notifications;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.work.ListenableWorker;
|
||||
|
||||
public class UpdateCheckService extends DelegateWorker {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ListenableWorker.Result doWork() {
|
||||
Shell.getShell();
|
||||
CheckUpdates.checkNow(this::onCheckDone);
|
||||
return ListenableWorker.Result.success();
|
||||
}
|
||||
|
||||
private void onCheckDone() {
|
||||
if (BuildConfig.VERSION_CODE < Config.remoteManagerVersionCode) {
|
||||
Notifications.managerUpdate();
|
||||
} else if (Config.magiskVersionCode < Config.remoteMagiskVersionCode) {
|
||||
Notifications.magiskUpdate();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,161 +0,0 @@
|
||||
package com.topjohnwu.magisk.dialogs;
|
||||
|
||||
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 androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class CustomAlertDialog extends AlertDialog.Builder {
|
||||
|
||||
private DialogInterface.OnClickListener positiveListener;
|
||||
private DialogInterface.OnClickListener negativeListener;
|
||||
private DialogInterface.OnClickListener neutralListener;
|
||||
|
||||
protected AlertDialog dialog;
|
||||
protected ViewHolder vh;
|
||||
|
||||
public class ViewHolder {
|
||||
@BindView(R.id.dialog_layout) public LinearLayout dialogLayout;
|
||||
@BindView(R.id.button_panel) public LinearLayout buttons;
|
||||
|
||||
@BindView(R.id.message) public TextView messageView;
|
||||
@BindView(R.id.negative) public Button negative;
|
||||
@BindView(R.id.positive) public Button positive;
|
||||
@BindView(R.id.neutral) public Button neutral;
|
||||
|
||||
ViewHolder(View v) {
|
||||
new CustomAlertDialog$ViewHolder_ViewBinding(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();
|
||||
}
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
package com.topjohnwu.magisk.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
import com.topjohnwu.magisk.tasks.MagiskInstaller;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
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) -> {
|
||||
ProgressDialog pd = ProgressDialog.show(activity,
|
||||
activity.getString(R.string.setup_title),
|
||||
activity.getString(R.string.setup_msg));
|
||||
new MagiskInstaller() {
|
||||
@Override
|
||||
protected boolean operations() {
|
||||
installDir = new SuFile("/data/adb/magisk");
|
||||
Shell.su("rm -rf /data/adb/magisk/*").exec();
|
||||
return extractZip() && Shell.su("fix_env").exec().isSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResult(boolean success) {
|
||||
pd.dismiss();
|
||||
Utils.toast(success ? R.string.setup_done : R.string.setup_fail, Toast.LENGTH_LONG);
|
||||
if (success) {
|
||||
// Relaunch the app
|
||||
try {
|
||||
Shell.getShell().close();
|
||||
} catch (IOException ignored) {}
|
||||
Intent intent = new Intent(activity, ClassMap.get(SplashActivity.class));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
activity.startActivity(intent);
|
||||
activity.finish();
|
||||
}
|
||||
|
||||
}
|
||||
}.exec();
|
||||
});
|
||||
setNegativeButton(R.string.no_thanks, null);
|
||||
}
|
||||
}
|
@@ -1,88 +0,0 @@
|
||||
package com.topjohnwu.magisk.dialogs;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
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.os.Build;
|
||||
import android.view.Gravity;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public class FingerprintAuthDialog extends CustomAlertDialog {
|
||||
|
||||
private Runnable callback;
|
||||
private DialogFingerprintHelper helper;
|
||||
|
||||
public FingerprintAuthDialog(@NonNull Activity activity, Runnable onSuccess) {
|
||||
super(activity);
|
||||
callback = onSuccess;
|
||||
Drawable fingerprint = activity.getResources().getDrawable(R.drawable.ic_fingerprint);
|
||||
fingerprint.setBounds(0, 0, Utils.dpInPx(50), Utils.dpInPx(50));
|
||||
Resources.Theme theme = activity.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);
|
||||
setMessage(R.string.auth_fingerprint);
|
||||
setNegativeButton(R.string.close, (d, w) -> helper.cancel());
|
||||
setOnCancelListener(d -> helper.cancel());
|
||||
try {
|
||||
helper = new DialogFingerprintHelper();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog show() {
|
||||
create();
|
||||
if (helper == null) {
|
||||
dialog.dismiss();
|
||||
Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
helper.authenticate();
|
||||
dialog.show();
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
class DialogFingerprintHelper extends FingerprintHelper {
|
||||
|
||||
DialogFingerprintHelper() throws Exception {}
|
||||
|
||||
@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) {
|
||||
dismiss();
|
||||
callback.run();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,99 +0,0 @@
|
||||
package com.topjohnwu.magisk.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.uicomponents.ProgressNotification;
|
||||
import com.topjohnwu.magisk.uicomponents.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
|
||||
import java.io.File;
|
||||
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:
|
||||
patchBoot(activity);
|
||||
break;
|
||||
case 0:
|
||||
downloadOnly(activity);
|
||||
break;
|
||||
case 2:
|
||||
intent = new Intent(activity, ClassMap.get(FlashActivity.class))
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_MAGISK);
|
||||
activity.startActivity(intent);
|
||||
break;
|
||||
case 3:
|
||||
installInactiveSlot(activity);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void patchBoot(BaseActivity a) {
|
||||
Utils.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT).setType("*/*");
|
||||
a.runWithExternalRW(() ->
|
||||
a.startActivityForResult(intent, Const.ID.SELECT_BOOT,
|
||||
(requestCode, resultCode, data) -> {
|
||||
if (requestCode == Const.ID.SELECT_BOOT &&
|
||||
resultCode == Activity.RESULT_OK && data != null) {
|
||||
Intent i = new Intent(a, ClassMap.get(FlashActivity.class))
|
||||
.setData(data.getData())
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_BOOT);
|
||||
a.startActivity(i);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private void downloadOnly(BaseActivity a) {
|
||||
a.runWithExternalRW(() -> {
|
||||
String filename = Utils.fmt("Magisk-v%s(%d).zip",
|
||||
Config.remoteMagiskVersionString, Config.remoteMagiskVersionCode);
|
||||
File zip = new File(Const.EXTERNAL_PATH, filename);
|
||||
ProgressNotification progress = new ProgressNotification(filename);
|
||||
Networking.get(Config.magiskLink)
|
||||
.setDownloadProgressListener(progress)
|
||||
.setErrorHandler(((conn, e) -> progress.dlFail()))
|
||||
.getAsFile(zip, f -> {
|
||||
progress.dlDone();
|
||||
SnackbarMaker.make(a,
|
||||
a.getString(R.string.internal_storage, "/Download/" + filename),
|
||||
Snackbar.LENGTH_LONG).show();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void installInactiveSlot(BaseActivity activity) {
|
||||
new CustomAlertDialog(activity)
|
||||
.setTitle(R.string.warning)
|
||||
.setMessage(R.string.install_inactive_slot_msg)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.yes, (d, i) -> {
|
||||
Intent intent = new Intent(activity, ClassMap.get(FlashActivity.class))
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_INACTIVE_SLOT);
|
||||
activity.startActivity(intent);
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
}
|
||||
}
|
@@ -1,50 +0,0 @@
|
||||
package com.topjohnwu.magisk.dialogs;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.uicomponents.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.utils.AppUtils;
|
||||
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 a) {
|
||||
super(a);
|
||||
String filename = Utils.fmt("Magisk-v%s(%d).zip",
|
||||
Config.remoteMagiskVersionString, Config.remoteMagiskVersionCode);
|
||||
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.magisk)));
|
||||
setMessage(a.getString(R.string.repo_install_msg, filename));
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.install, (d, i) -> {
|
||||
List<String> options = new ArrayList<>();
|
||||
options.add(a.getString(R.string.download_zip_only));
|
||||
options.add(a.getString(R.string.patch_boot_file));
|
||||
if (Shell.rootAccess()) {
|
||||
options.add(a.getString(R.string.direct_install));
|
||||
String s = ShellUtils.fastCmd("grep_prop ro.build.ab_update");
|
||||
if (!s.isEmpty() && Boolean.parseBoolean(s)) {
|
||||
options.add(a.getString(R.string.install_inactive_slot));
|
||||
}
|
||||
}
|
||||
new InstallMethodDialog(a, options).show();
|
||||
});
|
||||
if (!TextUtils.isEmpty(Config.magiskNoteLink)) {
|
||||
setNeutralButton(R.string.release_notes, (d, i) -> {
|
||||
if (Config.magiskNoteLink.contains("forum.xda-developers")) {
|
||||
// Open forum links in browser
|
||||
AppUtils.openLink(a, Uri.parse(Config.magiskNoteLink));
|
||||
} else {
|
||||
MarkDownWindow.show(a, null, Config.magiskNoteLink);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
package com.topjohnwu.magisk.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.uicomponents.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.utils.DownloadApp;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ManagerInstallDialog extends CustomAlertDialog {
|
||||
|
||||
public ManagerInstallDialog(@NonNull Activity a) {
|
||||
super(a);
|
||||
String name = Utils.fmt("MagiskManager v%s(%d)",
|
||||
Config.remoteManagerVersionString, Config.remoteManagerVersionCode);
|
||||
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.app_name)));
|
||||
setMessage(a.getString(R.string.repo_install_msg, name));
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.install, (d, i) -> DownloadApp.upgrade(name));
|
||||
if (!TextUtils.isEmpty(Config.managerNoteLink)) {
|
||||
setNeutralButton(R.string.app_changelog, (d, i) -> MarkDownWindow.show(a, null, Config.managerNoteLink));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,61 +0,0 @@
|
||||
package com.topjohnwu.magisk.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.uicomponents.ProgressNotification;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class UninstallDialog extends CustomAlertDialog {
|
||||
|
||||
public UninstallDialog(@NonNull Activity activity) {
|
||||
super(activity);
|
||||
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(Config.uninstallerLink)) {
|
||||
setPositiveButton(R.string.complete_uninstall, (d, i) -> {
|
||||
File zip = new File(activity.getFilesDir(), "uninstaller.zip");
|
||||
ProgressNotification progress = new ProgressNotification(zip.getName());
|
||||
Networking.get(Config.uninstallerLink)
|
||||
.setDownloadProgressListener(progress)
|
||||
.setErrorHandler(((conn, e) -> progress.dlFail()))
|
||||
.getAsFile(zip, f -> {
|
||||
progress.dismiss();
|
||||
Intent intent = new Intent(activity, ClassMap.get(FlashActivity.class))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.setData(Uri.fromFile(f))
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.UNINSTALL);
|
||||
activity.startActivity(intent);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
|
||||
import android.os.Build;
|
||||
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.MainActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.TabFragmentAdapter;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class LogFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.container) ViewPager viewPager;
|
||||
@BindView(R.id.tab) TabLayout tab;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
View v = inflater.inflate(R.layout.fragment_log, container, false);
|
||||
unbinder = new LogFragment_ViewBinding(this, v);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
((MainActivity) requireActivity()).toolbar.setElevation(0);
|
||||
}
|
||||
|
||||
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@@ -1,332 +0,0 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.net.Uri;
|
||||
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 com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.MainActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.dialogs.EnvFixDialog;
|
||||
import com.topjohnwu.magisk.dialogs.MagiskInstallDialog;
|
||||
import com.topjohnwu.magisk.dialogs.ManagerInstallDialog;
|
||||
import com.topjohnwu.magisk.dialogs.UninstallDialog;
|
||||
import com.topjohnwu.magisk.tasks.CheckUpdates;
|
||||
import com.topjohnwu.magisk.uicomponents.ArrowExpandedViewHolder;
|
||||
import com.topjohnwu.magisk.uicomponents.ExpandableViewHolder;
|
||||
import com.topjohnwu.magisk.uicomponents.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.uicomponents.SafetyNet;
|
||||
import com.topjohnwu.magisk.uicomponents.UpdateCardHolder;
|
||||
import com.topjohnwu.magisk.utils.AppUtils;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import androidx.transition.ChangeBounds;
|
||||
import androidx.transition.Fade;
|
||||
import androidx.transition.Transition;
|
||||
import androidx.transition.TransitionManager;
|
||||
import androidx.transition.TransitionSet;
|
||||
import butterknife.BindColor;
|
||||
import butterknife.BindView;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class MagiskFragment extends BaseFragment
|
||||
implements SwipeRefreshLayout.OnRefreshListener, Topic.Subscriber {
|
||||
|
||||
private static boolean shownDialog = false;
|
||||
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@BindView(R.id.linearLayout) LinearLayout root;
|
||||
|
||||
@BindView(R.id.install_option_card) CardView installOptionCard;
|
||||
@BindView(R.id.keep_force_enc) CheckBox keepEncChkbox;
|
||||
@BindView(R.id.keep_verity) CheckBox keepVerityChkbox;
|
||||
@BindView(R.id.install_option_expand) ViewGroup optionExpandLayout;
|
||||
@BindView(R.id.arrow) ImageView arrow;
|
||||
|
||||
@BindView(R.id.uninstall_button) CardView uninstallButton;
|
||||
|
||||
@BindColor(R.color.red500) int colorBad;
|
||||
@BindColor(R.color.green500) int colorOK;
|
||||
@BindColor(R.color.yellow500) int colorWarn;
|
||||
@BindColor(R.color.green500) int colorNeutral;
|
||||
@BindColor(R.color.blue500) int colorInfo;
|
||||
|
||||
private UpdateCardHolder magisk;
|
||||
private UpdateCardHolder manager;
|
||||
private SafetyNet safetyNet;
|
||||
private Transition transition;
|
||||
private ExpandableViewHolder optionExpand;
|
||||
|
||||
private void magiskInstall(View v) {
|
||||
// Show Manager update first
|
||||
if (Config.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
new ManagerInstallDialog(requireActivity()).show();
|
||||
return;
|
||||
}
|
||||
new MagiskInstallDialog((BaseActivity) requireActivity()).show();
|
||||
}
|
||||
|
||||
private void managerInstall(View v) {
|
||||
new ManagerInstallDialog(requireActivity()).show();
|
||||
}
|
||||
|
||||
private void openLink(String url) {
|
||||
AppUtils.openLink(requireActivity(), Uri.parse(url));
|
||||
}
|
||||
|
||||
@OnClick(R.id.paypal)
|
||||
void paypal() {
|
||||
openLink(Const.Url.PAYPAL_URL);
|
||||
}
|
||||
|
||||
@OnClick(R.id.patreon)
|
||||
void patreon() {
|
||||
openLink(Const.Url.PATREON_URL);
|
||||
}
|
||||
|
||||
@OnClick(R.id.twitter)
|
||||
void twitter() {
|
||||
openLink(Const.Url.TWITTER_URL);
|
||||
}
|
||||
|
||||
@OnClick(R.id.github)
|
||||
void github() {
|
||||
openLink(Const.Url.SOURCE_CODE_URL);
|
||||
}
|
||||
|
||||
@OnClick(R.id.xda)
|
||||
void xda() {
|
||||
openLink(Const.Url.XDA_THREAD);
|
||||
}
|
||||
|
||||
@OnClick(R.id.uninstall_button)
|
||||
void uninstall() {
|
||||
new UninstallDialog(requireActivity()).show();
|
||||
}
|
||||
|
||||
@OnClick(R.id.arrow)
|
||||
void expandOptions() {
|
||||
if (optionExpand.isExpanded())
|
||||
optionExpand.collapse();
|
||||
else optionExpand.expand();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.fragment_magisk, container, false);
|
||||
unbinder = new MagiskFragment_ViewBinding(this, v);
|
||||
requireActivity().setTitle(R.string.magisk);
|
||||
|
||||
optionExpand = new ArrowExpandedViewHolder(optionExpandLayout, arrow);
|
||||
safetyNet = new SafetyNet(v);
|
||||
magisk = new UpdateCardHolder(inflater, root);
|
||||
manager = new UpdateCardHolder(inflater, root);
|
||||
manager.setClickable(vv ->
|
||||
MarkDownWindow.show(requireActivity(), null,
|
||||
getResources().openRawResource(R.raw.changelog)));
|
||||
root.addView(magisk.itemView, 1);
|
||||
root.addView(manager.itemView, 2);
|
||||
|
||||
keepVerityChkbox.setChecked(Config.keepVerity);
|
||||
keepVerityChkbox.setOnCheckedChangeListener((view, checked) -> Config.keepVerity = checked);
|
||||
keepEncChkbox.setChecked(Config.keepEnc);
|
||||
keepEncChkbox.setOnCheckedChangeListener((view, checked) -> Config.keepEnc = checked);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(this);
|
||||
|
||||
magisk.install.setOnClickListener(this::magiskInstall);
|
||||
manager.install.setOnClickListener(this::managerInstall);
|
||||
if (Config.get(Config.Key.COREONLY)) {
|
||||
magisk.additional.setText(R.string.core_only_enabled);
|
||||
magisk.additional.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (!app.getPackageName().equals(BuildConfig.APPLICATION_ID)) {
|
||||
manager.additional.setText("(" + app.getPackageName() + ")");
|
||||
manager.additional.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
transition = new TransitionSet()
|
||||
.setOrdering(TransitionSet.ORDERING_TOGETHER)
|
||||
.addTransition(new Fade(Fade.OUT))
|
||||
.addTransition(new ChangeBounds())
|
||||
.addTransition(new Fade(Fade.IN));
|
||||
|
||||
updateUI();
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
safetyNet.unbinder.unbind();
|
||||
magisk.unbinder.unbind();
|
||||
manager.unbinder.unbind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
TransitionManager.beginDelayedTransition(root, transition);
|
||||
safetyNet.reset();
|
||||
magisk.reset();
|
||||
manager.reset();
|
||||
|
||||
Config.loadMagiskInfo();
|
||||
updateUI();
|
||||
|
||||
Topic.reset(getSubscribedTopics());
|
||||
Config.remoteMagiskVersionString = null;
|
||||
Config.remoteMagiskVersionCode = -1;
|
||||
|
||||
shownDialog = false;
|
||||
|
||||
// Trigger state check
|
||||
if (Networking.checkNetworkStatus(app)) {
|
||||
CheckUpdates.check();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.UPDATE_CHECK_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
updateCheckUI();
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
((MainActivity) requireActivity()).checkHideSection();
|
||||
int image, color;
|
||||
String status;
|
||||
if (Config.magiskVersionCode < 0) {
|
||||
color = colorBad;
|
||||
image = R.drawable.ic_cancel;
|
||||
status = getString(R.string.magisk_version_error);
|
||||
magisk.status.setText(status);
|
||||
magisk.currentVersion.setVisibility(View.GONE);
|
||||
} else {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
status = getString(R.string.magisk);
|
||||
magisk.currentVersion.setText(getString(R.string.current_installed,
|
||||
String.format(Locale.US, "v%s (%d)",
|
||||
Config.magiskVersionString, Config.magiskVersionCode)));
|
||||
}
|
||||
magisk.statusIcon.setColorFilter(color);
|
||||
magisk.statusIcon.setImageResource(image);
|
||||
|
||||
manager.statusIcon.setColorFilter(colorOK);
|
||||
manager.statusIcon.setImageResource(R.drawable.ic_check_circle);
|
||||
manager.currentVersion.setText(getString(R.string.current_installed,
|
||||
String.format(Locale.US, "v%s (%d)",
|
||||
BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
||||
|
||||
if (!Networking.checkNetworkStatus(app)) {
|
||||
// No network, updateCheckUI will not be triggered
|
||||
magisk.status.setText(status);
|
||||
manager.status.setText(R.string.app_name);
|
||||
magisk.setValid(false);
|
||||
manager.setValid(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCheckUI() {
|
||||
int image, color;
|
||||
String status, button = "";
|
||||
|
||||
if (Config.remoteMagiskVersionCode < 0) {
|
||||
color = colorNeutral;
|
||||
image = R.drawable.ic_help;
|
||||
status = getString(R.string.invalid_update_channel);
|
||||
} else {
|
||||
magisk.latestVersion.setText(getString(R.string.latest_version,
|
||||
String.format(Locale.US, "v%s (%d)",
|
||||
Config.remoteMagiskVersionString, Config.remoteMagiskVersionCode)));
|
||||
if (Config.remoteMagiskVersionCode > Config.magiskVersionCode) {
|
||||
color = colorInfo;
|
||||
image = R.drawable.ic_update;
|
||||
status = getString(R.string.magisk_update_title);
|
||||
button = getString(R.string.update);
|
||||
} else {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
status = getString(R.string.magisk_up_to_date);
|
||||
button = getString(R.string.install);
|
||||
}
|
||||
}
|
||||
if (Config.magiskVersionCode > 0) {
|
||||
// Only override status if Magisk is installed
|
||||
magisk.statusIcon.setImageResource(image);
|
||||
magisk.statusIcon.setColorFilter(color);
|
||||
magisk.status.setText(status);
|
||||
magisk.install.setText(button);
|
||||
}
|
||||
|
||||
if (Config.remoteManagerVersionCode < 0) {
|
||||
color = colorNeutral;
|
||||
image = R.drawable.ic_help;
|
||||
status = getString(R.string.invalid_update_channel);
|
||||
} else {
|
||||
manager.latestVersion.setText(getString(R.string.latest_version,
|
||||
String.format(Locale.US, "v%s (%d)",
|
||||
Config.remoteManagerVersionString, Config.remoteManagerVersionCode)));
|
||||
if (Config.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
color = colorInfo;
|
||||
image = R.drawable.ic_update;
|
||||
status = getString(R.string.manager_update_title);
|
||||
manager.install.setText(R.string.update);
|
||||
} else {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
status = getString(R.string.manager_up_to_date);
|
||||
manager.install.setText(R.string.install);
|
||||
}
|
||||
}
|
||||
manager.statusIcon.setImageResource(image);
|
||||
manager.statusIcon.setColorFilter(color);
|
||||
manager.status.setText(status);
|
||||
|
||||
magisk.setValid(Config.remoteMagiskVersionCode > 0);
|
||||
manager.setValid(Config.remoteManagerVersionCode > 0);
|
||||
|
||||
TransitionManager.beginDelayedTransition(root, transition);
|
||||
|
||||
if (Config.remoteMagiskVersionCode < 0) {
|
||||
// Hide install related components
|
||||
installOptionCard.setVisibility(View.GONE);
|
||||
uninstallButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
// Show install related components
|
||||
installOptionCard.setVisibility(View.VISIBLE);
|
||||
uninstallButton.setVisibility(Shell.rootAccess() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
if (!shownDialog && Config.magiskVersionCode > 0 &&
|
||||
!Shell.su("env_check").exec().isSuccess()) {
|
||||
shownDialog = true;
|
||||
new EnvFixDialog(requireActivity()).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,102 +0,0 @@
|
||||
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.SearchView;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.R;
|
||||
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;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber {
|
||||
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
|
||||
private SearchView search;
|
||||
private ApplicationAdapter adapter;
|
||||
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);
|
||||
unbinder = new MagiskHideFragment_ViewBinding(this, view);
|
||||
|
||||
adapter = new ApplicationAdapter(requireActivity());
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(adapter::refresh);
|
||||
|
||||
searchListener = new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
adapter.filter(query);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
adapter.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);
|
||||
boolean showSystem = Config.get(Config.Key.SHOW_SYSTEM_APP);
|
||||
menu.findItem(R.id.show_system).setChecked(showSystem);
|
||||
adapter.setShowSystem(showSystem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.show_system) {
|
||||
boolean showSystem = !item.isChecked();
|
||||
item.setChecked(showSystem);
|
||||
Config.set(Config.Key.SHOW_SYSTEM_APP, showSystem);
|
||||
adapter.setShowSystem(showSystem);
|
||||
adapter.filter(search.getQuery().toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.MAGISK_HIDE_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
adapter.filter(search.getQuery().toString());
|
||||
}
|
||||
}
|
@@ -1,157 +0,0 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.Manifest;
|
||||
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 com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.StringListAdapter;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.uicomponents.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.NOPList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class MagiskLogFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.recyclerView) RecyclerView rv;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_magisk_log, container, false);
|
||||
unbinder = new MagiskLogFragment_ViewBinding(this, view);
|
||||
setHasOptionsMenu(true);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
getActivity().setTitle(R.string.log);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
readLogs();
|
||||
}
|
||||
|
||||
@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();
|
||||
rv.setAdapter(new MagiskLogAdapter(NOPList.getInstance()));
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void readLogs() {
|
||||
Shell.su("tail -n 5000 " + Const.MAGISK_LOG).submit(result -> {
|
||||
rv.setAdapter(new MagiskLogAdapter(result.getOut()));
|
||||
});
|
||||
}
|
||||
|
||||
private 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(Const.EXTERNAL_PATH, filename);
|
||||
try {
|
||||
logFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
Shell.su("cat " + Const.MAGISK_LOG + " > " + logFile)
|
||||
.submit(result ->
|
||||
SnackbarMaker.make(rv, logFile.getPath(), Snackbar.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
private void clearLogs() {
|
||||
Shell.su("echo -n > " + Const.MAGISK_LOG).submit();
|
||||
SnackbarMaker.make(rv, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private class MagiskLogAdapter extends StringListAdapter<MagiskLogAdapter.ViewHolder> {
|
||||
|
||||
MagiskLogAdapter(List<String> list) {
|
||||
super(list);
|
||||
if (mList.isEmpty())
|
||||
mList.add(requireContext().getString(R.string.log_is_empty));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int itemLayoutRes() {
|
||||
return R.layout.list_item_console;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder createViewHolder(@NonNull View v) {
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdateTextWidth(ViewHolder holder) {
|
||||
super.onUpdateTextWidth(holder);
|
||||
// Find the longest string and update accordingly
|
||||
int max = 0;
|
||||
String maxStr = "";
|
||||
for (String s : mList) {
|
||||
int len = s.length();
|
||||
if (len > max) {
|
||||
max = len;
|
||||
maxStr = s;
|
||||
}
|
||||
}
|
||||
holder.txt.setText(maxStr);
|
||||
super.onUpdateTextWidth(holder);
|
||||
}
|
||||
|
||||
public class ViewHolder extends StringListAdapter.ViewHolder {
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int textViewResId() {
|
||||
return R.id.txt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,141 +0,0 @@
|
||||
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.ClassMap;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.ModulesAdapter;
|
||||
import com.topjohnwu.magisk.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;
|
||||
import butterknife.BindView;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class ModulesFragment extends BaseFragment implements Topic.Subscriber {
|
||||
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
|
||||
@OnClick(R.id.fab)
|
||||
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);
|
||||
unbinder = new ModulesFragment_ViewBinding(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(), ClassMap.get(FlashActivity.class));
|
||||
intent.setData(data.getData()).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@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:
|
||||
Utils.reboot();
|
||||
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);
|
||||
}
|
||||
}
|
@@ -1,117 +0,0 @@
|
||||
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.Config;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.tasks.UpdateRepos;
|
||||
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;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class ReposFragment extends BaseFragment implements Topic.Subscriber {
|
||||
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
@BindView(R.id.swipeRefreshLayout) 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);
|
||||
unbinder = new ReposFragment_ViewBinding(this, view);
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> 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) {
|
||||
switch (topic) {
|
||||
case Topic.MODULE_LOAD_DONE:
|
||||
adapter = new ReposAdapter(app.repoDB, (Map<String, Module>) result[0]);
|
||||
recyclerView.setAdapter(adapter);
|
||||
break;
|
||||
case Topic.REPO_LOAD_DONE:
|
||||
if (adapter != null)
|
||||
adapter.notifyDBChanged();
|
||||
break;
|
||||
}
|
||||
if (Topic.isPublished(this)) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
boolean empty = adapter.getItemCount() == 0;
|
||||
recyclerView.setVisibility(empty ? View.GONE : View.VISIBLE);
|
||||
emptyRv.setVisibility(empty ? 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,
|
||||
Config.get(Config.Key.REPO_ORDER), (d, which) -> {
|
||||
Config.set(Config.Key.REPO_ORDER, which);
|
||||
adapter.notifyDBChanged();
|
||||
d.dismiss();
|
||||
}).show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,293 +0,0 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.BasePreferenceFragment;
|
||||
import com.topjohnwu.magisk.dialogs.FingerprintAuthDialog;
|
||||
import com.topjohnwu.magisk.tasks.CheckUpdates;
|
||||
import com.topjohnwu.magisk.utils.AppUtils;
|
||||
import com.topjohnwu.magisk.utils.DownloadApp;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.PatchAPK;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
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.PreferenceScreen;
|
||||
import androidx.preference.SwitchPreferenceCompat;
|
||||
|
||||
public class SettingsFragment extends BasePreferenceFragment implements Topic.Subscriber {
|
||||
|
||||
private ListPreference updateChannel, autoRes, suNotification,
|
||||
requestTimeout, rootConfig, multiuserConfig, nsConfig;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.app_settings, rootKey);
|
||||
requireActivity().setTitle(R.string.settings);
|
||||
|
||||
boolean showSuperuser = Utils.showSuperUser();
|
||||
app.prefs.edit()
|
||||
.putBoolean(Config.Key.SU_FINGERPRINT, FingerprintHelper.useFingerprint())
|
||||
.apply();
|
||||
|
||||
PreferenceScreen prefScreen = getPreferenceScreen();
|
||||
|
||||
PreferenceCategory generalCatagory = (PreferenceCategory) findPreference("general");
|
||||
PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
|
||||
PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
|
||||
Preference hideManager = findPreference("hide");
|
||||
hideManager.setOnPreferenceClickListener(pref -> {
|
||||
PatchAPK.hideManager();
|
||||
return true;
|
||||
});
|
||||
Preference restoreManager = findPreference("restore");
|
||||
restoreManager.setOnPreferenceClickListener(pref -> {
|
||||
DownloadApp.restore();
|
||||
return true;
|
||||
});
|
||||
findPreference("clear").setOnPreferenceClickListener(pref -> {
|
||||
app.prefs.edit().remove(Config.Key.ETAG_KEY).apply();
|
||||
app.repoDB.clearRepo();
|
||||
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
|
||||
return true;
|
||||
});
|
||||
findPreference("hosts").setOnPreferenceClickListener(pref -> {
|
||||
Shell.su("add_hosts_module").exec();
|
||||
Utils.loadModules();
|
||||
Utils.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT);
|
||||
return true;
|
||||
});
|
||||
|
||||
updateChannel = (ListPreference) findPreference(Config.Key.UPDATE_CHANNEL);
|
||||
rootConfig = (ListPreference) findPreference(Config.Key.ROOT_ACCESS);
|
||||
autoRes = (ListPreference) findPreference(Config.Key.SU_AUTO_RESPONSE);
|
||||
requestTimeout = (ListPreference) findPreference(Config.Key.SU_REQUEST_TIMEOUT);
|
||||
suNotification = (ListPreference) findPreference(Config.Key.SU_NOTIFICATION);
|
||||
multiuserConfig = (ListPreference) findPreference(Config.Key.SU_MULTIUSER_MODE);
|
||||
nsConfig = (ListPreference) findPreference(Config.Key.SU_MNT_NS);
|
||||
SwitchPreferenceCompat reauth = (SwitchPreferenceCompat) findPreference(Config.Key.SU_REAUTH);
|
||||
SwitchPreferenceCompat fingerprint = (SwitchPreferenceCompat) findPreference(Config.Key.SU_FINGERPRINT);
|
||||
|
||||
updateChannel.setOnPreferenceChangeListener((p, o) -> {
|
||||
int prev = Config.get(Config.Key.UPDATE_CHANNEL);
|
||||
int channel = Integer.parseInt((String) o);
|
||||
if (channel == Config.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(app.prefs.getString(Config.Key.CUSTOM_CHANNEL, ""));
|
||||
new AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.settings_update_custom)
|
||||
.setView(v)
|
||||
.setPositiveButton(R.string.ok, (d, i) ->
|
||||
Config.set(Config.Key.CUSTOM_CHANNEL, url.getText().toString()))
|
||||
.setNegativeButton(R.string.close, (d, i) ->
|
||||
Config.set(Config.Key.UPDATE_CHANNEL, prev))
|
||||
.setOnCancelListener(d ->
|
||||
Config.set(Config.Key.UPDATE_CHANNEL, prev))
|
||||
.show();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
setSummary();
|
||||
|
||||
// Remove language setting when API < 17
|
||||
if (Build.VERSION.SDK_INT < 17)
|
||||
generalCatagory.removePreference(findPreference(Config.Key.LOCALE));
|
||||
|
||||
// Disable dangerous settings in secondary user
|
||||
if (Const.USER_ID > 0) {
|
||||
suCategory.removePreference(multiuserConfig);
|
||||
}
|
||||
|
||||
// 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 (Shell.rootAccess() && Const.USER_ID == 0) {
|
||||
if (app.getPackageName().equals(BuildConfig.APPLICATION_ID)) {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
} else {
|
||||
if (!Networking.checkNetworkStatus(app))
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
|
||||
if (!showSuperuser) {
|
||||
prefScreen.removePreference(suCategory);
|
||||
}
|
||||
|
||||
if (!Shell.rootAccess()) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
}
|
||||
|
||||
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++] = LocaleManager.toLanguageTag(locale);
|
||||
}
|
||||
lp.setEntries(entries);
|
||||
lp.setEntryValues(entryValues);
|
||||
lp.setSummary(LocaleManager.locale.getDisplayName(LocaleManager.locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
switch (key) {
|
||||
case Config.Key.ROOT_ACCESS:
|
||||
case Config.Key.SU_MULTIUSER_MODE:
|
||||
case Config.Key.SU_MNT_NS:
|
||||
app.mDB.setSettings(key, Utils.getPrefsInt(prefs, key));
|
||||
break;
|
||||
case Config.Key.DARK_THEME:
|
||||
requireActivity().recreate();
|
||||
break;
|
||||
case Config.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 Config.Key.MAGISKHIDE:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.su("magiskhide --enable").submit();
|
||||
} else {
|
||||
Shell.su("magiskhide --disable").submit();
|
||||
}
|
||||
break;
|
||||
case Config.Key.LOCALE:
|
||||
LocaleManager.setLocale(app);
|
||||
requireActivity().recreate();
|
||||
break;
|
||||
case Config.Key.UPDATE_CHANNEL:
|
||||
case Config.Key.CUSTOM_CHANNEL:
|
||||
CheckUpdates.check();
|
||||
break;
|
||||
case Config.Key.CHECK_UPDATES:
|
||||
AppUtils.scheduleUpdateCheck();
|
||||
break;
|
||||
}
|
||||
setSummary(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(Preference preference) {
|
||||
String key = preference.getKey();
|
||||
switch (key) {
|
||||
case Config.Key.SU_FINGERPRINT:
|
||||
boolean checked = ((SwitchPreferenceCompat) preference).isChecked();
|
||||
((SwitchPreferenceCompat) preference).setChecked(!checked);
|
||||
new FingerprintAuthDialog(requireActivity(), () -> {
|
||||
((SwitchPreferenceCompat) preference).setChecked(checked);
|
||||
Config.set(key, checked);
|
||||
}).show();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setSummary(String key) {
|
||||
switch (key) {
|
||||
case Config.Key.UPDATE_CHANNEL:
|
||||
updateChannel.setSummary(getResources()
|
||||
.getStringArray(R.array.update_channel)
|
||||
[(int) Config.get(Config.Key.UPDATE_CHANNEL)]);
|
||||
break;
|
||||
case Config.Key.ROOT_ACCESS:
|
||||
rootConfig.setSummary(getResources()
|
||||
.getStringArray(R.array.su_access)
|
||||
[(int) Config.get(Config.Key.ROOT_ACCESS)]);
|
||||
break;
|
||||
case Config.Key.SU_AUTO_RESPONSE:
|
||||
autoRes.setSummary(getResources()
|
||||
.getStringArray(R.array.auto_response)
|
||||
[(int) Config.get(Config.Key.SU_AUTO_RESPONSE)]);
|
||||
break;
|
||||
case Config.Key.SU_NOTIFICATION:
|
||||
suNotification.setSummary(getResources()
|
||||
.getStringArray(R.array.su_notification)
|
||||
[(int) Config.get(Config.Key.SU_NOTIFICATION)]);
|
||||
break;
|
||||
case Config.Key.SU_REQUEST_TIMEOUT:
|
||||
requestTimeout.setSummary(
|
||||
getString(R.string.request_timeout_summary,
|
||||
(int) Config.get(Config.Key.SU_REQUEST_TIMEOUT)));
|
||||
break;
|
||||
case Config.Key.SU_MULTIUSER_MODE:
|
||||
multiuserConfig.setSummary(getResources()
|
||||
.getStringArray(R.array.multiuser_summary)
|
||||
[(int) Config.get(Config.Key.SU_MULTIUSER_MODE)]);
|
||||
break;
|
||||
case Config.Key.SU_MNT_NS:
|
||||
nsConfig.setSummary(getResources()
|
||||
.getStringArray(R.array.namespace_summary)
|
||||
[(int) Config.get(Config.Key.SU_MNT_NS)]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void setSummary() {
|
||||
setSummary(Config.Key.UPDATE_CHANNEL);
|
||||
setSummary(Config.Key.ROOT_ACCESS);
|
||||
setSummary(Config.Key.SU_AUTO_RESPONSE);
|
||||
setSummary(Config.Key.SU_NOTIFICATION);
|
||||
setSummary(Config.Key.SU_REQUEST_TIMEOUT);
|
||||
setSummary(Config.Key.SU_MULTIUSER_MODE);
|
||||
setSummary(Config.Key.SU_MNT_NS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
setLocalePreference((ListPreference) findPreference(Config.Key.LOCALE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.LOCALE_FETCH_DONE};
|
||||
}
|
||||
}
|
@@ -1,79 +0,0 @@
|
||||
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.adapters.SuLogAdapter;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class SuLogFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
@BindView(R.id.recyclerView) 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);
|
||||
unbinder = new SuLogFragment_ViewBinding(this, v);
|
||||
adapter = new SuLogAdapter(app.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:
|
||||
app.mDB.clearLogs();
|
||||
updateList();
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,63 +0,0 @@
|
||||
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.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;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class SuperuserFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
@BindView(R.id.empty_rv) 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);
|
||||
unbinder = new SuperuserFragment_ViewBinding(this, view);
|
||||
|
||||
pm = requireActivity().getPackageManager();
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
requireActivity().setTitle(getString(R.string.superuser));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
displayPolicyList();
|
||||
}
|
||||
|
||||
private void displayPolicyList() {
|
||||
List<Policy> policyList = app.mDB.getPolicyList();
|
||||
|
||||
if (policyList.size() == 0) {
|
||||
emptyRv.setVisibility(View.VISIBLE);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
} else {
|
||||
recyclerView.setAdapter(new PolicyAdapter(policyList, app.mDB, pm));
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
package com.topjohnwu.magisk.uicomponents;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.RotateAnimation;
|
||||
|
||||
public class ArrowExpandedViewHolder extends ExpandableViewHolder {
|
||||
|
||||
private View arrow;
|
||||
|
||||
public ArrowExpandedViewHolder(ViewGroup viewGroup, View arrow) {
|
||||
super(viewGroup);
|
||||
this.arrow = arrow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExpanded(boolean expanded) {
|
||||
super.setExpanded(expanded);
|
||||
if (arrow != null)
|
||||
arrow.setRotation(expanded ? 180 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expand() {
|
||||
super.expand();
|
||||
setRotate(new RotateAnimation(0, 180,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collapse() {
|
||||
super.collapse();
|
||||
setRotate(new RotateAnimation(180, 0,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f));
|
||||
}
|
||||
|
||||
private void setRotate(RotateAnimation rotate) {
|
||||
rotate.setDuration(300);
|
||||
rotate.setFillAfter(true);
|
||||
arrow.startAnimation(rotate);
|
||||
}
|
||||
}
|
@@ -1,72 +0,0 @@
|
||||
package com.topjohnwu.magisk.uicomponents;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
public class ExpandableViewHolder {
|
||||
|
||||
private ViewGroup expandLayout;
|
||||
private ValueAnimator expandAnimator, collapseAnimator;
|
||||
private boolean mExpanded = false;
|
||||
private int expandHeight = 0;
|
||||
|
||||
public ExpandableViewHolder(ViewGroup viewGroup) {
|
||||
expandLayout = viewGroup;
|
||||
setExpanded(false);
|
||||
expandLayout.getViewTreeObserver().addOnPreDrawListener(
|
||||
new ViewTreeObserver.OnPreDrawListener() {
|
||||
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
if (expandHeight == 0) {
|
||||
expandLayout.measure(0, 0);
|
||||
expandHeight = expandLayout.getMeasuredHeight();
|
||||
}
|
||||
|
||||
expandLayout.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
expandAnimator = slideAnimator(0, expandHeight);
|
||||
collapseAnimator = slideAnimator(expandHeight, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isExpanded() {
|
||||
return mExpanded;
|
||||
}
|
||||
|
||||
public void setExpanded(boolean expanded) {
|
||||
mExpanded = expanded;
|
||||
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
|
||||
layoutParams.height = expanded ? expandHeight : 0;
|
||||
expandLayout.setLayoutParams(layoutParams);
|
||||
}
|
||||
|
||||
public void expand() {
|
||||
if (mExpanded) return;
|
||||
expandLayout.setVisibility(View.VISIBLE);
|
||||
expandAnimator.start();
|
||||
mExpanded = true;
|
||||
}
|
||||
|
||||
public void collapse() {
|
||||
if (!mExpanded) return;
|
||||
collapseAnimator.start();
|
||||
mExpanded = false;
|
||||
}
|
||||
|
||||
private ValueAnimator slideAnimator(int start, int end) {
|
||||
ValueAnimator animator = ValueAnimator.ofInt(start, end);
|
||||
|
||||
animator.addUpdateListener(valueAnimator -> {
|
||||
int value = (Integer) valueAnimator.getAnimatedValue();
|
||||
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
|
||||
layoutParams.height = value;
|
||||
expandLayout.setLayoutParams(layoutParams);
|
||||
});
|
||||
return animator;
|
||||
}
|
||||
}
|
@@ -1,103 +0,0 @@
|
||||
package com.topjohnwu.magisk.uicomponents;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import com.caverock.androidsvg.SVG;
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.net.ResponseListener;
|
||||
import com.topjohnwu.signing.ByteArrayStream;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import ru.noties.markwon.Markwon;
|
||||
import ru.noties.markwon.SpannableConfiguration;
|
||||
import ru.noties.markwon.spans.AsyncDrawable;
|
||||
|
||||
public class MarkDownWindow {
|
||||
|
||||
private static final SpannableConfiguration config = SpannableConfiguration.builder(App.self)
|
||||
.asyncDrawableLoader(new Loader()).build();
|
||||
|
||||
public static void show(Activity activity, String title, String url) {
|
||||
Networking.get(url).getAsString(new Listener(activity, title));
|
||||
}
|
||||
|
||||
public static void show (Activity activity, String title, InputStream is) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ShellUtils.pump(is, baos);
|
||||
new Listener(activity, title).onResponse(baos.toString());
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
static class Listener implements ResponseListener<String> {
|
||||
|
||||
Activity activity;
|
||||
String title;
|
||||
|
||||
Listener(Activity a, String t) {
|
||||
activity = a;
|
||||
title = t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(String md) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
alert.setTitle(title);
|
||||
View mv = LayoutInflater.from(activity).inflate(R.layout.markdown_window, null);
|
||||
Markwon.setMarkdown(mv.findViewById(R.id.md_txt), config, md);
|
||||
alert.setView(mv);
|
||||
alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss());
|
||||
alert.show();
|
||||
}
|
||||
}
|
||||
|
||||
static class Loader implements AsyncDrawable.Loader {
|
||||
|
||||
@Override
|
||||
public void load(@NonNull String url, @NonNull AsyncDrawable asyncDrawable) {
|
||||
App.THREAD_POOL.submit((Callable<?>) () -> {
|
||||
InputStream is = Networking.get(url).execForInputStream().getResult();
|
||||
if (is == null)
|
||||
return null;
|
||||
ByteArrayStream buf = new ByteArrayStream();
|
||||
buf.readFrom(is);
|
||||
// First try default drawables
|
||||
Drawable drawable = Drawable.createFromStream(buf.getInputStream(), "");
|
||||
if (drawable == null) {
|
||||
// SVG
|
||||
SVG svg = SVG.getFromInputStream(buf.getInputStream());
|
||||
int width = Utils.dpInPx((int) svg.getDocumentWidth());
|
||||
int height = Utils.dpInPx((int) svg.getDocumentHeight());
|
||||
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
|
||||
final Canvas canvas = new Canvas(bitmap);
|
||||
float density = App.self.getResources().getDisplayMetrics().density;
|
||||
canvas.scale(density, density);
|
||||
svg.renderToCanvas(canvas);
|
||||
drawable = new BitmapDrawable(App.self.getResources(), bitmap);
|
||||
}
|
||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||
asyncDrawable.setResult(drawable);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel(@NonNull String url) {}
|
||||
}
|
||||
}
|
@@ -1,76 +0,0 @@
|
||||
package com.topjohnwu.magisk.uicomponents;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.DownloadProgressListener;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
public class ProgressNotification implements DownloadProgressListener {
|
||||
|
||||
private NotificationCompat.Builder builder;
|
||||
private Notification notification;
|
||||
private long prevTime;
|
||||
|
||||
public ProgressNotification(String title) {
|
||||
builder = Notifications.progress(title);
|
||||
prevTime = System.currentTimeMillis();
|
||||
update();
|
||||
Utils.toast(App.self.getString(R.string.downloading_toast, title), Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(long bytesDownloaded, long totalBytes) {
|
||||
long cur = System.currentTimeMillis();
|
||||
if (cur - prevTime >= 1000) {
|
||||
prevTime = cur;
|
||||
int progress = (int) (bytesDownloaded * 100 / totalBytes);
|
||||
builder.setProgress(100, progress, false);
|
||||
builder.setContentText(progress + "%");
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
public NotificationCompat.Builder getNotificationBuilder() {
|
||||
return builder;
|
||||
}
|
||||
|
||||
public Notification getNotification() {
|
||||
return notification;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
notification = builder.build();
|
||||
Notifications.mgr.notify(hashCode(), notification);
|
||||
}
|
||||
|
||||
private void lastUpdate() {
|
||||
notification = builder.build();
|
||||
Notifications.mgr.cancel(hashCode());
|
||||
Notifications.mgr.notify(notification.hashCode(), notification);
|
||||
}
|
||||
|
||||
public void dlDone() {
|
||||
builder.setProgress(0, 0, false)
|
||||
.setContentText(App.self.getString(R.string.download_complete))
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setOngoing(false);
|
||||
lastUpdate();
|
||||
}
|
||||
|
||||
public void dlFail() {
|
||||
builder.setProgress(0, 0, false)
|
||||
.setContentText(App.self.getString(R.string.download_file_error))
|
||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||
.setOngoing(false);
|
||||
lastUpdate();
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
Notifications.mgr.cancel(hashCode());
|
||||
}
|
||||
}
|
@@ -1,161 +0,0 @@
|
||||
package com.topjohnwu.magisk.uicomponents;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.dialogs.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.utils.ISafetyNetHelper;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import butterknife.BindColor;
|
||||
import butterknife.BindView;
|
||||
import butterknife.OnClick;
|
||||
import butterknife.Unbinder;
|
||||
import dalvik.system.DexClassLoader;
|
||||
|
||||
public class SafetyNet implements ISafetyNetHelper.Callback {
|
||||
|
||||
private static final File EXT_APK =
|
||||
new File(App.self.getFilesDir().getParent() + "/snet", "snet.apk");
|
||||
|
||||
@BindView(R.id.safetyNet_card) CardView safetyNetCard;
|
||||
@BindView(R.id.safetyNet_refresh) ImageView safetyNetRefreshIcon;
|
||||
@BindView(R.id.safetyNet_status) TextView safetyNetStatusText;
|
||||
@BindView(R.id.safetyNet_check_progress) ProgressBar safetyNetProgress;
|
||||
@BindView(R.id.safetyNet_expand) ViewGroup expandLayout;
|
||||
@BindView(R.id.cts_status_icon) ImageView ctsStatusIcon;
|
||||
@BindView(R.id.cts_status) TextView ctsStatusText;
|
||||
@BindView(R.id.basic_status_icon) ImageView basicStatusIcon;
|
||||
@BindView(R.id.basic_status) TextView basicStatusText;
|
||||
|
||||
@BindColor(R.color.red500) int colorBad;
|
||||
@BindColor(R.color.green500) int colorOK;
|
||||
|
||||
public Unbinder unbinder;
|
||||
private ExpandableViewHolder expandable;
|
||||
|
||||
public SafetyNet(View v) {
|
||||
unbinder = new SafetyNet_ViewBinding(this, v);
|
||||
expandable = new ExpandableViewHolder(expandLayout);
|
||||
Context context = v.getContext();
|
||||
safetyNetCard.setVisibility(hasGms(context) && Networking.checkNetworkStatus(context) ?
|
||||
View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@OnClick(R.id.safetyNet_refresh)
|
||||
void safetyNet(View v) {
|
||||
Runnable task = () -> {
|
||||
safetyNetProgress.setVisibility(View.VISIBLE);
|
||||
safetyNetRefreshIcon.setVisibility(View.INVISIBLE);
|
||||
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
|
||||
check((Activity) v.getContext());
|
||||
expandable.collapse();
|
||||
};
|
||||
if (!SafetyNet.EXT_APK.exists()) {
|
||||
// Show dialog
|
||||
new CustomAlertDialog((Activity) v.getContext())
|
||||
.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 reset() {
|
||||
safetyNetStatusText.setText(R.string.safetyNet_check_text);
|
||||
expandable.setExpanded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(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);
|
||||
|
||||
expandable.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);
|
||||
}
|
||||
}
|
||||
|
||||
private void dyRun(Activity activity) throws Exception {
|
||||
DexClassLoader loader = new DexClassLoader(EXT_APK.getPath(), EXT_APK.getParent(),
|
||||
null, ISafetyNetHelper.class.getClassLoader());
|
||||
Class<?> clazz = loader.loadClass("com.topjohnwu.snet.Snet");
|
||||
ISafetyNetHelper helper = (ISafetyNetHelper) clazz.getMethod("newHelper",
|
||||
Class.class, String.class, Activity.class, Object.class)
|
||||
.invoke(null, ISafetyNetHelper.class, EXT_APK.getPath(), activity, this);
|
||||
if (helper.getVersion() < Const.SNET_EXT_VER)
|
||||
throw new Exception();
|
||||
helper.attest();
|
||||
}
|
||||
|
||||
private void check(Activity activity) {
|
||||
try {
|
||||
dyRun(activity);
|
||||
} catch (Exception ignored) {
|
||||
Shell.sh("rm -rf " + EXT_APK.getParent()).exec();
|
||||
EXT_APK.getParentFile().mkdir();
|
||||
Networking.get(Const.Url.SNET_URL).getAsFile(EXT_APK, f -> {
|
||||
try {
|
||||
dyRun(activity);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
onResponse(-1);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasGms(Context context) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
PackageInfo info;
|
||||
try {
|
||||
info = pm.getPackageInfo("com.google.android.gms", 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
return info.applicationInfo.enabled;
|
||||
}
|
||||
}
|
@@ -1,79 +0,0 @@
|
||||
package com.topjohnwu.magisk.uicomponents;
|
||||
|
||||
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.ClassMap;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
public class Shortcuts {
|
||||
|
||||
public static void setup(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
ShortcutManager manager = context.getSystemService(ShortcutManager.class);
|
||||
manager.setDynamicShortcuts(getShortCuts(context));
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
|
||||
private static ArrayList<ShortcutInfo> getShortCuts(Context context) {
|
||||
ArrayList<ShortcutInfo> shortCuts = new ArrayList<>();
|
||||
boolean root = Shell.rootAccess();
|
||||
if (Utils.showSuperUser()) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(context, "superuser")
|
||||
.setShortLabel(context.getString(R.string.superuser))
|
||||
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
|
||||
.putExtra(Const.Key.OPEN_SECTION, "superuser")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_superuser))
|
||||
.setRank(0)
|
||||
.build());
|
||||
}
|
||||
if (root && (boolean) Config.get(Config.Key.MAGISKHIDE)) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(context, "magiskhide")
|
||||
.setShortLabel(context.getString(R.string.magiskhide))
|
||||
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
|
||||
.putExtra(Const.Key.OPEN_SECTION, "magiskhide")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_magiskhide))
|
||||
.setRank(1)
|
||||
.build());
|
||||
}
|
||||
if (!(boolean) Config.get(Config.Key.COREONLY) && root && Config.magiskVersionCode >= 0) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(context, "modules")
|
||||
.setShortLabel(context.getString(R.string.modules))
|
||||
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
|
||||
.putExtra(Const.Key.OPEN_SECTION, "modules")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_extension))
|
||||
.setRank(3)
|
||||
.build());
|
||||
shortCuts.add(new ShortcutInfo.Builder(context, "downloads")
|
||||
.setShortLabel(context.getString(R.string.downloads))
|
||||
.setIntent(new Intent(context, ClassMap.get(SplashActivity.class))
|
||||
.putExtra(Const.Key.OPEN_SECTION, "downloads")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_cloud_download))
|
||||
.setRank(2)
|
||||
.build());
|
||||
}
|
||||
return shortCuts;
|
||||
}
|
||||
}
|
@@ -1,48 +0,0 @@
|
||||
package com.topjohnwu.magisk.uicomponents;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
package com.topjohnwu.magisk.uicomponents;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class UpdateCardHolder {
|
||||
|
||||
@BindView(R.id.status_icon) public ImageView statusIcon;
|
||||
@BindView(R.id.progress) public ProgressBar progress;
|
||||
@BindView(R.id.status) public TextView status;
|
||||
@BindView(R.id.current_version) public TextView currentVersion;
|
||||
@BindView(R.id.latest_version) public TextView latestVersion;
|
||||
@BindView(R.id.additional) public TextView additional;
|
||||
@BindView(R.id.install) public Button install;
|
||||
|
||||
public View itemView;
|
||||
public Unbinder unbinder;
|
||||
|
||||
public UpdateCardHolder(LayoutInflater inflater, ViewGroup root) {
|
||||
itemView = inflater.inflate(R.layout.update_card, root, false);
|
||||
unbinder = new UpdateCardHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
|
||||
public void setClickable(View.OnClickListener listener) {
|
||||
itemView.setClickable(true);
|
||||
itemView.setFocusable(true);
|
||||
itemView.setOnClickListener(listener);
|
||||
}
|
||||
|
||||
public void setValid(boolean valid) {
|
||||
progress.setVisibility(View.GONE);
|
||||
statusIcon.setVisibility(View.VISIBLE);
|
||||
if (valid) {
|
||||
install.setVisibility(View.VISIBLE);
|
||||
latestVersion.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
install.setVisibility(View.GONE);
|
||||
latestVersion.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
progress.setVisibility(View.VISIBLE);
|
||||
statusIcon.setVisibility(View.INVISIBLE);
|
||||
latestVersion.setVisibility(View.GONE);
|
||||
install.setVisibility(View.GONE);
|
||||
status.setText(R.string.checking_for_updates);
|
||||
}
|
||||
}
|
@@ -1,53 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.UpdateCheckService;
|
||||
import com.topjohnwu.magisk.tasks.CheckUpdates;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import androidx.work.Constraints;
|
||||
import androidx.work.ExistingPeriodicWorkPolicy;
|
||||
import androidx.work.NetworkType;
|
||||
import androidx.work.PeriodicWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
public class AppUtils {
|
||||
|
||||
public static void scheduleUpdateCheck() {
|
||||
if (Config.get(Config.Key.CHECK_UPDATES)) {
|
||||
Constraints constraints = new Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build();
|
||||
PeriodicWorkRequest request = new PeriodicWorkRequest
|
||||
.Builder(ClassMap.get(UpdateCheckService.class), 12, TimeUnit.HOURS)
|
||||
.setConstraints(constraints)
|
||||
.build();
|
||||
WorkManager.getInstance().enqueueUniquePeriodicWork(
|
||||
Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID,
|
||||
ExistingPeriodicWorkPolicy.REPLACE, request);
|
||||
} else {
|
||||
WorkManager.getInstance().cancelUniqueWork(Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID);
|
||||
CheckUpdates.check();
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Utils.toast(R.string.open_link_failed_toast, Toast.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z"/>
|
||||
</vector>
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#757575"
|
||||
android:pathData="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z"/>
|
||||
</vector>
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M13,7h-2v2h2L13,7zM13,11h-2v6h2v-6zM17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19L7,19L7,5h10v14z"/>
|
||||
</vector>
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#757575"
|
||||
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
|
||||
</vector>
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#757575"
|
||||
android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
|
||||
</vector>
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#757575"
|
||||
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM18.92,8h-2.95c-0.32,-1.25 -0.78,-2.45 -1.38,-3.56 1.84,0.63 3.37,1.91 4.33,3.56zM12,4.04c0.83,1.2 1.48,2.53 1.91,3.96h-3.82c0.43,-1.43 1.08,-2.76 1.91,-3.96zM4.26,14C4.1,13.36 4,12.69 4,12s0.1,-1.36 0.26,-2h3.38c-0.08,0.66 -0.14,1.32 -0.14,2 0,0.68 0.06,1.34 0.14,2L4.26,14zM5.08,16h2.95c0.32,1.25 0.78,2.45 1.38,3.56 -1.84,-0.63 -3.37,-1.9 -4.33,-3.56zM8.03,8L5.08,8c0.96,-1.66 2.49,-2.93 4.33,-3.56C8.81,5.55 8.35,6.75 8.03,8zM12,19.96c-0.83,-1.2 -1.48,-2.53 -1.91,-3.96h3.82c-0.43,1.43 -1.08,2.76 -1.91,3.96zM14.34,14L9.66,14c-0.09,-0.66 -0.16,-1.32 -0.16,-2 0,-0.68 0.07,-1.35 0.16,-2h4.68c0.09,0.65 0.16,1.32 0.16,2 0,0.68 -0.07,1.34 -0.16,2zM14.59,19.56c0.6,-1.11 1.06,-2.31 1.38,-3.56h2.95c-0.96,1.65 -2.49,2.93 -4.33,3.56zM16.36,14c0.08,-0.66 0.14,-1.32 0.14,-2 0,-0.68 -0.06,-1.34 -0.14,-2h3.38c0.16,0.64 0.26,1.31 0.26,2s-0.1,1.36 -0.26,2h-3.38z"/>
|
||||
</vector>
|
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0" >
|
||||
<path
|
||||
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2c-1.1,0 -2,0.9 -2,2S10.9,8 12,8zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2c1.1,0 2,-0.9 2,-2S13.1,10 12,10zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2c1.1,0 2,-0.9 2,-2S13.1,16 12,16z"
|
||||
android:fillColor="#757575"/>
|
||||
</vector>
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#757575"
|
||||
android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
|
||||
</vector>
|
@@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/flashing_background_color"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/toolbar" />
|
||||
|
||||
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/button_panel"
|
||||
style="?android:buttonStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<Button
|
||||
android:id="@+id/close"
|
||||
style="?android:borderlessButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/close" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/save_logs"
|
||||
style="?android:borderlessButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/menuSaveLog" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/reboot"
|
||||
style="?android:borderlessButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/reboot" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:openDrawer="start">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:windowBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_marginTop="?attr/actionBarSize"
|
||||
android:id="@+id/content_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<include layout="@layout/toolbar" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/nav_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:fitsSystemWindows="true"
|
||||
app:menu="@menu/drawer" />
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
@@ -1,126 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/su_popup"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="?attr/colorBackgroundFloating"
|
||||
android:minWidth="350dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/request_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/su_request_title"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_weight="0" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxWidth="300dp"
|
||||
android:maxLines="1"
|
||||
android:minWidth="200dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/package_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxWidth="300dp"
|
||||
android:maxLines="1"
|
||||
android:minWidth="200dp"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/timeout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/warning"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_margin="5dp"
|
||||
android:drawablePadding="10dp"
|
||||
android:text="@string/su_warning"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
<LinearLayout
|
||||
style="?android:buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="bottom"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="30dp"
|
||||
android:paddingRight="30dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/deny_btn"
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/deny_with_str" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/grant_btn"
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/grant" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/fingerprint"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:padding="7dp"
|
||||
android:tint="?attr/colorAccent"
|
||||
app:srcCompat="@drawable/ic_fingerprint" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@@ -1,59 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/dialog_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="5dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/message"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="25dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingRight="25dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/button_panel"
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:measureWithLargestChild="true"
|
||||
android:minHeight="54dp"
|
||||
android:orientation="horizontal"
|
||||
android:padding="2dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/negative"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:layout_weight="1"
|
||||
android:maxLines="2" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/neutral"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_weight="1"
|
||||
android:maxLines="2" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/positive"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:layout_weight="1"
|
||||
android:maxLines="2" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="5dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:labelFor="@id/custom_url"
|
||||
android:text="@string/settings_update_custom_msg"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/custom_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textUri" />
|
||||
</LinearLayout>
|
@@ -1,24 +0,0 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tab"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:elevation="4dp"
|
||||
android:visibility="gone"
|
||||
app:tabPaddingEnd="20dp"
|
||||
app:tabPaddingStart="20dp">
|
||||
|
||||
</com.google.android.material.tabs.TabLayout>
|
||||
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:dividerHeight="@dimen/card_divider_space"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
@@ -1,52 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/coordinator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:dividerHeight="@dimen/card_divider_space"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/empty_rv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/no_modules_found"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="italic"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:layout_margin="@dimen/fab_padding"
|
||||
android:elevation="6dp"
|
||||
app:srcCompat="@drawable/ic_add"
|
||||
tools:fabSize="normal"
|
||||
tools:pressedTranslationZ="12dp" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/empty_rv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/no_modules_found"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="italic" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:dividerHeight="@dimen/card_divider_space"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
@@ -1,27 +0,0 @@
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/empty_rv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/no_apps_found"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="italic"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:dividerHeight="@dimen/card_divider_space"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
</LinearLayout>
|
@@ -1,87 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="?attr/cardStyle"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginLeft="@dimen/card_horizontal_margin"
|
||||
android:layout_marginTop="@dimen/card_vertical_margin"
|
||||
android:layout_marginRight="@dimen/card_horizontal_margin"
|
||||
android:layout_marginBottom="@dimen/card_vertical_margin"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
app:cardCornerRadius="@dimen/card_corner_radius"
|
||||
app:cardElevation="@dimen/card_elevation">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/info_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:gravity="end"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/app_name"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/package_name"
|
||||
app:layout_constraintEnd_toStartOf="@+id/checkbox"
|
||||
app:layout_constraintStart_toEndOf="@+id/app_icon"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/package_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/app_name"
|
||||
app:layout_constraintStart_toStartOf="@+id/app_name"
|
||||
app:layout_constraintTop_toBottomOf="@+id/app_name" />
|
||||
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginRight="5dp"
|
||||
android:checked="false"
|
||||
android:focusable="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/app_name"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_menu_overflow_material" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/txt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="monospace"
|
||||
android:textSize="10sp" />
|
@@ -1,119 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="?attr/cardStyle"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginLeft="@dimen/card_horizontal_margin"
|
||||
android:layout_marginTop="@dimen/card_vertical_margin"
|
||||
android:layout_marginRight="@dimen/card_horizontal_margin"
|
||||
android:layout_marginBottom="@dimen/card_vertical_margin"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
app:cardCornerRadius="@dimen/card_corner_radius"
|
||||
app:cardElevation="@dimen/card_elevation">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/version_name"
|
||||
app:layout_constraintEnd_toStartOf="@+id/checkbox"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/version_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
android:textStyle="bold|italic"
|
||||
app:layout_constraintBottom_toTopOf="@+id/author"
|
||||
app:layout_constraintEnd_toEndOf="@+id/title"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/author"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
android:textStyle="bold|italic"
|
||||
app:layout_constraintBottom_toTopOf="@+id/description"
|
||||
app:layout_constraintEnd_toEndOf="@+id/title"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/version_name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/notice"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/author" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notice"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/remove_file_created"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@color/red500"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/description" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginRight="5dp"
|
||||
android:focusable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/description"
|
||||
app:layout_constraintEnd_toStartOf="@+id/delete"
|
||||
app:layout_constraintStart_toEndOf="@+id/title"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:tint="@color/icon_grey"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/checkbox"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/checkbox"
|
||||
app:layout_constraintTop_toTopOf="@+id/checkbox"
|
||||
app:srcCompat="@drawable/ic_delete"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
@@ -1,223 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="?attr/cardStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginLeft="@dimen/card_horizontal_margin"
|
||||
android:layout_marginTop="@dimen/card_vertical_margin"
|
||||
android:layout_marginRight="@dimen/card_horizontal_margin"
|
||||
android:layout_marginBottom="@dimen/card_vertical_margin"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
app:cardCornerRadius="@dimen/card_corner_radius"
|
||||
app:cardElevation="@dimen/card_elevation">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:gravity="end"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/app_name"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/package_name"
|
||||
app:layout_constraintEnd_toStartOf="@+id/master_switch"
|
||||
app:layout_constraintStart_toEndOf="@+id/app_icon"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/package_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/arrow"
|
||||
app:layout_constraintStart_toStartOf="@id/app_name"
|
||||
app:layout_constraintTop_toBottomOf="@id/app_name" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/arrow"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:tint="?attr/imageColorTint"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/package_name"
|
||||
app:layout_constraintEnd_toEndOf="@+id/app_name"
|
||||
app:layout_constraintStart_toEndOf="@+id/package_name"
|
||||
app:layout_constraintTop_toTopOf="@+id/package_name"
|
||||
app:srcCompat="@drawable/ic_arrow" />
|
||||
|
||||
<View
|
||||
android:id="@+id/trigger"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/arrow"
|
||||
app:layout_constraintStart_toStartOf="@+id/arrow"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/master_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:checked="false"
|
||||
android:gravity="center_vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/app_name"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/expand_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/package_name">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bell"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:tint="@color/icon_grey"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/notification_switch"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_notifications" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/notification_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:checked="false"
|
||||
android:gravity="center_vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toEndOf="@+id/bell"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.4" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bug"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:tint="@color/icon_grey"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/logging_switch"
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_bug_report" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/logging_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:checked="false"
|
||||
android:gravity="center_vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@+id/bug"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.8" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/more_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:tint="@color/icon_grey"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/delete"
|
||||
app:layout_constraintStart_toStartOf="@id/guideline2"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_more" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:tint="@color/icon_grey"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/more_info"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_delete" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
@@ -1,121 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="?attr/cardStyle"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginLeft="@dimen/card_horizontal_margin"
|
||||
android:layout_marginTop="@dimen/card_vertical_margin"
|
||||
android:layout_marginRight="@dimen/card_horizontal_margin"
|
||||
android:layout_marginBottom="@dimen/card_vertical_margin"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
app:cardCornerRadius="@dimen/card_corner_radius"
|
||||
app:cardElevation="@dimen/card_elevation">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/version_name"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/version_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
android:textStyle="bold|italic"
|
||||
app:layout_constraintBottom_toTopOf="@+id/author"
|
||||
app:layout_constraintEnd_toEndOf="@+id/title"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/author"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
android:textStyle="bold|italic"
|
||||
app:layout_constraintBottom_toTopOf="@+id/description"
|
||||
app:layout_constraintEnd_toEndOf="@+id/title"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/version_name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/update_time"
|
||||
app:layout_constraintEnd_toEndOf="@id/title"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/author" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/update_time"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textStyle="bold|italic"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/title"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/description" />
|
||||
|
||||
<View
|
||||
android:id="@+id/info_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/update_time"
|
||||
app:layout_constraintEnd_toEndOf="@+id/title"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toTopOf="@+id/title" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/download"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="end"
|
||||
android:tint="@color/icon_grey"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/title"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_file_download_black" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:orientation="horizontal"
|
||||
android:padding="5dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/arrow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:tint="?attr/imageColorTint"
|
||||
app:srcCompat="@drawable/ic_arrow" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/date"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:text="2017/1/1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
</LinearLayout>
|
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.appcompat.widget.Toolbar android:id="@+id/toolbar"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:elevation="4dp"/>
|
@@ -1,8 +0,0 @@
|
||||
# v7.0.0
|
||||
- Major UI redesign!
|
||||
- Render Markdown natively (no more buggy WebView!)
|
||||
- Support down to Android 4.1 (native Magisk only support Android 4.2 though)
|
||||
- Significantly improve Magisk log disply performance
|
||||
- Fix post OTA scripts for A/B devices
|
||||
- Reduce memory usages when verifying and signing boot image
|
||||
- Drop support for Magisk lower than v18.0
|
@@ -1,189 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<resources>
|
||||
<string name="advanced_settings_title">إعدادات متقدمة</string>
|
||||
<string name="app_changelog">تغييرات التطبيق</string>
|
||||
<string name="app_translators">مترجم التطبيق</string>
|
||||
<string name="author">انشئ بواسطة %1$s</string>
|
||||
<string name="auto_response">استجابة تلقائية</string>
|
||||
<string name="checking_for_updates">البحث عن تحديثات…</string>
|
||||
<string name="checking_safetyNet_status">التحقق من حالة SafetyNet…</string>
|
||||
<string name="close">إغلاق</string>
|
||||
<string name="command">الأمر: %1$s</string>
|
||||
<string name="deny">رفض</string>
|
||||
<string name="deny_with_str">رفض%1$s</string>
|
||||
<string name="disable_file_created">سيتم تعطيل الإضافة في إعادة التشغيل التالي</string>
|
||||
<string name="disable_file_removed">سيتم تمكين الإضافة في إعادة التشغيل التالي</string>
|
||||
<string name="download">التنزيل</string>
|
||||
<string name="downloads">التنزيلات</string>
|
||||
<string name="forever">للابد</string>
|
||||
<string name="grant">سماح</string>
|
||||
<string name="install">التثبيت</string>
|
||||
<string name="installed">مثبت</string>
|
||||
<string name="internal_storage">"يتم تخزين الملف المضغوط في:
|
||||
[التخزين الداخلي]%1$s"</string>
|
||||
<string name="keep_dm_verity">إبقاء AVB 2.0/dm-verity</string>
|
||||
<string name="keep_force_encryption">الحفاظ علي قوه التشفير</string>
|
||||
<string name="log">السجل</string>
|
||||
<string name="log_is_empty">السجل فارغ</string>
|
||||
<string name="logs_cleared">تم حذف السجل بنجاح</string>
|
||||
<string name="magisk_update_title">تحديث Magisk جديد متوفر!</string>
|
||||
<string name="magisk_version_error">Magisk غير مثبت</string>
|
||||
<string name="menuClearLog">حذف السجل الآن</string>
|
||||
<string name="menuReload">إعادة تحميل</string>
|
||||
<string name="modules">الإضافات</string>
|
||||
<string name="multiuser_mode">وضع تعدد المستخدمين</string>
|
||||
<string name="no_apps_found">لا توجد تطبيقات</string>
|
||||
<string name="no_info_provided">(لم يتم توفير أي معلومات)</string>
|
||||
<string name="no_modules_found">لم يعثر على الإضافات</string>
|
||||
<string name="none">بدون</string>
|
||||
<string name="not_installed">غير مثبت</string>
|
||||
<string name="once">مرة</string>
|
||||
<string name="owner_manage_summary">يمكن للمالك فقط إدارة صلاحيات الروت وتلقي مطالبات الطلب</string>
|
||||
<string name="owner_only_summary">المالك فقط لديه صلاحيات الروت</string>
|
||||
<string name="prompt">طلب</string>
|
||||
<string name="reboot">إعادة التشغيل</string>
|
||||
<string name="release_notes">ملاحظات الإصدار</string>
|
||||
<string name="remove_file_created">سيتم حذف الإضافة في إعادة التشغيل التالي</string>
|
||||
<string name="remove_file_deleted">لن يتم حذف الإضافة في إعادة التشغيل التالي</string>
|
||||
<string name="repo_cache_cleared">تم مسح الذاكرة المؤقته للمستودع</string>
|
||||
<string name="repo_install_msg">هل تريد تثبيت %1$s ?</string>
|
||||
<string name="repo_install_title">تثبيت %1$s</string>
|
||||
<string name="request_timeout">مهلة الطلب</string>
|
||||
<string name="request_timeout_summary">%1$d ثانية</string>
|
||||
<string name="safetyNet_check_success">نجح فحص SafetyNet</string>
|
||||
<string name="safetyNet_check_text">انقر لبدء فحص SafetyNet</string>
|
||||
<string name="safetyNet_res_invalid">الاستجابة غير صالحه</string>
|
||||
<string name="settings">الإعدادات</string>
|
||||
<string name="settings_clear_cache_summary">حذف المعلومات المخزنة مؤقتا للمستودع على الانترنت، يجبر التطبيق لتحديث عبر الانترنت</string>
|
||||
<string name="settings_clear_cache_title">حذف الذاكرة المؤقتة للمستودع</string>
|
||||
<string name="settings_core_only_summary">تمكين الميزات الأساسية فقط، لن يتم تحميل جميع الإضافات. MagiskSU، MagiskHide، systemless hosts، و لا يزال ممكنا</string>
|
||||
<string name="settings_core_only_title">Magisk الوضع الأساسي فقط</string>
|
||||
<string name="settings_dark_theme_summary">تفعيل السمة الغامقة</string>
|
||||
<string name="settings_dark_theme_title">السمة الغامقة</string>
|
||||
<string name="settings_general_category">عام</string>
|
||||
<string name="settings_hosts_summary">Systemless يدعم تطبيقات حجب الإعلانات</string>
|
||||
<string name="settings_hosts_title">تمكين المضيفين(الهوست) لـ systemless</string>
|
||||
<string name="settings_magiskhide_summary">إخفاء Magisk من مختلف الاكتشافات</string>
|
||||
<string name="settings_owner_manage">إدارة مالك الجهاز</string>
|
||||
<string name="settings_owner_only">مالك الجهاز فقط</string>
|
||||
<string name="settings_reboot_toast">إعادة التشغيل لتطبيق الإعدادات</string>
|
||||
<string name="settings_su_adb">ADB فقط</string>
|
||||
<string name="settings_su_app">التطبيقات فقط</string>
|
||||
<string name="settings_su_app_adb">التطبيقات و ADB</string>
|
||||
<string name="settings_su_disable">معطل</string>
|
||||
<string name="settings_su_request_10">10 ثواني</string>
|
||||
<string name="settings_su_request_20">20 ثانية</string>
|
||||
<string name="settings_su_request_30">30 ثانية</string>
|
||||
<string name="settings_su_request_60">60 ثانية</string>
|
||||
<string name="settings_user_independent">مستخدم مستقل</string>
|
||||
<string name="sixtymin">60 دقائق</string>
|
||||
<string name="su_allow_toast">%1$s يتم منح صلاحيات Superuser</string>
|
||||
<string name="su_deny_toast">%1$s يتم رفض صلاحيات Superuser</string>
|
||||
<string name="su_request_title">Superuser طلبات</string>
|
||||
<string name="su_revoke_msg">تأكيد لسحب صلاحيات %1$s ?</string>
|
||||
<string name="su_revoke_title">سحب؟</string>
|
||||
<string name="su_snack_deny">Superuser الصلاحيات لـ %1$s تم رفضها</string>
|
||||
<string name="su_snack_grant">Superuser الصلاحيات لـ %1$s تم منحها</string>
|
||||
<string name="su_snack_log_off">السجلات لـ %1$s تم تعطيلها</string>
|
||||
<string name="su_snack_log_on">السجلات لـ %1$s تم تفعيلها</string>
|
||||
<string name="su_snack_notif_off">الإشعارات لـ %1$s تم تعطيلها</string>
|
||||
<string name="su_snack_notif_on">الإشعارات لـ %1$s تم تفعيلها</string>
|
||||
<string name="su_snack_revoke">%1$s الصلاحيات سحبت</string>
|
||||
<string name="su_warning">"منح حق الوصول الكامل إلى جهازك.
|
||||
رفض إذا كنت غير متأكد!"</string>
|
||||
<string name="superuser">Superuser</string>
|
||||
<string name="superuser_access">Superuser صلاحيات</string>
|
||||
<string name="superuser_notification">Superuser إشعارات</string>
|
||||
<string name="target_uid">الهدف UID: %1$d</string>
|
||||
<string name="tenmin">10 دقائق</string>
|
||||
<string name="thirtymin">30 دقائق</string>
|
||||
<string name="toast">ملاحظة منبثقة</string>
|
||||
<string name="translators">xx6600xx ,silent_6600</string>
|
||||
<string name="twentymin">20 دقائق</string>
|
||||
<string name="uninstall">إلغاء التثبيت</string>
|
||||
<string name="uninstall_magisk_title">إلغاء تثبيت Magisk</string>
|
||||
<string name="update_available">يتوفر تحديث</string>
|
||||
<string name="update_file_created">سيتم تحديث الإضافة في إعادة التشغيل التالي</string>
|
||||
<string name="user_indepenent_summary">كل مستخدم لديه قواعد روت منفصلة خاصة به</string>
|
||||
<string name="android_o_not_support">لا يدعم إصدار الأندرويد +8.0</string>
|
||||
<string name="auth_fail">فشل المصادقة</string>
|
||||
<string name="auth_fingerprint">مصادقة البصمة</string>
|
||||
<string name="boot_file_patch_msg">حدد الذاكرة المؤقتة لنسخة boot الأصلية بتنسيق img. أو .img.tar</string>
|
||||
<string name="complete_uninstall">إلغاء التثبيت بالكامل</string>
|
||||
<string name="direct_install">تثبيت مباشر (موصى به)</string>
|
||||
<string name="disable_fingerprint">لم يتم تعيين بصمات الأصابع أو لا يوجد جهاز مدعوم</string>
|
||||
<string name="download_zip_only">تحميل ملف zip فقط</string>
|
||||
<string name="dtbo_patched_reboot">قام مدير Magisk بتصحيح dtbo.img ، يرجى إعادة التشغيل</string>
|
||||
<string name="dtbo_patched_title">تم تصحيح DTBO!</string>
|
||||
<string name="env_fix_msg">يحتاج جهازك إلى إعداد إضافي لـ Magisk للعمل بشكل صحيح. سيتم تنزيل ملف zip لتثبيت Magisk ، هل تريد المتابعة الآن؟</string>
|
||||
<string name="env_fix_title">يتطلب إعداد إضافي</string>
|
||||
<string name="flashing">التثبيت</string>
|
||||
<string name="global_summary">تستخدم كافة جلسات الجذر مساحة الأسم ذات التركيب العالمي</string>
|
||||
<string name="hide_manager_fail_toast">فشل إخفاء مدير Magisk …</string>
|
||||
<string name="hide_manager_title">إخفاء مدير Magisk…</string>
|
||||
<string name="install_inactive_slot">التثبيت على فتحة غير نشطة (بعد OTA)</string>
|
||||
<string name="install_inactive_slot_msg">"سيتم إجبار جهازك للتمهيد على الفتحة غير النشطة الحالية بعد إعادة التشغيل!
|
||||
فقط استخدام هذا الخيار بعد الانتهاء من OTA.
|
||||
استمرار؟"</string>
|
||||
<string name="invalid_update_channel">قناة تحديث غير صالحة</string>
|
||||
<string name="isolate_summary">سيكون لكل جلسة جذر مساحة الاسم الخاصة بها معزولة</string>
|
||||
<string name="language">اللغة</string>
|
||||
<string name="update_channel">تحديثات Magisk</string>
|
||||
<string name="manager_download_install">اضغط للتنزيل والتثبيت</string>
|
||||
<string name="manager_update_title">تحديث مدير Magisk الجديد متوفر!</string>
|
||||
<string name="menuSaveLog">حفظ السجل</string>
|
||||
<string name="mount_namespace_mode">وضع تركيب مساحة الأسم</string>
|
||||
<string name="open_link_failed_toast">لم يتم العثور على تطبيق لفتح الرابط …</string>
|
||||
<string name="patch_boot_file">تصحيح ملف صورة boot</string>
|
||||
<string name="proprietary_notice">مدير Magisk هو FOSS ، والذي لا يحتوي على شفرة API الخاصة بشركة SafetyNet الخاصة بشركة Google.
|
||||
|
||||
هل تسمح لـ Magisk Manager بتنزيل ملحق (يحتوي على GoogleApiClient) لعمليات التحقق من SafetyNet؟ "</string>
|
||||
<string name="proprietary_title">تحميل رمز الملكية</string>
|
||||
<string name="reboot_bootloader">إعادة تمهيد إلى وضع Bootloader</string>
|
||||
<string name="reboot_download">إعادة تمهيد إلى وضع التحميل</string>
|
||||
<string name="reboot_recovery">إعادة تمهيد إلى وضع الإسترداد</string>
|
||||
<string name="requester_summary">"سترث جلسات الجذر مساحة الأسماء المطلوبة الخاصة بها"</string>
|
||||
<string name="restore_done">تمت الأستعادة!</string>
|
||||
<string name="restore_fail">النسخ الاحتياطي الأصلي غير موجود!</string>
|
||||
<string name="restore_img">استعادة الصور</string>
|
||||
<string name="restore_img_msg">الأستعادة …</string>
|
||||
<string name="safetyNet_api_error">خطأ SafetyNet API</string>
|
||||
<string name="select_method">حدد الطريقة</string>
|
||||
<string name="settings_boot_format_summary">"حدد تنسيق ملف boot المصحح للإخراج.
|
||||
اختر img. للتثبيت من خلال وضع fastboot/ التحميل ؛ اختر img.tar. للتثبيت مع ODIN. "</string>
|
||||
<string name="settings_boot_format_title">تنسيق إخراج ملف Boot المصحح</string>
|
||||
<string name="settings_check_update_summary">التحقق من التحديثات في الخلفية بشكل دوري</string>
|
||||
<string name="settings_check_update_title">تفقد التحديث</string>
|
||||
<string name="settings_hide_manager_summary">أعد حزم مدير Magisk مع اسم حزمة عشوائية</string>
|
||||
<string name="settings_hide_manager_title">إخفاء مدير Magisk</string>
|
||||
<string name="settings_ns_global">مساحة الاسم العالمية</string>
|
||||
<string name="settings_ns_isolate">مساحة الاسم المعزولة</string>
|
||||
<string name="settings_ns_requester">وراثة مساحة الاسم</string>
|
||||
<string name="settings_restore_manager_summary">استعادة مدير Magisk مع الحزمة الأصلية</string>
|
||||
<string name="settings_restore_manager_title">استعادة مدير Magisk</string>
|
||||
<string name="settings_su_fingerprint_summary">أستخدام ماسح بصمات الأصابع للسماح بطلبات المستخدم المتميز</string>
|
||||
<string name="settings_su_fingerprint_title">تمكين مصادقة البصمة</string>
|
||||
<string name="settings_su_reauth_summary">أعد المصادقة على صلاحيات المستخدم المتميز بعد إجراء ترقيات للتطبيق</string>
|
||||
<string name="settings_su_reauth_title">إعادة المصادقة بعد الترقية</string>
|
||||
<string name="settings_update">تحديث الاعدادات</string>
|
||||
<string name="settings_update_beta">بيتا</string>
|
||||
<string name="settings_update_channel_title">قناة التحديث</string>
|
||||
<string name="settings_update_custom">مخصص</string>
|
||||
<string name="settings_update_custom_msg">أدخل عنوان URL مخصص</string>
|
||||
<string name="settings_update_stable">مستقر</string>
|
||||
<string name="setup_done">تم الإعداد</string>
|
||||
<string name="setup_fail">فشل الإعداد</string>
|
||||
<string name="setup_msg">تشغيل إعداد البيئة…</string>
|
||||
<string name="setup_title">إعداد إضافي</string>
|
||||
<string name="sort_by_name">الترتيب حسب الاسم</string>
|
||||
<string name="sort_by_update">فرز حسب آخر تحديث</string>
|
||||
<string name="sorting_order">ترتيب الفرز</string>
|
||||
<string name="system_default">(أفتراضي النظام)</string>
|
||||
<string name="uninstall_magisk_msg">سيتم تعطيل/إزالة جميع الوحدات. ستتم إزالة الجذر ، وربما تشفير بياناتك إذا كانت بياناتك غير مشفرة حالياً</string>
|
||||
<string name="update">تحديث</string>
|
||||
<string name="updated_on">تم التحديث في: %1$s</string>
|
||||
<string name="warning">تحذير</string>
|
||||
<string name="downloading_toast">جاري التنزيل %1$s</string>
|
||||
<string name="download_file_error">خطأ تنزيل الملف</string>
|
||||
<string name="no_rw_storage">أن هذه الميزة لا تعمل دون الحصول على إذن الكتابة على التخزين الخارجي.</string>
|
||||
</resources>
|
@@ -1,212 +0,0 @@
|
||||
<resources>
|
||||
|
||||
<!--Welcome Activity-->
|
||||
<string name="modules">Модули</string>
|
||||
<string name="downloads">Изтегляния</string>
|
||||
<string name="superuser">Superuser</string>
|
||||
<string name="log">Дневник</string>
|
||||
<string name="settings">Настройки</string>
|
||||
<string name="install">Инсталиране</string>
|
||||
|
||||
<!--Status Fragment-->
|
||||
<string name="magisk_version_error">Magisk не е инсталиран.</string>
|
||||
<string name="checking_for_updates">Проверяваме за актуализации…</string>
|
||||
<string name="invalid_update_channel">Невалиден канал за актуализации</string>
|
||||
<string name="safetyNet_check_text">Докоснете за стартиране на SafetyNet проверката.</string>
|
||||
<string name="checking_safetyNet_status">Проверка статуса на SafetyNet…</string>
|
||||
<string name="safetyNet_check_success">SafetyNet проверката е успешна.</string>
|
||||
<string name="safetyNet_api_error">Грешка в SafetyNet ППИ</string>
|
||||
<string name="safetyNet_res_invalid">Невалиден отговор</string>
|
||||
|
||||
<!--Install Fragment-->
|
||||
<string name="advanced_settings_title">Допълнителни настройки</string>
|
||||
<string name="keep_force_encryption">Запазване на наложеното криптиране</string>
|
||||
<string name="keep_dm_verity">Запазване на AVB 2.0/dm-verity</string>
|
||||
<string name="current_installed">Инсталирана: %1$s</string>
|
||||
<string name="latest_version">Най-нова: %1$s</string>
|
||||
<string name="uninstall">Деинсталиране</string>
|
||||
<string name="uninstall_magisk_title">Деинсталиране на Magisk</string>
|
||||
<string name="uninstall_magisk_msg">Всички модули ще бъдат изключени/премахнати. Руут достъпът ще бъде премахнат и е възможно криптиране на данните Ви.</string>
|
||||
<string name="update">Актуализация</string>
|
||||
|
||||
<!--Module Fragment-->
|
||||
<string name="no_info_provided">(Не е представена информация)</string>
|
||||
<string name="no_modules_found">Няма намерени модули.</string>
|
||||
<string name="update_file_created">Модулът ще бъде обновен при следващото рестартиране.</string>
|
||||
<string name="remove_file_created">Модулът ще бъде премахнат при следващото рестартиране.</string>
|
||||
<string name="remove_file_deleted">Модулът няма да бъде премахнат при следващото рестартиране.</string>
|
||||
<string name="disable_file_created">Модулът ще бъде изключен при следващото рестартиране.</string>
|
||||
<string name="disable_file_removed">Модулът ще бъде активиран при следващото рестартиране.</string>
|
||||
<string name="author">Създаден от %1$s</string>
|
||||
<string name="reboot_recovery">Рестартиране в режима за възстановяване</string>
|
||||
<string name="reboot_bootloader">Рестартиране в буутлоудъра</string>
|
||||
<string name="reboot_download">Рестартиране в даунлоуд режима</string>
|
||||
|
||||
<!--Repo Fragment-->
|
||||
<string name="update_available">Налице е актуализация.</string>
|
||||
<string name="installed">Инсталирани</string>
|
||||
<string name="not_installed">Не са инсталирани</string>
|
||||
<string name="updated_on">Актуализиран на: %1$s</string>
|
||||
<string name="sorting_order">Сортиране</string>
|
||||
<string name="sort_by_name">Сортиране по наименование</string>
|
||||
<string name="sort_by_update">Сортиране по последно обновяване</string>
|
||||
|
||||
<!--Log Fragment-->
|
||||
<string name="menuSaveLog">Запазване на доклад</string>
|
||||
<string name="menuReload">Презареждане</string>
|
||||
<string name="menuClearLog">Изчистване на дневника</string>
|
||||
<string name="logs_cleared">Успешно изчистване на дневника.</string>
|
||||
<string name="log_is_empty">Дневникът е празен.</string>
|
||||
|
||||
<!--About Activity-->
|
||||
<string name="app_changelog">Списък с промени</string>
|
||||
<string name="translators" />
|
||||
<string name="app_translators">Преводачи</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="close">Затваряне</string>
|
||||
<string name="repo_install_title">Инсталиране на %1$s</string>
|
||||
<string name="repo_install_msg">Желаете ли да инсталирате %1$s сега?</string>
|
||||
<string name="download">Изтегляне</string>
|
||||
<string name="reboot">Рестартиране</string>
|
||||
<string name="magisk_update_title">Налице е нова версия на Magisk.</string>
|
||||
<string name="settings_reboot_toast">Трябва да рестартирате устройството, за да бъдат приложени настройките.</string>
|
||||
<string name="release_notes">Бележки</string>
|
||||
<string name="repo_cache_cleared">Кешът на хранилището е изчистен.</string>
|
||||
<string name="internal_storage">Архивът е записан във:\n[Вътрешната памет]%1$s.</string>
|
||||
<string name="manager_update_title">Налице е нова версия на Magisk Manager.</string>
|
||||
<string name="manager_download_install">Докоснете за изтегляне и инсталиране.</string>
|
||||
<string name="dtbo_patched_title">DTBO беше модифициран!</string>
|
||||
<string name="dtbo_patched_reboot">Magisk Manager модифицира dtbo.img, моля да рестартирате устройството.</string>
|
||||
<string name="update_channel">Актуализации на Magisk</string>
|
||||
<string name="flashing">Инсталиране</string>
|
||||
<string name="hide_manager_title">Скриване на Magisk Manager…</string>
|
||||
<string name="hide_manager_fail_toast">Скриването на Magisk Manager бе неуспешно.</string>
|
||||
<string name="open_link_failed_toast">Не бе намерено приложение за отваряне на линка.</string>
|
||||
<string name="download_zip_only">Изтегляне само на архив</string>
|
||||
<string name="patch_boot_file">Модифициране на Boot образа</string>
|
||||
<string name="direct_install">Директно инсталиране (Препоръчва се.)</string>
|
||||
<string name="install_inactive_slot">Инсталиране на неактивен слот (След OTA)</string>
|
||||
<string name="warning">Внимание</string>
|
||||
<string name="install_inactive_slot_msg">Вашето устройство НАЛОЖИТЕЛНО ще стартира текущия неактивен слот при следващото рестартиране.\nИзползвайте тази опция само след като приключи инсталирането на OTA.\nПродължаване?</string>
|
||||
. <string name="select_method">Избор на метод</string>
|
||||
<string name="boot_file_patch_msg">Изберете стоков boot образ с формат .img или .img.tar.</string>
|
||||
<string name="complete_uninstall">Пълно деинсталиране</string>
|
||||
<string name="restore_img">Възстановяване на образи</string>
|
||||
<string name="restore_img_msg">Възстановяване…</string>
|
||||
<string name="restore_done">Възстановяването е успешно!</string>
|
||||
<string name="restore_fail">Не е налице архив на стоковия образ!</string>
|
||||
<string name="proprietary_title">Изтегляне на патентования код</string>
|
||||
<string name="proprietary_notice">Magisk Manager е FOSS и затова не включва патентования код за SafetyNet ППИ на Google.\n\nПозволявате ли на Magisk Manager да изтегли добавката (включва GoogleApiClient) за SafetyNet проверки?</string>
|
||||
<string name="setup_done">Първоначалната настройка е готова.</string>
|
||||
<string name="setup_fail">Първоначалната настройка е неуспешна.</string>
|
||||
<string name="env_fix_title">Изисква допълнително настройване</string>
|
||||
<string name="env_fix_msg">Вашето устройство се нуждае от допълнително надстройване на Magisk, за да работи нормално. Ще бъде изтеглен архивът за надстройка на Magisk. Желаете ли да продължите?</string>
|
||||
<string name="setup_title">Допълнително надстройване</string>
|
||||
<string name="setup_msg">Надстройването на средата е в ход…</string>
|
||||
<string name="download_file_error">Грешка при изтеглянето на файла.</string>
|
||||
<string name="downloading_toast">Изтегляне на %1$s</string>
|
||||
<string name="no_rw_storage">Тази функция няма да работи без разрешение за запис във външната памет.</string>
|
||||
|
||||
<!--Settings Activity -->
|
||||
<string name="settings_general_category">Общи</string>
|
||||
<string name="settings_dark_theme_title">Тъмна тема</string>
|
||||
<string name="settings_dark_theme_summary">Включване на тъмната тема</string>
|
||||
<string name="settings_clear_cache_title">Изчистване кеша на хранилището</string>
|
||||
<string name="settings_clear_cache_summary">Изчистване на кешираната информация на онлайн хранилището за принудителното му обновяване.</string>
|
||||
<string name="settings_hide_manager_title">Скриване на Magisk Manager</string>
|
||||
<string name="settings_hide_manager_summary">Смяна пакетното наименование на Magisk Manager със случайно наименование.</string>
|
||||
<string name="settings_restore_manager_title">Възстановяване на Magisk Manager</string>
|
||||
<string name="settings_restore_manager_summary">Възстановяване на оригиналното пакетно наименование на Magisk Manager.</string>
|
||||
<string name="language">Език</string>
|
||||
<string name="system_default">(Системен)</string>
|
||||
<string name="settings_update">Настройки за актуализиране</string>
|
||||
<string name="settings_check_update_title">Проверка за актуализации</string>
|
||||
<string name="settings_check_update_summary">Периодично проверяване за актуализации във фонов режим.</string>
|
||||
<string name="settings_update_channel_title">Канал за актуализации</string>
|
||||
<string name="settings_update_stable">Стабилен</string>
|
||||
<string name="settings_update_beta">Бета</string>
|
||||
<string name="settings_update_custom">Потребителски</string>
|
||||
<string name="settings_update_custom_msg">Въведете потребителски URL</string>
|
||||
<string name="settings_boot_format_title">Изходен формат за модифициран boot образ</string>
|
||||
<string name="settings_boot_format_summary">Избор на изходен формат за модифициран boot образ.\nИзберете .img за инсталиране чрез fastboot/даунлоуд режима; изберете .img.tar за инсталиране чрез ODIN.</string>
|
||||
<string name="settings_core_only_title">Режим Magisk Core Only</string>
|
||||
<string name="settings_core_only_summary">Работят само основни функции, като MagiskSU и MagiskHide, без всякакви модули.</string>
|
||||
<string name="settings_magiskhide_summary">Скриване на Magisk от различни детектори.</string>
|
||||
<string name="settings_hosts_title">Несистемни хостове</string>
|
||||
<string name="settings_hosts_summary">Поддръжка на несистемни хостове за използване на приложения, блокиращи реклами.</string>
|
||||
<string name="settings_hosts_toast">Бе добавен модул с несистемни хостове.</string>
|
||||
|
||||
<string name="settings_su_app_adb">Приложения и ADB</string>
|
||||
<string name="settings_su_app">Само приложения</string>
|
||||
<string name="settings_su_adb">Само ADB</string>
|
||||
<string name="settings_su_disable">Изключен</string>
|
||||
<string name="settings_su_request_10">10 секунди</string>
|
||||
<string name="settings_su_request_15">15 секунди</string>
|
||||
<string name="settings_su_request_20">20 секунди</string>
|
||||
<string name="settings_su_request_30">30 секунди</string>
|
||||
<string name="settings_su_request_45">45 секунди</string>
|
||||
<string name="settings_su_request_60">60 секунди</string>
|
||||
<string name="superuser_access">Superuser достъп</string>
|
||||
<string name="auto_response">Автоматичен отговор</string>
|
||||
<string name="request_timeout">Време за запитване</string>
|
||||
<string name="superuser_notification">Superuser известие</string>
|
||||
<string name="request_timeout_summary">%1$d секунди</string>
|
||||
<string name="settings_su_reauth_title">Повторно запитване след актуализация</string>
|
||||
<string name="settings_su_reauth_summary">Повторно запитване за Superuser достъп след актуализация на приложенията.</string>
|
||||
<string name="settings_su_fingerprint_title">Superuser права само с пръстов отпечатък</string>
|
||||
<string name="settings_su_fingerprint_summary">Използване на сензора за пръстови отпечатъци за разрешаване на Superuser достъп.</string>
|
||||
<string name="auth_fingerprint">Удостоверете с пръстов отпечатък.</string>
|
||||
|
||||
<string name="multiuser_mode">Потребителски достъп</string>
|
||||
<string name="settings_owner_only">Само собственик</string>
|
||||
<string name="settings_owner_manage">Управление от страна на собственика</string>
|
||||
<string name="settings_user_independent">Независими потребители</string>
|
||||
<string name="owner_only_summary">Само собственикът има руут достъп.</string>
|
||||
<string name="owner_manage_summary">Само собственикът може да управлява руут достъпа и да получава запитвания за достъп.</string>
|
||||
<string name="user_indepenent_summary">Всеки потребител има собствени правила за руут достъп.</string>
|
||||
|
||||
<string name="mount_namespace_mode">Монтиране по именни пространства</string>
|
||||
<string name="settings_ns_global">Глобално</string>
|
||||
<string name="settings_ns_requester">Наследено</string>
|
||||
<string name="settings_ns_isolate">Изолирано</string>
|
||||
<string name="global_summary">Всички сесии с руут достъп използват глобалното именно пространство.</string>
|
||||
<string name="requester_summary">Всички сесии с руут достъп наследяват именното пространство на запитващото приложение.</string>
|
||||
<string name="isolate_summary">Всички сесии с руут достъп имат собствени именни пространства.</string>
|
||||
<string name="android_o_not_support">Не поддържа Android 8.0+.</string>
|
||||
<string name="disable_fingerprint">Не са добавени пръстови отпечатъци или устройството не поддържа тази функция.</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Запитване за Superuser достъп</string>
|
||||
<string name="deny_with_str">Отказ%1$s</string>
|
||||
<string name="deny">Отказ</string>
|
||||
<string name="prompt">Запитване</string>
|
||||
<string name="grant">Разрешаване</string>
|
||||
<string name="su_warning">Дава пълен достъп до устройството Ви.\Откажете, ако не сте сигурен/на.</string>
|
||||
<string name="forever">Завинаги</string>
|
||||
<string name="once">Веднъж</string>
|
||||
<string name="tenmin">10 мин</string>
|
||||
<string name="twentymin">20 мин</string>
|
||||
<string name="thirtymin">30 мин</string>
|
||||
<string name="sixtymin">60 мин</string>
|
||||
<string name="su_allow_toast">На %1$s бе разрешен Superuser достъп.</string>
|
||||
<string name="su_deny_toast">На %1$s бе отказан Superuser достъп.</string>
|
||||
<string name="no_apps_found">Няма намерени приложения.</string>
|
||||
<string name="su_snack_grant">На %1$s е предоставен Superuser достъп.</string>
|
||||
<string name="su_snack_deny">На %1$s е отказан Superuser достъп.</string>
|
||||
<string name="su_snack_notif_on">Известията за %1$s са включени.</string>
|
||||
<string name="su_snack_notif_off">Известията за %1$s са изключени.</string>
|
||||
<string name="su_snack_log_on">Записването в дневника за %1$s е включено.</string>
|
||||
<string name="su_snack_log_off">Записването в дневника за %1$s е изключено.</string>
|
||||
<string name="su_snack_revoke">Настройките за достъп на %1$s са анулирани.</string>
|
||||
<string name="su_revoke_title">Анулиране?</string>
|
||||
<string name="su_revoke_msg">Потвърждавате ли анулирането на настройките за достъп на %1$s?</string>
|
||||
<string name="toast">Toast</string>
|
||||
<string name="none">Без</string>
|
||||
<string name="auth_fail">Неуспешна заверка.</string>
|
||||
|
||||
<!--Superuser logs-->
|
||||
<string name="target_uid">Целеви UID: %1$d</string>
|
||||
<string name="command">Команда: %1$s</string>
|
||||
|
||||
</resources>
|
@@ -1,224 +0,0 @@
|
||||
<resources>
|
||||
|
||||
<!--Welcome Activity-->
|
||||
<string name="modules">Mòduls</string>
|
||||
<string name="downloads">Baixada</string>
|
||||
<string name="superuser">Superusuari</string>
|
||||
<string name="log">Registre</string>
|
||||
<string name="settings">Configuració</string>
|
||||
<string name="install">Instal·lar</string>
|
||||
<string name="unsupport_magisk_title">Versió de Magisk incompatible</string>
|
||||
<string name="unsupport_magisk_message">Aquesta versió de Magisk Manager no suporta una versió inferior a la 18.0.\n\nPots actualitzar Magisk manualment o pots instalar una versió anterior de la app.</string>
|
||||
|
||||
<!--Status Fragment-->
|
||||
<string name="magisk_version_error">Magisk no està instal·lat</string>
|
||||
<string name="checking_for_updates">Cercant actualitzacions</string>
|
||||
<string name="invalid_update_channel">Canal d\'actualització invàlid</string>
|
||||
<string name="safetyNet_check_text">Comprova l\'estat de SafetyNet</string>
|
||||
<string name="checking_safetyNet_status">Comprovant l\'estat de SafetyNet…</string>
|
||||
<string name="safetyNet_check_success">La comprovació ha sigut exitosa</string>
|
||||
<string name="safetyNet_api_error">Error en la API de SafetyNet</string>
|
||||
<string name="safetyNet_res_invalid">La resposta és invàlida</string>
|
||||
<string name="magisk_up_to_date">Magisk està actualitzat</string>
|
||||
<string name="manager_up_to_date">Magisk Manager està actualitzat</string>
|
||||
<string name="advanced_settings_title">Configuració avançada</string>
|
||||
<string name="keep_force_encryption">Mantenir el xifrat forçat</string>
|
||||
<string name="keep_dm_verity">Mantenir AVB 2.0/dm-verity</string>
|
||||
<string name="current_installed">Instal·lada: %1$s</string>
|
||||
<string name="latest_version">Última: %1$s</string>
|
||||
<string name="uninstall">Desinstal·lar</string>
|
||||
<string name="uninstall_magisk_title">Desinstal·lar Magisk</string>
|
||||
<string name="uninstall_magisk_msg">Tots els mòduls seran desactivats / eliminats. L\'accés Arrel s\'eliminarà i, possiblement, xifrarà totes les dades si no estan ja xifrades.</string>
|
||||
<string name="update">Actualització</string>
|
||||
<string name="core_only_enabled">(Mode únicament nucli activat)</string>
|
||||
|
||||
<!--Module Fragment-->
|
||||
<string name="no_info_provided">(No hi ha informació)</string>
|
||||
<string name="no_modules_found">No s\’han trobat mòduls</string>
|
||||
<string name="update_file_created">El mòdul s\’actualitzarà en el següent reinici</string>
|
||||
<string name="remove_file_created">El mòdul s\’eliminarà en el següent reinici</string>
|
||||
<string name="remove_file_deleted"> El mòdul no s\’eliminarà en el següent reinici </string>
|
||||
<string name="disable_file_created"> El mòdul es desactivarà en el següent reinici </string>
|
||||
<string name="disable_file_removed"> El mòdul s\’activarà en el següent reinici </string>
|
||||
<string name="author">Creat per %1$s</string>
|
||||
<string name="reboot_recovery">Reiniciar en Mode Recuperació</string>
|
||||
<string name="reboot_bootloader">Reiniciar en Mode Bootloader</string>
|
||||
<string name="reboot_download">Reiniciar en Mode Download</string>
|
||||
|
||||
<!--Repo Fragment-->
|
||||
<string name="update_available">Actualització Disponible</string>
|
||||
<string name="installed">Instal·lat</string>
|
||||
<string name="not_installed">No Instal·lat</string>
|
||||
<string name="updated_on">Actualitzat el: %1$s</string>
|
||||
<string name="sorting_order">Ordre de Classificació</string>
|
||||
<string name="sort_by_name">Ordenar per nom</string>
|
||||
<string name="sort_by_update">Ordenar segons l\’última actualització</string>
|
||||
|
||||
<!--Log Fragment-->
|
||||
<string name="menuSaveLog">Guardar registre</string>
|
||||
<string name="menuReload">Recarregar</string>
|
||||
<string name="menuClearLog">Netejar registre ara</string>
|
||||
<string name="logs_cleared">Registre netejat correctament</string>
|
||||
<string name="log_is_empty">El registre està buit</string>
|
||||
|
||||
<!--About Activity-->
|
||||
<string name="app_changelog">Registre de canvis</string>
|
||||
<string name="translators" />
|
||||
<string name="app_translators">JoanVC100, QuitusAnbu27</string>
|
||||
|
||||
<!-- System Components, Notifications -->
|
||||
<string name="update_channel">Actualització de Magisk</string>
|
||||
<string name="progress_channel">Notificacions de progrés</string>
|
||||
<string name="download_complete">Baixada completada</string>
|
||||
<string name="download_file_error">Error en baixar l\'arxiu</string>
|
||||
<string name="magisk_update_title">Actualització de Magisk disponible!</string>
|
||||
<string name="manager_update_title">Actualització de Magisk Manager disponible!</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="close">Tancar</string>
|
||||
<string name="repo_install_title">Instal·lar %1$s</string>
|
||||
<string name="repo_install_msg">Vols instal·lar %1$s ara?</string>
|
||||
<string name="download">Baixar</string>
|
||||
<string name="reboot">Reiniciar</string>
|
||||
<string name="settings_reboot_toast">Reinicia per aplicar els canvis</string>
|
||||
<string name="release_notes">Notes de llançament</string>
|
||||
<string name="repo_cache_cleared">Memòria cau del repositori netejada</string>
|
||||
<string name="internal_storage">El zip està emmagatzemat en:\n[Internal Storage]%1$s</string>
|
||||
<string name="manager_download_install">Prem per descarregar i instal·lar.</string>
|
||||
<string name="dtbo_patched_title">S\'ha arranjat DTBO</string>
|
||||
<string name="dtbo_patched_reboot">Magisk Manager ha arranjat dtbo.img. Si us plau, reinicia el telèfon.</string>
|
||||
<string name="flashing">Arranjament</string>
|
||||
<string name="hide_manager_title">Amagant Magisk Manager…</string>
|
||||
<string name="hide_manager_fail_toast">L\'amagament de Magisk Manager ha fallat.</string>
|
||||
<string name="open_link_failed_toast">No s\'ha trobat una aplicació per obrir l\'enllaç.</string>
|
||||
<string name="download_zip_only">Descarrega només el ZIP</string>
|
||||
<string name="patch_boot_file">Arranja l\'imatge d\'arrencada</string>
|
||||
<string name="direct_install">Instal·lació directa (Recomanat)</string>
|
||||
<string name="install_inactive_slot">Instal·lar a la ranura inactiva (Després d\'una OTA)</string>
|
||||
<string name="warning">Avís</string>
|
||||
<string name="install_inactive_slot_msg">El teu dispositiu serà FORÇAT a arrancar en la ranura inactiva després de reiniciar!\nUtilitza aquesta opció només quan s\'hagi fet la OTA.\nContinuar?</string>
|
||||
<string name="select_method">Selecciona mètode</string>
|
||||
<string name="boot_file_patch_msg">Seleccioneu l\'arxiu de bolcat de l\'arrencada en format .img o img.tar.</string>
|
||||
<string name="complete_uninstall">Desinstal·lació completa</string>
|
||||
<string name="restore_img">Restaura imatges</string>
|
||||
<string name="restore_img_msg">Restaurant…</string>
|
||||
<string name="restore_done">Restauració feta!</string>
|
||||
<string name="restore_fail">La còpia de seguretat de Stock no existeix!</string>
|
||||
<string name="proprietary_title">Descarrega codi propietari</string>
|
||||
<string name="proprietary_notice">Magisk Manager és codi lliure i no conté codi de l\'API de SafetyNet, ja que és codi propietari de Google.\n\nPot permetre que Magisk Manager descarregui una extensió que conté el GoogleApiClient per poder fer la comprobació de SafetyNet?</string>
|
||||
<string name="setup_done">Instal·lació realitzada.</string>
|
||||
<string name="setup_fail">Instal·lació fallida.</string>
|
||||
<string name="env_fix_title">Es requereix instal·lació addicional</string>
|
||||
<string name="env_fix_msg">El teu dispositiu necessita instal·lació addicional per Magisk per funcionar correctament. Es descarregarà el ZIP d\'instal·lació de Magisk , vol procedir a l\'instalació ara?</string>
|
||||
<string name="setup_title">Instal·lació addicional</string>
|
||||
<string name="setup_msg">S\'està executant la configuració de l\'entorn…</string>
|
||||
<string name="downloading_toast">Descarregant %1$s</string>
|
||||
<string name="no_rw_storage">Aquesta funció no funcionara si no es té permís per escriure a l\'emmagatzematge.</string>
|
||||
<string name="dl_one_module">Descarrega només un mòdul a la vegada.</string>
|
||||
|
||||
<!--Settings Activity -->
|
||||
<string name="settings_general_category">General</string>
|
||||
<string name="settings_dark_theme_title">Tema obscur</string>
|
||||
<string name="settings_dark_theme_summary">Habilitar el tema obscur</string>
|
||||
<string name="settings_clear_cache_title">Netejar memòria cau del repositori</string>
|
||||
<string name="settings_clear_cache_summary">Neteja l’informació en memòria cau per als repositoris en línia, força a l’aplicació a actualitzar-se en línia.</string>
|
||||
<string name="settings_hide_manager_title">Amagar Magisk Manager</string>
|
||||
<string name="settings_hide_manager_summary">Re-empaquetar Magisk Manager amb un nom de paquet a l’atzar</string>
|
||||
<string name="settings_restore_manager_title">Restaurar Magisk Manager</string>
|
||||
<string name="settings_restore_manager_summary">Restaura Magisk Manager amb el nom de paquet original</string>
|
||||
<string name="language">Idioma</string>
|
||||
<string name="system_default">(Idioma del sistema)</string>
|
||||
<string name="settings_update">Configuració d’Actualització</string>
|
||||
<string name="settings_check_update_title">Comprovar Actualitzacions</string>
|
||||
<string name="settings_check_update_summary">Comprovar periòdicament en segon pla si existeixen actualitzacions</string>
|
||||
<string name="settings_update_channel_title">Canal d’Actualitzacions</string>
|
||||
<string name="settings_update_stable">Estable</string>
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_update_custom">Personalitzat</string>
|
||||
<string name="settings_update_custom_msg">Inserta una URL personalitzada</string>
|
||||
<string name="settings_boot_format_title">Arranja la imatge d’arrancada segons el tipus de format</string>
|
||||
<string name="settings_boot_format_summary">Seleccionar el format de sortida per arranjar la imatge d’arrancada.\nTriï .img per flashejar mitcançant fastboot/download; triï .img.tar per flashejar amb ODIN.</string>
|
||||
<string name="settings_core_only_title">Mode únicament nucli de Magisk</string>
|
||||
<string name="settings_core_only_summary">Habilitar només les funcions principals, no es carregaran tots els mòduls. MagiskSU y MagiskHide seguirán habilitats</string>
|
||||
<string name="settings_magiskhide_summary">Amagar Magisk de varies deteccions</string>
|
||||
<string name="settings_hosts_title">Systemless Hosts</string>
|
||||
<string name="settings_hosts_summary">Suport per aplicacions tipus Adblock fora de la partició del sistema</string>
|
||||
<string name="settings_hosts_toast">Agregat el mòdul Systemless Hosts</string>
|
||||
|
||||
<string name="settings_su_app_adb">Aplicacions y ADB</string>
|
||||
<string name="settings_su_app">Només aplicacions</string>
|
||||
<string name="settings_su_adb">Només ADB</string>
|
||||
<string name="settings_su_disable">Deshabilitat</string>
|
||||
<string name="settings_su_request_10">10 segons</string>
|
||||
<string name="settings_su_request_15">15 segons</string>
|
||||
<string name="settings_su_request_20">20 segons</string>
|
||||
<string name="settings_su_request_30">30 segons</string>
|
||||
<string name="settings_su_request_45">45 segons</string>
|
||||
<string name="settings_su_request_60">60 segons</string>
|
||||
<string name="superuser_access">Accés de superusuari</string>
|
||||
<string name="auto_response">Resposta automàtica</string>
|
||||
<string name="request_timeout">Temps de petició</string>
|
||||
<string name="superuser_notification">Notificació de superusuari</string>
|
||||
<string name="request_timeout_summary">%1$d segons</string>
|
||||
<string name="settings_su_reauth_title">Re-autenticació</string>
|
||||
<string name="settings_su_reauth_summary">Demanar permisos de superusuari novament si una aplicació es actualitzada o reinstal·lada</string>
|
||||
<string name="settings_su_fingerprint_title">Autenticació per Empremta Dactilar</string>
|
||||
<string name="settings_su_fingerprint_summary">Utilitza el sensor d’Empremta Dactilar per permetre les sol·licituds de superusuari</string>
|
||||
<string name="auth_fingerprint">Autenticar Emprempta Digital</string>
|
||||
|
||||
|
||||
<string name="multiuser_mode">Mode Multiusuari</string>
|
||||
<string name="settings_owner_only">Només Administrador del Dispositiu</string>
|
||||
<string name="settings_owner_manage">Administrador del Dispositiu</string>
|
||||
<string name="settings_user_independent">Usuari Independent</string>
|
||||
<string name="owner_only_summary">Només l\’administrador té accés d\'arrel</string>
|
||||
<string name="owner_manage_summary"> Només l\’administrador pot supervisar l’acces d\'arrel y rebre sol·licituds d’altres usuaris</string>
|
||||
<string name="user_indepenent_summary">Tots els usuaris tenen separades les seves pròpies regles d\'arrel </string>
|
||||
|
||||
<string name="mount_namespace_mode">Muntar Namespace </string>
|
||||
<string name="settings_ns_global">Namespace Global</string>
|
||||
<string name="settings_ns_requester">Heretar Namespace</string>
|
||||
<string name="settings_ns_isolate">Aïllar Namespace</string>
|
||||
<string name="global_summary">Totes les sessions d\'arrel utilitzen el suport Namespace Global</string>
|
||||
<string name="requester_summary">Les sessions d\'arrel heretaran les peticiones Namespace</string>
|
||||
<string name="isolate_summary">Totes les sessions d\'arrel tindran la seva pròpia Namespace</string>
|
||||
<string name="android_o_not_support">No es compatible amb Android 8.0+</string>
|
||||
<string name="disable_fingerprint">No s\’han establert empremtes dactilars o no existeix el suport del dispositiu</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Petició de superusuari</string>
|
||||
<string name="deny_with_str">Denegar%1$s</string>
|
||||
<string name="deny">Denegar</string>
|
||||
<string name="prompt">Preguntar</string>
|
||||
<string name="grant">Permetre</string>
|
||||
<string name="su_warning">Permet accés total al teu dispositiu.\nDenega si no n\'està segur!</string>
|
||||
<string name="forever">Sempre</string>
|
||||
<string name="once">Un cop </string>
|
||||
<string name="tenmin">10 mins</string>
|
||||
<string name="twentymin">20 mins</string>
|
||||
<string name="thirtymin">30 mins</string>
|
||||
<string name="sixtymin">60 mins</string>
|
||||
<string name="su_allow_toast">Permesos els drets de superusuari de %1$s</string>
|
||||
<string name="su_deny_toast">Denegats els drets de superusuari de %1$s</string>
|
||||
<string name="no_apps_found">No s’han trobat aplicacions</string>
|
||||
<string name="su_snack_grant">Drets de superusuari de %1$s permesos</string>
|
||||
<string name="su_snack_deny"> Drets de superusuari de %1$s denegats</string>
|
||||
<string name="su_snack_notif_on">Notificacions de %1$s habilitades</string>
|
||||
<string name="su_snack_notif_off">Notificacions de %1$s deshabilitades</string>
|
||||
<string name="su_snack_log_on">Registres de %1$s habilitats</string>
|
||||
<string name="su_snack_log_off">Registres de %1$s deshabilitats</string>
|
||||
<string name="su_snack_revoke">Anul·lats drets de %1$s</string>
|
||||
<string name="su_revoke_title">¿Revocar?</string>
|
||||
<string name="su_revoke_msg">Confirmi per revocar drets de %1$s</string>
|
||||
<string name="toast">Avís</string>
|
||||
<string name="none">Cap</string>
|
||||
<string name="auth_fail">Autenticació fallida</string>
|
||||
|
||||
<!--Superuser logs-->
|
||||
<string name="target_uid">UID de l\’objectiu: %1$d</string>
|
||||
<string name="command">Ordre: %1$s</string>
|
||||
|
||||
<!-- MagiskHide -->
|
||||
<string name="show_system_app">Ensenya apps del sistema</string>
|
||||
|
||||
</resources>
|
@@ -1,130 +0,0 @@
|
||||
<resources>
|
||||
<!--Universal-->
|
||||
|
||||
<!--Welcome Activity-->
|
||||
<string name="modules">Moduly</string>
|
||||
<string name="downloads">Stahování</string>
|
||||
<string name="superuser">Superuser</string>
|
||||
<string name="log">Log</string>
|
||||
<string name="settings">Nastavení</string>
|
||||
<string name="install">Instalovat</string>
|
||||
|
||||
<!--Status Fragment-->
|
||||
<string name="magisk_version_error">Magisk není nainstalován</string>
|
||||
|
||||
<string name="checking_for_updates">Kontrola aktualizací…</string>
|
||||
<string name="safetyNet_check_text">Kliknutím zahájíte SafetyNet kontrolu</string>
|
||||
<string name="checking_safetyNet_status">Kontrola stavu SafetyNet…</string>
|
||||
|
||||
<!--Install Fragment-->
|
||||
<string name="advanced_settings_title">Pokročilá Nastavení</string>
|
||||
<string name="keep_force_encryption">Udržet "force encryption"</string>
|
||||
<string name="keep_dm_verity">Udržet AVB 2.0/dm-verity</string>
|
||||
<string name="current_installed">Nainstalovaná: %1$s</string>
|
||||
<string name="latest_version">Poslední: %1$s</string>
|
||||
<string name="uninstall">Odinstalovat</string>
|
||||
<string name="uninstall_magisk_title">Odinstalovat Magisk</string>
|
||||
|
||||
<!--Module Fragment-->
|
||||
<string name="no_info_provided">(Žádné info)</string>
|
||||
<string name="no_modules_found">Žádný modul nenalezen</string>
|
||||
<string name="update_file_created">Modul bude aktualizován během příštího restartu</string>
|
||||
<string name="remove_file_created">Modul bude smazán během příštího restartu</string>
|
||||
<string name="remove_file_deleted">Modul nebude smazán během příštího restartu</string>
|
||||
<string name="disable_file_created">Modul bude zakázán během příštího restartu</string>
|
||||
<string name="disable_file_removed">Modul bude povolen během příštího restartu</string>
|
||||
<string name="author">Vytvořeno %1$s</string>
|
||||
|
||||
<!--Repo Fragment-->
|
||||
<string name="update_available">Dostupná Aktualizace</string>
|
||||
<string name="installed">Nainstalováno</string>
|
||||
<string name="not_installed">Nenainstalováno</string>
|
||||
|
||||
<!--Log Fragment-->
|
||||
<string name="menuSaveLog">Uložit log</string>
|
||||
<string name="menuReload">Aktualizovat</string>
|
||||
<string name="menuClearLog">Smazat Log</string>
|
||||
<string name="logs_cleared">Log byl smazán</string>
|
||||
<string name="log_is_empty">Log je prázdný</string>
|
||||
|
||||
<!--About Activity-->
|
||||
<string name="app_changelog">Seznam změn</string>
|
||||
<string name="translators" />
|
||||
<string name="app_translators">Překladatelé aplikace</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
|
||||
<string name="close">Zavřít</string>
|
||||
<string name="repo_install_title">Instalovat %1$s</string>
|
||||
<string name="repo_install_msg">Chcete nainstalovat %1$s ?</string>
|
||||
<string name="download">Stáhnout</string>
|
||||
<string name="reboot">Restart</string>
|
||||
<string name="magisk_update_title">K dispozici je aktualizace Magisk!</string>
|
||||
<string name="settings_reboot_toast">Restartovat pro použití nastavení</string>
|
||||
<string name="release_notes">Poznámky k vydání</string>
|
||||
<string name="repo_cache_cleared">Mezipaměť smazána</string>
|
||||
<string name="internal_storage">Zip je uchován v:\n[Interním Úložišti]%1$s</string>
|
||||
<string name="download_file_error">Chyba při Stahování souboru</string>
|
||||
<string name="downloading_toast">Stahování %1$s</string>
|
||||
<string name="no_rw_storage">Tato funkce nebude fungovat bez povolení k zápisu na externí úložiště.</string>
|
||||
|
||||
<!--Settings Activity -->
|
||||
<string name="settings_general_category">Obecné</string>
|
||||
<string name="settings_dark_theme_title">Tmavý Vzhled</string>
|
||||
<string name="settings_dark_theme_summary">Povolit tmavý vzhled</string>
|
||||
<string name="settings_clear_cache_title">Smazat Uchovanou Mezipaměť</string>
|
||||
<string name="settings_clear_cache_summary">Smaže informace online použití v Mezipaměti, donutí aplikaci obnovit informace online</string>
|
||||
|
||||
<string name="settings_magiskhide_summary">Skryje root (MagiskSU) před různými aplikacemi</string>
|
||||
<string name="settings_hosts_title">Nesystémová "hosts" data</string>
|
||||
<string name="settings_hosts_summary">Podpora nesystémových dat "hosts" pro Adblock aplikace</string>
|
||||
|
||||
<string name="settings_su_app_adb">Aplikace i ADB</string>
|
||||
<string name="settings_su_app">Pouze aplikace</string>
|
||||
<string name="settings_su_adb">Pouze ADB</string>
|
||||
<string name="settings_su_disable">Zakázáno</string>
|
||||
<string name="settings_su_request_10">10 sekund</string>
|
||||
<string name="settings_su_request_15">15 sekund</string>
|
||||
<string name="settings_su_request_20">20 sekund</string>
|
||||
<string name="settings_su_request_30">30 sekund</string>
|
||||
<string name="settings_su_request_45">45 sekund</string>
|
||||
<string name="settings_su_request_60">60 sekund</string>
|
||||
<string name="superuser_access">Přístup Superuser</string>
|
||||
<string name="auto_response">Automatická Reakce</string>
|
||||
<string name="request_timeout">Časový limit Požadavku</string>
|
||||
<string name="superuser_notification">Oznámení Superuser</string>
|
||||
<string name="request_timeout_summary">%1$d sekund</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Požadavek Superuser</string>
|
||||
<string name="deny_with_str">Zamítnout%1$s</string>
|
||||
<string name="deny">Zamítnout</string>
|
||||
<string name="prompt">Dotaz</string>
|
||||
<string name="grant">Povolit</string>
|
||||
<string name="su_warning">Povolí plný přístup k vašemu zařízení.\nZamítněte pokud si nejste jisti!</string>
|
||||
<string name="forever">Navždy</string>
|
||||
<string name="once">Jednou</string>
|
||||
<string name="tenmin">10 min</string>
|
||||
<string name="twentymin">20 min</string>
|
||||
<string name="thirtymin">30 min</string>
|
||||
<string name="sixtymin">60 min</string>
|
||||
<string name="su_allow_toast">Pro %1$s bylo oprávnění Superuser povoleno</string>
|
||||
<string name="su_deny_toast">Pro %1$s bylo oprávnění Superuser zamítnuto</string>
|
||||
<string name="no_apps_found">Zatím zde není žádná aplikace</string>
|
||||
<string name="su_snack_grant">Superuser oprávnění pro %1$s je povoleno</string>
|
||||
<string name="su_snack_deny">Superuser oprávnění pro %1$s je zamítnuto</string>
|
||||
<string name="su_snack_notif_on">Oznámení pro %1$s je povoleno</string>
|
||||
<string name="su_snack_notif_off">Oznámení pro %1$s je zakázáno</string>
|
||||
<string name="su_snack_log_on">Logování %1$s je povoleno</string>
|
||||
<string name="su_snack_log_off">Logování %1$s je zakázáno</string>
|
||||
<string name="su_snack_revoke">Záznamy oprávnění %1$s jsou smazány</string>
|
||||
<string name="su_revoke_title">Smazat?</string>
|
||||
<string name="su_revoke_msg">Smazat záznam ohledně oprávnění pro %1$s?</string>
|
||||
<string name="toast">Toast</string>
|
||||
<string name="none">Žádné</string>
|
||||
|
||||
<!--Superuser logs-->
|
||||
<string name="target_uid">Cílové UID: %1$d</string>
|
||||
<string name="command">Příkaz: %1$s</string>
|
||||
|
||||
</resources>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user