mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-18 06:27:37 +00:00
Compare commits
493 Commits
manager-v7
...
v20.4
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ba7cb47383 | ||
![]() |
48d417f9af | ||
![]() |
df4db6bf6b | ||
![]() |
b8ef491bc7 | ||
![]() |
ea1ebb8d00 | ||
![]() |
91b6d2852a | ||
![]() |
d7cd1b37f8 | ||
![]() |
160ff7bb07 | ||
![]() |
31142180cb | ||
![]() |
38b0fa04a8 | ||
![]() |
29817245ba | ||
![]() |
925fe6f152 | ||
![]() |
93fd574b75 | ||
![]() |
0de88bcbb9 | ||
![]() |
0b70bd2b60 | ||
![]() |
84ecba4629 | ||
![]() |
f7142e69b6 | ||
![]() |
ed7e560849 | ||
![]() |
47e50e8511 | ||
![]() |
72f6770d61 | ||
![]() |
7da35e5468 | ||
![]() |
7768274b2f | ||
![]() |
33f006655d | ||
![]() |
612b51d48f | ||
![]() |
8101f3f67d | ||
![]() |
e3c8d723e3 | ||
![]() |
4579825758 | ||
![]() |
ef91c33f55 | ||
![]() |
511d5993df | ||
![]() |
9f4958e869 | ||
![]() |
c07775f5e3 | ||
![]() |
e261579e72 | ||
![]() |
cf54cad3ce | ||
![]() |
a0998009c1 | ||
![]() |
d6fdbfe9b7 | ||
![]() |
07228279a3 | ||
![]() |
6877ef790f | ||
![]() |
a3809648dd | ||
![]() |
df15606b00 | ||
![]() |
4dc0d13688 | ||
![]() |
541fa5cb1f | ||
![]() |
ab9442d4ae | ||
![]() |
f5c099e9a7 | ||
![]() |
9582379e1b | ||
![]() |
db9a4b31f9 | ||
![]() |
409cb06ea0 | ||
![]() |
88d917b662 | ||
![]() |
faf077b494 | ||
![]() |
ee1f45aa91 | ||
![]() |
915fd3020b | ||
![]() |
642788abec | ||
![]() |
3cd11dd9a0 | ||
![]() |
bf2c5ce368 | ||
![]() |
65c510a211 | ||
![]() |
6fbc38d764 | ||
![]() |
200bf993d8 | ||
![]() |
38af82e152 | ||
![]() |
fc05f377fb | ||
![]() |
5c0e86383c | ||
![]() |
64f5ff5475 | ||
![]() |
758777111a | ||
![]() |
b90e0430f8 | ||
![]() |
0ce7da1bf6 | ||
![]() |
e6464c5c7f | ||
![]() |
c6b3f06b95 | ||
![]() |
581419b6a3 | ||
![]() |
696ab677be | ||
![]() |
0d229dac3b | ||
![]() |
3b8ea599f0 | ||
![]() |
3e70a61e33 | ||
![]() |
76f35d02b7 | ||
![]() |
356b417a04 | ||
![]() |
56147a80b5 | ||
![]() |
91728991d7 | ||
![]() |
0f7e59d288 | ||
![]() |
f33028c645 | ||
![]() |
f9149ad433 | ||
![]() |
0d7474cc88 | ||
![]() |
1e7e06d1cc | ||
![]() |
8453282fa6 | ||
![]() |
40f971d18a | ||
![]() |
ce7cb1eeae | ||
![]() |
d2701616da | ||
![]() |
10eb159e1b | ||
![]() |
36897ceb19 | ||
![]() |
9a8274130b | ||
![]() |
c8d050c3e3 | ||
![]() |
a46cd63c9d | ||
![]() |
e9e6eaf079 | ||
![]() |
cb5897af93 | ||
![]() |
d701d6eb82 | ||
![]() |
470ebb54e2 | ||
![]() |
632cab398e | ||
![]() |
189c4cc9d8 | ||
![]() |
70d5e2dee8 | ||
![]() |
c586106e51 | ||
![]() |
ffa85a616a | ||
![]() |
e5ea3e4a43 | ||
![]() |
0492e63862 | ||
![]() |
9952387356 | ||
![]() |
d7653e6e42 | ||
![]() |
e9fc40d285 | ||
![]() |
740559e3bc | ||
![]() |
9471577b3b | ||
![]() |
e85d5e54e2 | ||
![]() |
5fb071d80b | ||
![]() |
022151fefd | ||
![]() |
3b8d2fe8b7 | ||
![]() |
d51d549a28 | ||
![]() |
b5ac24f239 | ||
![]() |
3ca99005f8 | ||
![]() |
0b9f2921d2 | ||
![]() |
389501ad0c | ||
![]() |
082e4eb05c | ||
![]() |
47f885a566 | ||
![]() |
bc964b8588 | ||
![]() |
b57b3313e4 | ||
![]() |
f185cefa11 | ||
![]() |
9d256e02d7 | ||
![]() |
086c64c0be | ||
![]() |
798fe57025 | ||
![]() |
a03f744648 | ||
![]() |
64f35744c4 | ||
![]() |
b512528148 | ||
![]() |
fdfa037dca | ||
![]() |
db4ef1443d | ||
![]() |
810468c279 | ||
![]() |
8146d0830d | ||
![]() |
7e946b040c | ||
![]() |
97d24a7d4d | ||
![]() |
f8bea66313 | ||
![]() |
dd9129017f | ||
![]() |
cbe3602cb7 | ||
![]() |
1d831d65f3 | ||
![]() |
c35d020731 | ||
![]() |
c18db555a4 | ||
![]() |
373092af16 | ||
![]() |
1a2e157cda | ||
![]() |
b3bc1a3907 | ||
![]() |
4dd8d75cc0 | ||
![]() |
e5f50bb7e0 | ||
![]() |
45d5b4bea6 | ||
![]() |
ed58cf953a | ||
![]() |
ec26bc5ab7 | ||
![]() |
84e4bd3d41 | ||
![]() |
0ecfb63cd6 | ||
![]() |
ebdd6ec40c | ||
![]() |
0586760347 | ||
![]() |
d535f244ad | ||
![]() |
613d46824d | ||
![]() |
041355f182 | ||
![]() |
6977dc082f | ||
![]() |
d3dffe8165 | ||
![]() |
6812f9d202 | ||
![]() |
555e7cc907 | ||
![]() |
6180558068 | ||
![]() |
cf589f8c64 | ||
![]() |
e864919c0b | ||
![]() |
c72d83b637 | ||
![]() |
f2d2f28e23 | ||
![]() |
a7435dad6d | ||
![]() |
793f0b605c | ||
![]() |
5b56ca7ffc | ||
![]() |
5c988510b3 | ||
![]() |
290624844b | ||
![]() |
497efc9f5e | ||
![]() |
19d76b635c | ||
![]() |
4875def31c | ||
![]() |
155c0e3609 | ||
![]() |
00ea15dc19 | ||
![]() |
f04c4cb78a | ||
![]() |
6e4777692e | ||
![]() |
4638fdf2d7 | ||
![]() |
0783d385d5 | ||
![]() |
cf918e7df8 | ||
![]() |
1ba9faf35b | ||
![]() |
6e48294f2a | ||
![]() |
dea607b148 | ||
![]() |
e938e717b0 | ||
![]() |
2eed09ef1b | ||
![]() |
8a6b3644be | ||
![]() |
1d89fe503b | ||
![]() |
788db036fd | ||
![]() |
c38c473e11 | ||
![]() |
aef1f8f701 | ||
![]() |
83f9767254 | ||
![]() |
3e0352eee6 | ||
![]() |
28faff6425 | ||
![]() |
d0112f989c | ||
![]() |
9c4c310f46 | ||
![]() |
7bf7bfb9c6 | ||
![]() |
fbe776db0b | ||
![]() |
1e2de1bb14 | ||
![]() |
e395c9442f | ||
![]() |
30286f0ea5 | ||
![]() |
60ee742855 | ||
![]() |
a913ede48f | ||
![]() |
9592583783 | ||
![]() |
ad49d3ad26 | ||
![]() |
21ee73c2a3 | ||
![]() |
f5d0cc9f32 | ||
![]() |
b90c65370e | ||
![]() |
88920e0546 | ||
![]() |
d27773de03 | ||
![]() |
8abdaeb044 | ||
![]() |
9682d2f84a | ||
![]() |
a86b9e81e9 | ||
![]() |
a8bb7c68a3 | ||
![]() |
bdad29adab | ||
![]() |
fadcfe5f7a | ||
![]() |
fbd83b5ff3 | ||
![]() |
c351174fa4 | ||
![]() |
cc4f99fe28 | ||
![]() |
b2a9b88fe5 | ||
![]() |
da06e0ec76 | ||
![]() |
851ee81486 | ||
![]() |
0dc9f5c324 | ||
![]() |
36513c2301 | ||
![]() |
3a10597aed | ||
![]() |
2291be5d26 | ||
![]() |
345c3ef15e | ||
![]() |
c1dad11cb3 | ||
![]() |
12b219e7b2 | ||
![]() |
f8b48cf18d | ||
![]() |
12a9792c7d | ||
![]() |
ba55e2bc32 | ||
![]() |
c5e5b70e08 | ||
![]() |
327b186240 | ||
![]() |
5c1417e276 | ||
![]() |
0a2c99f1dc | ||
![]() |
836bfbdd02 | ||
![]() |
b13a35057a | ||
![]() |
c3e77b1ec1 | ||
![]() |
fb60bea659 | ||
![]() |
b2ddba4cbf | ||
![]() |
053251d566 | ||
![]() |
cf161a5dd9 | ||
![]() |
e4bcdbd0c4 | ||
![]() |
cae43b26f4 | ||
![]() |
b95cf9b9a3 | ||
![]() |
e6f443cb24 | ||
![]() |
087ccd69c9 | ||
![]() |
7532477a2f | ||
![]() |
433ae89e53 | ||
![]() |
de853a2651 | ||
![]() |
47c3045980 | ||
![]() |
dd50c19ba3 | ||
![]() |
707d7b3342 | ||
![]() |
ba1a2fbce4 | ||
![]() |
84f1e78660 | ||
![]() |
3490ba0a56 | ||
![]() |
1449486958 | ||
![]() |
9094cf7ce3 | ||
![]() |
df0a5b59f8 | ||
![]() |
0827044caf | ||
![]() |
342ae7c8cd | ||
![]() |
fc690b9f02 | ||
![]() |
22c9d836e0 | ||
![]() |
984997e73b | ||
![]() |
b39f407596 | ||
![]() |
615ad0cc5a | ||
![]() |
fcedd06e72 | ||
![]() |
6a2acbe929 | ||
![]() |
4cfff40475 | ||
![]() |
904948dc7d | ||
![]() |
7342509b2e | ||
![]() |
ed837ba26f | ||
![]() |
13262fdb18 | ||
![]() |
baf18a8762 | ||
![]() |
c0b56b927f | ||
![]() |
ea9947081f | ||
![]() |
e04f943980 | ||
![]() |
b38e940088 | ||
![]() |
bc0bb92f7a | ||
![]() |
8737be2623 | ||
![]() |
eb929160b3 | ||
![]() |
b8b0f257db | ||
![]() |
67b5f39df2 | ||
![]() |
7e9b3f1a60 | ||
![]() |
465aaeff82 | ||
![]() |
40c64d50d5 | ||
![]() |
89b1fa341b | ||
![]() |
3bda7cb26b | ||
![]() |
85a350b6c8 | ||
![]() |
eae4eff92f | ||
![]() |
848be8f806 | ||
![]() |
c79b79b37e | ||
![]() |
8a03c366b8 | ||
![]() |
37677f389c | ||
![]() |
2692234b8c | ||
![]() |
bfb5d7e5ac | ||
![]() |
8c818e707f | ||
![]() |
3efea47ca8 | ||
![]() |
89da45f9ac | ||
![]() |
34a0a00e3c | ||
![]() |
dec1094a59 | ||
![]() |
02e323133d | ||
![]() |
cb96b536a2 | ||
![]() |
627b40799c | ||
![]() |
73c4b21285 | ||
![]() |
78d7c45be3 | ||
![]() |
ac5ecf222e | ||
![]() |
a20594ed48 | ||
![]() |
cb59cc92a3 | ||
![]() |
cc7e47bbb6 | ||
![]() |
42606162b2 | ||
![]() |
e82bc1b7bc | ||
![]() |
4f0e1c6c61 | ||
![]() |
550f6aff7e | ||
![]() |
67c50d7504 | ||
![]() |
94f0c61619 | ||
![]() |
8a86b30fd1 | ||
![]() |
6379108a75 | ||
![]() |
fbeaad077f | ||
![]() |
8918113a31 | ||
![]() |
c5385b5b4c | ||
![]() |
35475e1d25 | ||
![]() |
fb2c292f35 | ||
![]() |
afc3fb10c7 | ||
![]() |
0a239c2fef | ||
![]() |
f5342a09d3 | ||
![]() |
f72de687c5 | ||
![]() |
833269fd0a | ||
![]() |
332c1a6c59 | ||
![]() |
0f1f43057e | ||
![]() |
784a7a7f24 | ||
![]() |
8e34baa59f | ||
![]() |
2926772bba | ||
![]() |
a7f4496db7 | ||
![]() |
f972f02fff | ||
![]() |
1c77e26c05 | ||
![]() |
59c5363933 | ||
![]() |
b744bb0a5a | ||
![]() |
0f140b408c | ||
![]() |
711799b194 | ||
![]() |
2105cacce3 | ||
![]() |
9d1d1710eb | ||
![]() |
c69dcf3e20 | ||
![]() |
eec5b37da1 | ||
![]() |
e1bda4ee8b | ||
![]() |
54930024f5 | ||
![]() |
c5f2f63458 | ||
![]() |
b2b81a5d0f | ||
![]() |
265dca3723 | ||
![]() |
495e734428 | ||
![]() |
82120cf47f | ||
![]() |
027a5695f2 | ||
![]() |
d6d82edff5 | ||
![]() |
a12eb3fc6f | ||
![]() |
6c84574366 | ||
![]() |
bc5cbe9fba | ||
![]() |
f83f92d3fa | ||
![]() |
19fd4dd89c | ||
![]() |
f941f5c0b0 | ||
![]() |
c7cad7e4aa | ||
![]() |
1c8988d3f7 | ||
![]() |
70a3dbe2b0 | ||
![]() |
efbb3ab25f | ||
![]() |
b0e7c65504 | ||
![]() |
b18b044b63 | ||
![]() |
8f5f8db717 | ||
![]() |
016e28383b | ||
![]() |
f1427e9279 | ||
![]() |
169e9ab5ad | ||
![]() |
dad52724db | ||
![]() |
d48e9d5d72 | ||
![]() |
24e2c3a5e9 | ||
![]() |
064523ef25 | ||
![]() |
85f293a44e | ||
![]() |
8e412bee5f | ||
![]() |
7d5555f82e | ||
![]() |
6720725d27 | ||
![]() |
fe5c65d798 | ||
![]() |
253f3cf1ba | ||
![]() |
db2e48b49f | ||
![]() |
5e089451af | ||
![]() |
6aa22267f4 | ||
![]() |
f76c020dd7 | ||
![]() |
722fba7805 | ||
![]() |
86551909fc | ||
![]() |
588e94c11d | ||
![]() |
9e66310c28 | ||
![]() |
93c422dce6 | ||
![]() |
7d6eebdae3 | ||
![]() |
f11bb609c9 | ||
![]() |
b910a92731 | ||
![]() |
ee7d297ca8 | ||
![]() |
a70c0174e1 | ||
![]() |
da707afa3f | ||
![]() |
a41597431c | ||
![]() |
d0b817381e | ||
![]() |
60a2e9b5dc | ||
![]() |
df3a37b0a3 | ||
![]() |
5f4718cd13 | ||
![]() |
3cc5cb3123 | ||
![]() |
8a2872afa4 | ||
![]() |
85941c4729 | ||
![]() |
82eeefb544 | ||
![]() |
f6061ba00e | ||
![]() |
9e3afcfe7a | ||
![]() |
21f2f86cb8 | ||
![]() |
04576ca828 | ||
![]() |
067cb0cd9d | ||
![]() |
17fb8f2298 | ||
![]() |
fbfc4e72ca | ||
![]() |
d2e171eabc | ||
![]() |
e50094af80 | ||
![]() |
93edf72993 | ||
![]() |
a230d63cf9 | ||
![]() |
2bb39bee2f | ||
![]() |
ce2ca5446a | ||
![]() |
8a014ff786 | ||
![]() |
dc09ec7598 | ||
![]() |
27fb0474d5 | ||
![]() |
7f0a87742a | ||
![]() |
47e236788c | ||
![]() |
236ad57608 | ||
![]() |
6d03798314 | ||
![]() |
c954a4f7bc | ||
![]() |
ba588d1097 | ||
![]() |
44f7c9a545 | ||
![]() |
b910db322b | ||
![]() |
c44a942fb7 | ||
![]() |
d713ad3499 | ||
![]() |
ddf40df649 | ||
![]() |
7c6d85221d | ||
![]() |
b66b82a6e9 | ||
![]() |
c44b85ea87 | ||
![]() |
fcbf56e93a | ||
![]() |
a539ffb188 | ||
![]() |
512f533a80 | ||
![]() |
96ef9cdbee | ||
![]() |
28fcbbcf7b | ||
![]() |
0f4326151f | ||
![]() |
e0e27774ad | ||
![]() |
1223b48b2c | ||
![]() |
d8338f0b48 | ||
![]() |
38019f7f42 | ||
![]() |
23978ef4d2 | ||
![]() |
3b4cb23112 | ||
![]() |
974cb1167f | ||
![]() |
6ccbc272c6 | ||
![]() |
0eb28c3265 | ||
![]() |
2daa131fb2 | ||
![]() |
51247d36c5 | ||
![]() |
37fa227fb5 | ||
![]() |
9dd272b357 | ||
![]() |
277298feae | ||
![]() |
ff24bc0b68 | ||
![]() |
700c51f95c | ||
![]() |
659914afbe | ||
![]() |
ee06aed94b | ||
![]() |
af1f5d5ab2 | ||
![]() |
4292ddd0ae | ||
![]() |
4a68fd65b6 | ||
![]() |
0e33632e79 | ||
![]() |
a9b20dae33 | ||
![]() |
e595937740 | ||
![]() |
72eb584e65 | ||
![]() |
8999a57f06 | ||
![]() |
8024089bde | ||
![]() |
5e01f785ae | ||
![]() |
d35d1b8860 | ||
![]() |
88027f2151 | ||
![]() |
cd41e7108b | ||
![]() |
6da566faff | ||
![]() |
df7a866617 | ||
![]() |
1cc8f13d54 | ||
![]() |
086ce63c6c | ||
![]() |
f1dcecc6cf | ||
![]() |
fe1ce08a6c | ||
![]() |
1d64ddb7f5 | ||
![]() |
823b121cc7 | ||
![]() |
149d35c687 | ||
![]() |
3a18e68751 | ||
![]() |
6afcc83955 | ||
![]() |
277d8773f2 | ||
![]() |
f161cf8b0a | ||
![]() |
dc62ae95a6 | ||
![]() |
f4ecc315d0 | ||
![]() |
cb2a1e57fe | ||
![]() |
1396faf433 | ||
![]() |
dc8d2ae683 | ||
![]() |
191c7c50b6 | ||
![]() |
c6725b0518 | ||
![]() |
4820a6e01c | ||
![]() |
57a9b5bc0c | ||
![]() |
8c224da5d5 | ||
![]() |
14e49f3c80 | ||
![]() |
cc8f1adca3 | ||
![]() |
122e2f7a8e | ||
![]() |
b4e1585e2b | ||
![]() |
a5830599c4 |
5
.gitattributes
vendored
5
.gitattributes
vendored
@@ -17,3 +17,8 @@ tools/** binary
|
|||||||
*.apk binary
|
*.apk binary
|
||||||
*.png binary
|
*.png binary
|
||||||
*.jpg binary
|
*.jpg binary
|
||||||
|
*.ttf binary
|
||||||
|
|
||||||
|
# Help GitHub detect languages
|
||||||
|
native/jni/external/** linguist-vendored
|
||||||
|
native/jni/systemproperties/** linguist-language=C++
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -22,6 +22,9 @@
|
|||||||
[submodule "mincrypt"]
|
[submodule "mincrypt"]
|
||||||
path = native/jni/external/mincrypt
|
path = native/jni/external/mincrypt
|
||||||
url = https://github.com/topjohnwu/mincrypt.git
|
url = https://github.com/topjohnwu/mincrypt.git
|
||||||
|
[submodule "pcre"]
|
||||||
|
path = native/jni/external/pcre
|
||||||
|
url = https://android.googlesource.com/platform/external/pcre
|
||||||
[submodule "termux-elf-cleaner"]
|
[submodule "termux-elf-cleaner"]
|
||||||
path = tools/termux-elf-cleaner
|
path = tools/termux-elf-cleaner
|
||||||
url = https://github.com/termux/termux-elf-cleaner.git
|
url = https://github.com/termux/termux-elf-cleaner.git
|
||||||
|
32
README.MD
32
README.MD
@@ -6,11 +6,18 @@
|
|||||||
|
|
||||||
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.
|
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).
|
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 almost perfectly hide modifications within userspace. Note that since 2020.3, the CTS check of [Google's SafetyNet API](https://developer.android.com/training/safetynet/index.html) will **NOT** pass.
|
||||||
|
|
||||||
## Bug Reports
|
## 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 are 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](https://github.com/topjohnwu/Magisk/issues) or directly in the thread.
|
**Only reports using debug canary builds will be accepted.** \
|
||||||
|
Access canary builds by upgrading to either canary Magisk Manager:
|
||||||
|
- [Canary Manager (Release)](https://raw.githubusercontent.com/topjohnwu/magisk_files/canary/app-release.apk)
|
||||||
|
- [Canary Manager (Debug)](https://raw.githubusercontent.com/topjohnwu/magisk_files/canary/app-debug.apk)
|
||||||
|
|
||||||
|
For installation issues, upload both boot image and install logs. \
|
||||||
|
For Magisk issues, upload boot logcat or dmesg. \
|
||||||
|
For Magisk Manager crashes, record and upload the logcat when the crash occurs.
|
||||||
|
|
||||||
## Building Environment Requirements
|
## Building Environment Requirements
|
||||||
|
|
||||||
@@ -37,27 +44,6 @@ Default string resources for Magisk Manager and its stub APK are located here:
|
|||||||
|
|
||||||
Translate each and place them in the respective locations (`[module]/src/main/res/values-[lang]/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
|
## License
|
||||||
|
|
||||||
Magisk, including all git submodules are free software:
|
Magisk, including all git submodules are free software:
|
||||||
|
@@ -19,6 +19,12 @@ android {
|
|||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
versionName props['appVersion']
|
versionName props['appVersion']
|
||||||
versionCode props['appVersionCode'] as Integer
|
versionCode props['appVersionCode'] as Integer
|
||||||
|
|
||||||
|
javaCompileOptions {
|
||||||
|
annotationProcessorOptions {
|
||||||
|
arguments = ["room.incremental":"true"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -26,7 +32,7 @@ android {
|
|||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
|
||||||
'proguard-rules.pro', 'proguard-kotlin.pro'
|
'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +68,7 @@ dependencies {
|
|||||||
implementation 'com.ncapdevi:frag-nav:3.2.0'
|
implementation 'com.ncapdevi:frag-nav:3.2.0'
|
||||||
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.6'
|
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.6'
|
||||||
|
|
||||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.13'
|
implementation 'io.reactivex.rxjava2:rxjava:2.2.18'
|
||||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
|
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||||
|
|
||||||
@@ -74,7 +80,7 @@ dependencies {
|
|||||||
implementation "${bindingAdapter}:${vBAdapt}"
|
implementation "${bindingAdapter}:${vBAdapt}"
|
||||||
implementation "${bindingAdapter}-recyclerview:${vBAdapt}"
|
implementation "${bindingAdapter}-recyclerview:${vBAdapt}"
|
||||||
|
|
||||||
def vMarkwon = '4.2.0'
|
def vMarkwon = '4.2.1'
|
||||||
implementation "io.noties.markwon:core:${vMarkwon}"
|
implementation "io.noties.markwon:core:${vMarkwon}"
|
||||||
implementation "io.noties.markwon:html:${vMarkwon}"
|
implementation "io.noties.markwon:html:${vMarkwon}"
|
||||||
implementation "io.noties.markwon:image:${vMarkwon}"
|
implementation "io.noties.markwon:image:${vMarkwon}"
|
||||||
@@ -89,47 +95,41 @@ dependencies {
|
|||||||
implementation "org.koin:koin-android:${vKoin}"
|
implementation "org.koin:koin-android:${vKoin}"
|
||||||
implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
|
implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
|
||||||
|
|
||||||
def vRetrofit = '2.6.2'
|
def vRetrofit = '2.7.1'
|
||||||
implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
|
implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
|
||||||
implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
|
implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
|
||||||
implementation "com.squareup.retrofit2:converter-scalars:${vRetrofit}"
|
implementation "com.squareup.retrofit2:converter-scalars:${vRetrofit}"
|
||||||
implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
|
implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
|
||||||
|
|
||||||
def vOkHttp = '3.12.6'
|
def vOkHttp = '3.12.10'
|
||||||
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
|
implementation("com.squareup.okhttp3:okhttp:${vOkHttp}") {
|
||||||
|
force = true
|
||||||
|
}
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
|
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
|
||||||
|
|
||||||
def vMoshi = '1.9.2'
|
def vMoshi = '1.10.0-SNAPSHOT'
|
||||||
implementation "com.squareup.moshi:moshi:${vMoshi}"
|
implementation "com.squareup.moshi:moshi:${vMoshi}"
|
||||||
|
kapt "com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}"
|
||||||
|
|
||||||
def vKotshi = '2.0.2'
|
def vRoom = '2.2.4'
|
||||||
implementation "se.ansman.kotshi:api:${vKotshi}"
|
implementation "androidx.room:room-runtime:${vRoom}"
|
||||||
kapt "se.ansman.kotshi:compiler:${vKotshi}"
|
|
||||||
|
|
||||||
modules {
|
|
||||||
module('androidx.room:room-runtime') {
|
|
||||||
replacedBy('com.github.topjohnwu:room-runtime')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def vRoom = '2.2.2'
|
|
||||||
implementation "com.github.topjohnwu:room-runtime:${vRoom}"
|
|
||||||
implementation "androidx.room:room-rxjava2:${vRoom}"
|
implementation "androidx.room:room-rxjava2:${vRoom}"
|
||||||
kapt "androidx.room:room-compiler:${vRoom}"
|
kapt "androidx.room:room-compiler:${vRoom}"
|
||||||
|
|
||||||
def vNav = '2.1.0'
|
def vNav = '2.2.1'
|
||||||
implementation "androidx.navigation:navigation-fragment-ktx:${vNav}"
|
implementation "androidx.navigation:navigation-fragment-ktx:${vNav}"
|
||||||
implementation "androidx.navigation:navigation-ui-ktx:${vNav}"
|
implementation "androidx.navigation:navigation-ui-ktx:${vNav}"
|
||||||
|
|
||||||
implementation 'androidx.biometric:biometric:1.0.0'
|
implementation 'androidx.biometric:biometric:1.0.1'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-beta01'
|
||||||
|
implementation 'androidx.browser:browser:1.2.0'
|
||||||
implementation 'androidx.preference:preference:1.1.0'
|
implementation 'androidx.preference:preference:1.1.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.2.0-rc03'
|
implementation 'androidx.fragment:fragment-ktx:1.2.2'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.work:work-runtime:2.3.3'
|
||||||
implementation 'androidx.work:work-runtime:2.2.0'
|
implementation 'androidx.transition:transition:1.3.1'
|
||||||
implementation 'androidx.transition:transition:1.3.0-rc02'
|
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
implementation 'androidx.core:core-ktx:1.1.0'
|
implementation 'androidx.core:core-ktx:1.2.0'
|
||||||
implementation 'com.google.android.material:material:1.2.0-alpha02'
|
implementation 'com.google.android.material:material:1.2.0-alpha03'
|
||||||
}
|
}
|
||||||
|
@@ -1,20 +0,0 @@
|
|||||||
## 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
|
|
43
app/proguard-rules.pro
vendored
43
app/proguard-rules.pro
vendored
@@ -16,33 +16,36 @@
|
|||||||
# public *;
|
# public *;
|
||||||
#}
|
#}
|
||||||
|
|
||||||
|
# Kotlin
|
||||||
|
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
||||||
|
public static void checkExpressionValueIsNotNull(...);
|
||||||
|
public static void checkNotNullExpressionValue(...);
|
||||||
|
public static void checkReturnedValueIsNotNull(...);
|
||||||
|
public static void checkFieldIsNotNull(...);
|
||||||
|
public static void checkParameterIsNotNull(...);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Stubs
|
||||||
|
-keep class a.* { *; }
|
||||||
|
|
||||||
# Snet
|
# Snet
|
||||||
-keepclassmembers class com.topjohnwu.magisk.utils.SafetyNetHelper { *; }
|
-keepclassmembers class com.topjohnwu.magisk.core.utils.SafetyNetHelper { *; }
|
||||||
-keep,allowobfuscation interface com.topjohnwu.magisk.utils.SafetyNetHelper$Callback
|
-keep,allowobfuscation interface com.topjohnwu.magisk.core.utils.SafetyNetHelper$Callback
|
||||||
-keepclassmembers class * implements com.topjohnwu.magisk.utils.SafetyNetHelper$Callback {
|
-keepclassmembers class * implements com.topjohnwu.magisk.core.utils.SafetyNetHelper$Callback {
|
||||||
void onResponse(int);
|
void onResponse(int);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Keep all fragment constructors
|
# Fragments
|
||||||
-keepclassmembers class * extends androidx.fragment.app.Fragment {
|
-keep,allowobfuscation class * extends androidx.fragment.app.Fragment
|
||||||
public <init>(...);
|
|
||||||
|
# Strip Timber verbose and debug logging
|
||||||
|
-assumenosideeffects class timber.log.Timber.Tree {
|
||||||
|
public void v(**);
|
||||||
|
public void d(**);
|
||||||
}
|
}
|
||||||
|
|
||||||
# DelegateWorker
|
|
||||||
-keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker
|
|
||||||
|
|
||||||
# BootSigner
|
|
||||||
-keep class a.a { *; }
|
|
||||||
|
|
||||||
# Workaround R8 bug
|
|
||||||
-keep,allowobfuscation class com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
|
||||||
-keepclassmembers class a.e { *; }
|
|
||||||
|
|
||||||
# Strip logging
|
|
||||||
-assumenosideeffects class timber.log.Timber.Tree { *; }
|
|
||||||
|
|
||||||
# Excessive obfuscation
|
# Excessive obfuscation
|
||||||
-repackageclasses 'a'
|
-repackageclasses
|
||||||
-allowaccessmodification
|
-allowaccessmodification
|
||||||
|
|
||||||
# QOL
|
# QOL
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.topjohnwu.magisk">
|
package="com.topjohnwu.magisk">
|
||||||
@@ -21,6 +20,10 @@
|
|||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<!-- Main -->
|
<!-- Main -->
|
||||||
|
@@ -1,19 +1,9 @@
|
|||||||
package a;
|
package a;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.PatchAPK;
|
|
||||||
import com.topjohnwu.signing.BootSigner;
|
import com.topjohnwu.signing.BootSigner;
|
||||||
|
|
||||||
public class a {
|
public class a {
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public static boolean patchAPK(String in, String out, String pkg) {
|
|
||||||
return PatchAPK.patch(in, out, pkg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean patchAPK(String in, String out, String pkg, String label) {
|
|
||||||
return PatchAPK.patch(in, out, pkg, label);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
BootSigner.main(args);
|
BootSigner.main(args);
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.ui.MainActivity;
|
|
||||||
|
|
||||||
public class b extends MainActivity {
|
|
||||||
/* stub */
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity;
|
|
||||||
|
|
||||||
public class c extends SplashActivity {
|
|
||||||
/* stub */
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.App;
|
|
||||||
|
|
||||||
public class e extends App {
|
|
||||||
public e() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public e(Object o) {
|
|
||||||
super(o);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashActivity;
|
|
||||||
|
|
||||||
public class f extends FlashActivity {
|
|
||||||
/* stub */
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.model.update.UpdateCheckService;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.work.WorkerParameters;
|
|
||||||
|
|
||||||
public class g extends w<UpdateCheckService> {
|
|
||||||
/* Stub */
|
|
||||||
public g(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
|
||||||
super(context, workerParams);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver;
|
|
||||||
|
|
||||||
public class h extends GeneralReceiver {
|
|
||||||
/* stub */
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.model.download.DownloadService;
|
|
||||||
|
|
||||||
public class j extends DownloadService {
|
|
||||||
/* stub */
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity;
|
|
||||||
|
|
||||||
public class m extends SuRequestActivity {
|
|
||||||
/* stub */
|
|
||||||
}
|
|
26
app/src/main/java/a/stubs.kt
Normal file
26
app/src/main/java/a/stubs.kt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package a
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.core.App
|
||||||
|
import com.topjohnwu.magisk.core.GeneralReceiver
|
||||||
|
import com.topjohnwu.magisk.core.SplashActivity
|
||||||
|
import com.topjohnwu.magisk.core.download.DownloadService
|
||||||
|
import com.topjohnwu.magisk.legacy.flash.FlashActivity
|
||||||
|
import com.topjohnwu.magisk.legacy.surequest.SuRequestActivity
|
||||||
|
import com.topjohnwu.magisk.ui.MainActivity
|
||||||
|
|
||||||
|
class b : MainActivity()
|
||||||
|
|
||||||
|
class c : SplashActivity()
|
||||||
|
|
||||||
|
class e : App {
|
||||||
|
constructor() : super()
|
||||||
|
constructor(o: Any) : super(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
class f : FlashActivity()
|
||||||
|
|
||||||
|
class h : GeneralReceiver()
|
||||||
|
|
||||||
|
class j : DownloadService()
|
||||||
|
|
||||||
|
class m : SuRequestActivity()
|
@@ -1,42 +0,0 @@
|
|||||||
package a;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.work.Worker;
|
|
||||||
import androidx.work.WorkerParameters;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.base.DelegateWorker;
|
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
|
|
||||||
public abstract class w<T extends DelegateWorker> extends Worker {
|
|
||||||
|
|
||||||
/* Wrapper class to workaround Proguard -keep class * extends Worker */
|
|
||||||
|
|
||||||
private T base;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
w(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
|
||||||
super(context, workerParams);
|
|
||||||
try {
|
|
||||||
base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
|
|
||||||
.getActualTypeArguments()[0]).newInstance();
|
|
||||||
base.attachWorker(this);
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Result doWork() {
|
|
||||||
if (base == null)
|
|
||||||
return Result.failure();
|
|
||||||
return base.doWork();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStopped() {
|
|
||||||
if (base != null)
|
|
||||||
base.onStopped();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,50 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.base
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.annotation.CallSuper
|
|
||||||
import androidx.databinding.DataBindingUtil
|
|
||||||
import androidx.databinding.ViewDataBinding
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import com.topjohnwu.magisk.BR
|
|
||||||
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
|
||||||
import com.topjohnwu.magisk.model.events.EventHandler
|
|
||||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
|
||||||
|
|
||||||
abstract class BaseFragment<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
|
|
||||||
Fragment(), EventHandler {
|
|
||||||
|
|
||||||
protected val activity get() = requireActivity() as BaseActivity<*, *>
|
|
||||||
protected lateinit var binding: Binding
|
|
||||||
protected abstract val layoutRes: Int
|
|
||||||
protected abstract val viewModel: ViewModel
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
viewModel.viewEvents.observe(this, viewEventObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
binding = DataBindingUtil.inflate<Binding>(inflater, layoutRes, container, false).apply {
|
|
||||||
setVariable(BR.viewModel, viewModel)
|
|
||||||
lifecycleOwner = this@BaseFragment
|
|
||||||
}
|
|
||||||
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
@CallSuper
|
|
||||||
override fun onEventDispatched(event: ViewEvent) {
|
|
||||||
super.onEventDispatched(event)
|
|
||||||
activity.onEventDispatched(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun onBackPressed(): Boolean = false
|
|
||||||
|
|
||||||
}
|
|
@@ -1,56 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.base
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.preference.*
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import org.koin.android.ext.android.inject
|
|
||||||
|
|
||||||
abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
|
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
|
||||||
|
|
||||||
protected val prefs: SharedPreferences by inject()
|
|
||||||
protected val activity get() = requireActivity() as BaseActivity<*, *>
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
val v = super.onCreateView(inflater, container, savedInstanceState)
|
|
||||||
prefs.registerOnSharedPreferenceChangeListener(this)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
prefs.unregisterOnSharedPreferenceChangeListener(this)
|
|
||||||
super.onDestroyView()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setAllPreferencesToAvoidHavingExtraSpace(preference: Preference) {
|
|
||||||
preference.isIconSpaceReserved = false
|
|
||||||
if (preference is PreferenceGroup)
|
|
||||||
for (i in 0 until preference.preferenceCount)
|
|
||||||
setAllPreferencesToAvoidHavingExtraSpace(preference.getPreference(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
|
|
||||||
if (preferenceScreen != null)
|
|
||||||
setAllPreferencesToAvoidHavingExtraSpace(preferenceScreen)
|
|
||||||
super.setPreferenceScreen(preferenceScreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateAdapter(preferenceScreen: PreferenceScreen?): RecyclerView.Adapter<*> =
|
|
||||||
object : PreferenceGroupAdapter(preferenceScreen) {
|
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
override fun onPreferenceHierarchyChange(preference: Preference?) {
|
|
||||||
if (preference != null)
|
|
||||||
setAllPreferencesToAvoidHavingExtraSpace(preference)
|
|
||||||
super.onPreferenceHierarchyChange(preference)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,35 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.base.viewmodel
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.base.BaseActivity
|
|
||||||
import com.topjohnwu.magisk.extensions.doOnSubscribeUi
|
|
||||||
import com.topjohnwu.magisk.model.events.BackPressEvent
|
|
||||||
import com.topjohnwu.magisk.model.events.PermissionEvent
|
|
||||||
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
|
||||||
import io.reactivex.Observable
|
|
||||||
import io.reactivex.subjects.PublishSubject
|
|
||||||
import com.topjohnwu.magisk.Info.isConnected as gIsConnected
|
|
||||||
|
|
||||||
|
|
||||||
abstract class BaseViewModel(
|
|
||||||
initialState: State = State.LOADING
|
|
||||||
) : LoadingViewModel(initialState) {
|
|
||||||
|
|
||||||
val isConnected = object : KObservableField<Boolean>(gIsConnected.value, gIsConnected) {
|
|
||||||
override fun get(): Boolean {
|
|
||||||
return gIsConnected.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun withView(action: BaseActivity<*, *>.() -> Unit) {
|
|
||||||
ViewActionEvent(action).publish()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun withPermissions(vararg permissions: String): Observable<Boolean> {
|
|
||||||
val subject = PublishSubject.create<Boolean>()
|
|
||||||
return subject.doOnSubscribeUi { PermissionEvent(permissions.toList(), subject).publish() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun back() = BackPressEvent().publish()
|
|
||||||
|
|
||||||
}
|
|
@@ -1,78 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.base.viewmodel
|
|
||||||
|
|
||||||
import androidx.databinding.Bindable
|
|
||||||
import com.topjohnwu.magisk.BR
|
|
||||||
import io.reactivex.*
|
|
||||||
|
|
||||||
abstract class LoadingViewModel(defaultState: State = State.LOADING) :
|
|
||||||
StatefulViewModel<LoadingViewModel.State>(defaultState) {
|
|
||||||
|
|
||||||
val loading @Bindable get() = state == State.LOADING
|
|
||||||
val loaded @Bindable get() = state == State.LOADED
|
|
||||||
val loadingFailed @Bindable get() = state == State.LOADING_FAILED
|
|
||||||
|
|
||||||
@Deprecated(
|
|
||||||
"Direct access is recommended since 0.2. This access method will be removed in 1.0",
|
|
||||||
ReplaceWith("state = State.LOADING", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
|
|
||||||
DeprecationLevel.WARNING
|
|
||||||
)
|
|
||||||
fun setLoading() {
|
|
||||||
state = State.LOADING
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated(
|
|
||||||
"Direct access is recommended since 0.2. This access method will be removed in 1.0",
|
|
||||||
ReplaceWith("state = State.LOADED", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
|
|
||||||
DeprecationLevel.WARNING
|
|
||||||
)
|
|
||||||
fun setLoaded() {
|
|
||||||
state = State.LOADED
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated(
|
|
||||||
"Direct access is recommended since 0.2. This access method will be removed in 1.0",
|
|
||||||
ReplaceWith("state = State.LOADING_FAILED", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
|
|
||||||
DeprecationLevel.WARNING
|
|
||||||
)
|
|
||||||
fun setLoadingFailed() {
|
|
||||||
state = State.LOADING_FAILED
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun notifyStateChanged() {
|
|
||||||
notifyPropertyChanged(BR.loading)
|
|
||||||
notifyPropertyChanged(BR.loaded)
|
|
||||||
notifyPropertyChanged(BR.loadingFailed)
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class State {
|
|
||||||
LOADED, LOADING, LOADING_FAILED
|
|
||||||
}
|
|
||||||
|
|
||||||
//region Rx
|
|
||||||
protected fun <T> Observable<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
|
|
||||||
doOnSubscribe { viewModel.state = State.LOADING }
|
|
||||||
.doOnError { viewModel.state = State.LOADING_FAILED }
|
|
||||||
.doOnNext { if (allowFinishing) viewModel.state = State.LOADED }
|
|
||||||
|
|
||||||
protected fun <T> Single<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
|
|
||||||
doOnSubscribe { viewModel.state = State.LOADING }
|
|
||||||
.doOnError { viewModel.state = State.LOADING_FAILED }
|
|
||||||
.doOnSuccess { if (allowFinishing) viewModel.state = State.LOADED }
|
|
||||||
|
|
||||||
protected fun <T> Maybe<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
|
|
||||||
doOnSubscribe { viewModel.state = State.LOADING }
|
|
||||||
.doOnError { viewModel.state = State.LOADING_FAILED }
|
|
||||||
.doOnComplete { if (allowFinishing) viewModel.state = State.LOADED }
|
|
||||||
.doOnSuccess { if (allowFinishing) viewModel.state = State.LOADED }
|
|
||||||
|
|
||||||
protected fun <T> Flowable<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
|
|
||||||
doOnSubscribe { viewModel.state = State.LOADING }
|
|
||||||
.doOnError { viewModel.state = State.LOADING_FAILED }
|
|
||||||
.doOnNext { if (allowFinishing) viewModel.state = State.LOADED }
|
|
||||||
|
|
||||||
protected fun Completable.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
|
|
||||||
doOnSubscribe { viewModel.state = State.LOADING }
|
|
||||||
.doOnError { viewModel.state = State.LOADING_FAILED }
|
|
||||||
.doOnComplete { if (allowFinishing) viewModel.state = State.LOADED }
|
|
||||||
//endregion
|
|
||||||
}
|
|
@@ -1,46 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.base.viewmodel
|
|
||||||
|
|
||||||
import androidx.databinding.Observable
|
|
||||||
import androidx.databinding.PropertyChangeRegistry
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy of [android.databinding.BaseObservable] which extends [ViewModel]
|
|
||||||
*/
|
|
||||||
abstract class ObservableViewModel : TeanityViewModel(), Observable {
|
|
||||||
|
|
||||||
@Transient
|
|
||||||
private var callbacks: PropertyChangeRegistry? = null
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
|
|
||||||
if (callbacks == null) {
|
|
||||||
callbacks = PropertyChangeRegistry()
|
|
||||||
}
|
|
||||||
callbacks?.add(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
|
|
||||||
callbacks?.remove(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies listeners that all properties of this instance have changed.
|
|
||||||
*/
|
|
||||||
@Synchronized
|
|
||||||
fun notifyChange() {
|
|
||||||
callbacks?.notifyCallbacks(this, 0, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies listeners that a specific property has changed. The getter for the property
|
|
||||||
* that changes should be marked with [android.databinding.Bindable] to generate a field in
|
|
||||||
* `BR` to be used as `fieldId`.
|
|
||||||
*
|
|
||||||
* @param fieldId The generated BR id for the Bindable field.
|
|
||||||
*/
|
|
||||||
fun notifyPropertyChanged(fieldId: Int) {
|
|
||||||
callbacks?.notifyCallbacks(this, fieldId, null)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.base.viewmodel
|
|
||||||
|
|
||||||
abstract class StatefulViewModel<State : Enum<*>>(
|
|
||||||
val defaultState: State
|
|
||||||
) : ObservableViewModel() {
|
|
||||||
|
|
||||||
var state: State = defaultState
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
notifyStateChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun notifyStateChanged() = Unit
|
|
||||||
|
|
||||||
}
|
|
@@ -1,33 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.base.viewmodel
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import com.topjohnwu.magisk.model.events.SimpleViewEvent
|
|
||||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
|
||||||
import io.reactivex.disposables.Disposable
|
|
||||||
|
|
||||||
abstract class TeanityViewModel : ViewModel() {
|
|
||||||
|
|
||||||
private val disposables = CompositeDisposable()
|
|
||||||
private val _viewEvents = MutableLiveData<ViewEvent>()
|
|
||||||
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
|
|
||||||
|
|
||||||
override fun onCleared() {
|
|
||||||
super.onCleared()
|
|
||||||
disposables.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <Event : ViewEvent> Event.publish() {
|
|
||||||
_viewEvents.value = this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Int.publish() {
|
|
||||||
_viewEvents.value = SimpleViewEvent(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Disposable.add() {
|
|
||||||
disposables.add(this)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,29 +1,26 @@
|
|||||||
package com.topjohnwu.magisk
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.multidex.MultiDex
|
import androidx.multidex.MultiDex
|
||||||
import androidx.room.Room
|
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import androidx.work.impl.WorkDatabase
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import androidx.work.impl.WorkDatabase_Impl
|
import com.topjohnwu.magisk.DynAPK
|
||||||
import com.topjohnwu.magisk.data.database.RepoDatabase
|
import com.topjohnwu.magisk.FileProvider
|
||||||
import com.topjohnwu.magisk.data.database.RepoDatabase_Impl
|
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||||
import com.topjohnwu.magisk.data.database.SuLogDatabase
|
import com.topjohnwu.magisk.core.utils.RootInit
|
||||||
import com.topjohnwu.magisk.data.database.SuLogDatabase_Impl
|
import com.topjohnwu.magisk.core.utils.updateConfig
|
||||||
import com.topjohnwu.magisk.di.ActivityTracker
|
import com.topjohnwu.magisk.di.ActivityTracker
|
||||||
import com.topjohnwu.magisk.di.koinModules
|
import com.topjohnwu.magisk.di.koinModules
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.unwrap
|
import com.topjohnwu.magisk.extensions.unwrap
|
||||||
import com.topjohnwu.magisk.utils.RootInit
|
|
||||||
import com.topjohnwu.magisk.utils.SuHandler
|
|
||||||
import com.topjohnwu.magisk.utils.updateConfig
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.core.context.startKoin
|
import org.koin.core.context.startKoin
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
open class App() : Application() {
|
open class App() : Application() {
|
||||||
|
|
||||||
@@ -37,14 +34,13 @@ open class App() : Application() {
|
|||||||
Shell.Config.verboseLogging(BuildConfig.DEBUG)
|
Shell.Config.verboseLogging(BuildConfig.DEBUG)
|
||||||
Shell.Config.addInitializers(RootInit::class.java)
|
Shell.Config.addInitializers(RootInit::class.java)
|
||||||
Shell.Config.setTimeout(2)
|
Shell.Config.setTimeout(2)
|
||||||
FileProvider.callHandler = SuHandler
|
FileProvider.callHandler = SuCallbackHandler
|
||||||
Room.setFactory {
|
|
||||||
when (it) {
|
// Always log full stack trace with Timber
|
||||||
WorkDatabase::class.java -> WorkDatabase_Impl()
|
Timber.plant(Timber.DebugTree())
|
||||||
RepoDatabase::class.java -> RepoDatabase_Impl()
|
Thread.setDefaultUncaughtExceptionHandler { _, e ->
|
||||||
SuLogDatabase::class.java -> SuLogDatabase_Impl()
|
Timber.e(e)
|
||||||
else -> null
|
exitProcess(1)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +48,6 @@ open class App() : Application() {
|
|||||||
// Basic setup
|
// Basic setup
|
||||||
if (BuildConfig.DEBUG)
|
if (BuildConfig.DEBUG)
|
||||||
MultiDex.install(base)
|
MultiDex.install(base)
|
||||||
Timber.plant(Timber.DebugTree())
|
|
||||||
|
|
||||||
// Some context magic
|
// Some context magic
|
||||||
val app: Application
|
val app: Application
|
||||||
@@ -72,7 +67,7 @@ open class App() : Application() {
|
|||||||
androidContext(wrapped)
|
androidContext(wrapped)
|
||||||
modules(koinModules)
|
modules(koinModules)
|
||||||
}
|
}
|
||||||
ResourceMgr.init(impl)
|
ResMgr.init(impl)
|
||||||
app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
|
app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
|
||||||
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
|
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
|
||||||
}
|
}
|
@@ -1,19 +1,22 @@
|
|||||||
package com.topjohnwu.magisk
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.util.Xml
|
import android.util.Xml
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.topjohnwu.magisk.data.database.SettingsDao
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.data.database.StringDao
|
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
|
||||||
|
import com.topjohnwu.magisk.core.magiskdb.StringDao
|
||||||
|
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||||
|
import com.topjohnwu.magisk.core.utils.Utils
|
||||||
import com.topjohnwu.magisk.data.repository.DBConfig
|
import com.topjohnwu.magisk.data.repository.DBConfig
|
||||||
import com.topjohnwu.magisk.di.Protected
|
import com.topjohnwu.magisk.di.Protected
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
import com.topjohnwu.magisk.model.preference.PreferenceModel
|
import com.topjohnwu.magisk.model.preference.PreferenceModel
|
||||||
import com.topjohnwu.magisk.utils.BiometricHelper
|
import com.topjohnwu.magisk.ui.theme.Theme
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.io.SuFile
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
import com.topjohnwu.superuser.io.SuFileInputStream
|
import com.topjohnwu.superuser.io.SuFileInputStream
|
||||||
@@ -45,10 +48,15 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
const val CUSTOM_CHANNEL = "custom_channel"
|
const val CUSTOM_CHANNEL = "custom_channel"
|
||||||
const val LOCALE = "locale"
|
const val LOCALE = "locale"
|
||||||
const val DARK_THEME = "dark_theme"
|
const val DARK_THEME = "dark_theme"
|
||||||
|
const val DARK_THEME_EXTENDED = "dark_theme_extended"
|
||||||
const val REPO_ORDER = "repo_order"
|
const val REPO_ORDER = "repo_order"
|
||||||
const val SHOW_SYSTEM_APP = "show_system"
|
const val SHOW_SYSTEM_APP = "show_system"
|
||||||
const val DOWNLOAD_PATH = "download_path"
|
const val DOWNLOAD_PATH = "download_path"
|
||||||
|
const val REDESIGN = "redesign"
|
||||||
|
const val SAFETY = "safety_notice"
|
||||||
|
const val THEME_ORDINAL = "theme_ordinal"
|
||||||
const val BOOT_ID = "boot_id"
|
const val BOOT_ID = "boot_id"
|
||||||
|
const val LIST_SPAN_COUNT = "column_count"
|
||||||
|
|
||||||
// system state
|
// system state
|
||||||
const val MAGISKHIDE = "magiskhide"
|
const val MAGISKHIDE = "magiskhide"
|
||||||
@@ -98,13 +106,12 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val defaultChannel =
|
private val defaultChannel =
|
||||||
if (Utils.isCanary) {
|
if (isCanaryVersion) {
|
||||||
if (BuildConfig.DEBUG)
|
if (BuildConfig.DEBUG)
|
||||||
Value.CANARY_DEBUG_CHANNEL
|
Value.CANARY_DEBUG_CHANNEL
|
||||||
else
|
else
|
||||||
Value.CANARY_CHANNEL
|
Value.CANARY_CHANNEL
|
||||||
}
|
} else Value.DEFAULT_CHANNEL
|
||||||
else Value.DEFAULT_CHANNEL
|
|
||||||
|
|
||||||
var bootId by preference(Key.BOOT_ID, "")
|
var bootId by preference(Key.BOOT_ID, "")
|
||||||
|
|
||||||
@@ -116,12 +123,20 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
var suNotification by preferenceStrInt(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST)
|
var suNotification by preferenceStrInt(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST)
|
||||||
var updateChannel by preferenceStrInt(Key.UPDATE_CHANNEL, defaultChannel)
|
var updateChannel by preferenceStrInt(Key.UPDATE_CHANNEL, defaultChannel)
|
||||||
|
|
||||||
var darkTheme by preference(Key.DARK_THEME, true)
|
var safetyNotice by preference(Key.SAFETY, true)
|
||||||
|
var darkThemeExtended by preference(
|
||||||
|
Key.DARK_THEME_EXTENDED,
|
||||||
|
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||||
|
)
|
||||||
|
var themeOrdinal by preference(Key.THEME_ORDINAL, Theme.Piplup.ordinal)
|
||||||
var suReAuth by preference(Key.SU_REAUTH, false)
|
var suReAuth by preference(Key.SU_REAUTH, false)
|
||||||
var checkUpdate by preference(Key.CHECK_UPDATES, true)
|
var checkUpdate by preference(Key.CHECK_UPDATES, true)
|
||||||
var magiskHide by preference(Key.MAGISKHIDE, true)
|
var magiskHide by preference(Key.MAGISKHIDE, true)
|
||||||
|
@JvmStatic
|
||||||
var coreOnly by preference(Key.COREONLY, false)
|
var coreOnly by preference(Key.COREONLY, false)
|
||||||
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
|
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
|
||||||
|
@JvmStatic
|
||||||
|
var listSpanCount by preference(Key.LIST_SPAN_COUNT, 1)
|
||||||
|
|
||||||
var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "")
|
var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "")
|
||||||
var locale by preference(Key.LOCALE, "")
|
var locale by preference(Key.LOCALE, "")
|
||||||
@@ -163,7 +178,9 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parsePrefs(editor: SharedPreferences.Editor) = editor.apply {
|
private fun parsePrefs(editor: SharedPreferences.Editor) = editor.apply {
|
||||||
val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS)
|
val config = SuFile.open("/data/adb",
|
||||||
|
Const.MANAGER_CONFIGS
|
||||||
|
)
|
||||||
if (config.exists()) runCatching {
|
if (config.exists()) runCatching {
|
||||||
val input = SuFileInputStream(config)
|
val input = SuFileInputStream(config)
|
||||||
val parser = Xml.newPullParser()
|
val parser = Xml.newPullParser()
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -6,7 +6,7 @@ import java.io.File
|
|||||||
object Const {
|
object Const {
|
||||||
|
|
||||||
// Paths
|
// Paths
|
||||||
const val MAGISK_PATH = "/sbin/.magisk/img"
|
const val MAGISK_PATH = "/sbin/.magisk/modules"
|
||||||
var MAGISK_DISABLE_FILE = File("xxx")
|
var MAGISK_DISABLE_FILE = File("xxx")
|
||||||
const val TMP_FOLDER_PATH = "/dev/tmp"
|
const val TMP_FOLDER_PATH = "/dev/tmp"
|
||||||
const val MAGISK_LOG = "/cache/magisk.log"
|
const val MAGISK_LOG = "/cache/magisk.log"
|
||||||
@@ -23,8 +23,8 @@ object Const {
|
|||||||
val USER_ID = Process.myUid() / 100000
|
val USER_ID = Process.myUid() / 100000
|
||||||
|
|
||||||
object Version {
|
object Version {
|
||||||
const val MIN_VERSION = "v18.0"
|
const val MIN_VERSION = "v19.0"
|
||||||
const val MIN_VERCODE = 18000
|
const val MIN_VERCODE = 19000
|
||||||
const val CONNECT_MODE = 20100
|
const val CONNECT_MODE = 20100
|
||||||
const val PROVIDER_CONNECT = 20102
|
const val PROVIDER_CONNECT = 20102
|
||||||
}
|
}
|
||||||
@@ -62,6 +62,7 @@ object Const {
|
|||||||
const val ETAG_KEY = "ETag"
|
const val ETAG_KEY = "ETag"
|
||||||
// intents
|
// intents
|
||||||
const val OPEN_SECTION = "section"
|
const val OPEN_SECTION = "section"
|
||||||
|
const val OPEN_SETTINGS = "settings"
|
||||||
const val INTENT_SET_APP = "app_json"
|
const val INTENT_SET_APP = "app_json"
|
||||||
const val FLASH_ACTION = "action"
|
const val FLASH_ACTION = "action"
|
||||||
const val FLASH_DATA = "additional_data"
|
const val FLASH_DATA = "additional_data"
|
@@ -1,19 +1,16 @@
|
|||||||
package com.topjohnwu.magisk.model.receiver
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.core.base.BaseReceiver
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.download.DownloadService
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||||
import com.topjohnwu.magisk.base.BaseReceiver
|
import com.topjohnwu.magisk.core.model.ManagerJson
|
||||||
import com.topjohnwu.magisk.data.database.PolicyDao
|
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||||
|
import com.topjohnwu.magisk.core.view.Shortcuts
|
||||||
import com.topjohnwu.magisk.extensions.reboot
|
import com.topjohnwu.magisk.extensions.reboot
|
||||||
import com.topjohnwu.magisk.model.download.DownloadService
|
|
||||||
import com.topjohnwu.magisk.model.entity.ManagerJson
|
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||||
import com.topjohnwu.magisk.utils.SuHandler
|
|
||||||
import com.topjohnwu.magisk.view.Shortcuts
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import org.koin.core.inject
|
import org.koin.core.inject
|
||||||
|
|
||||||
@@ -30,7 +27,7 @@ open class GeneralReceiver : BaseReceiver() {
|
|||||||
|
|
||||||
when (intent.action ?: return) {
|
when (intent.action ?: return) {
|
||||||
Intent.ACTION_REBOOT -> {
|
Intent.ACTION_REBOOT -> {
|
||||||
SuHandler(context, intent.getStringExtra("action"), intent.extras)
|
SuCallbackHandler(context, intent.getStringExtra("action"), intent.extras)
|
||||||
}
|
}
|
||||||
Intent.ACTION_PACKAGE_REPLACED -> {
|
Intent.ACTION_PACKAGE_REPLACED -> {
|
||||||
// This will only work pre-O
|
// This will only work pre-O
|
@@ -1,6 +1,6 @@
|
|||||||
@file:Suppress("DEPRECATION")
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
package com.topjohnwu.magisk
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.job.JobInfo
|
import android.app.job.JobInfo
|
||||||
@@ -14,23 +14,23 @@ import android.content.res.AssetManager
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import com.topjohnwu.magisk.DynAPK
|
||||||
|
import com.topjohnwu.magisk.ProcessPhoenix
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.download.DownloadService
|
||||||
|
import com.topjohnwu.magisk.core.utils.refreshLocale
|
||||||
|
import com.topjohnwu.magisk.core.utils.updateConfig
|
||||||
import com.topjohnwu.magisk.extensions.forceGetDeclaredField
|
import com.topjohnwu.magisk.extensions.forceGetDeclaredField
|
||||||
import com.topjohnwu.magisk.model.download.DownloadService
|
import com.topjohnwu.magisk.legacy.flash.FlashActivity
|
||||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
import com.topjohnwu.magisk.legacy.surequest.SuRequestActivity
|
||||||
import com.topjohnwu.magisk.model.update.UpdateCheckService
|
|
||||||
import com.topjohnwu.magisk.ui.MainActivity
|
import com.topjohnwu.magisk.ui.MainActivity
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity
|
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
|
||||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
|
|
||||||
import com.topjohnwu.magisk.utils.refreshLocale
|
|
||||||
import com.topjohnwu.magisk.utils.updateConfig
|
|
||||||
|
|
||||||
fun AssetManager.addAssetPath(path: String) {
|
fun AssetManager.addAssetPath(path: String) {
|
||||||
DynAPK.addAssetPath(this, path)
|
DynAPK.addAssetPath(this, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.wrap(global: Boolean = true): Context
|
fun Context.wrap(global: Boolean = true): Context =
|
||||||
= if (global) GlobalResContext(this) else ResContext(this)
|
if (global) GlobalResContext(this) else ResContext(this)
|
||||||
|
|
||||||
fun Context.wrapJob(): Context = object : GlobalResContext(this) {
|
fun Context.wrapJob(): Context = object : GlobalResContext(this) {
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ fun Class<*>.cmp(pkg: String): ComponentName {
|
|||||||
inline fun <reified T> Context.intent() = Intent().setComponent(T::class.java.cmp(packageName))
|
inline fun <reified T> Context.intent() = Intent().setComponent(T::class.java.cmp(packageName))
|
||||||
|
|
||||||
private open class GlobalResContext(base: Context) : ContextWrapper(base) {
|
private open class GlobalResContext(base: Context) : ContextWrapper(base) {
|
||||||
open val mRes: Resources get() = ResourceMgr.resource
|
open val mRes: Resources get() = ResMgr.resource
|
||||||
|
|
||||||
override fun getResources(): Resources {
|
override fun getResources(): Resources {
|
||||||
return mRes
|
return mRes
|
||||||
@@ -78,22 +78,24 @@ private class ResContext(base: Context) : GlobalResContext(base) {
|
|||||||
private fun Resources.patch(): Resources {
|
private fun Resources.patch(): Resources {
|
||||||
updateConfig()
|
updateConfig()
|
||||||
if (isRunningAsStub)
|
if (isRunningAsStub)
|
||||||
assets.addAssetPath(ResourceMgr.resApk)
|
assets.addAssetPath(ResMgr.apk)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ResourceMgr {
|
object ResMgr {
|
||||||
|
|
||||||
lateinit var resource: Resources
|
lateinit var resource: Resources
|
||||||
lateinit var resApk: String
|
lateinit var apk: String
|
||||||
|
|
||||||
fun init(context: Context) {
|
fun init(context: Context) {
|
||||||
resource = context.resources
|
resource = context.resources
|
||||||
refreshLocale()
|
refreshLocale()
|
||||||
if (isRunningAsStub) {
|
if (isRunningAsStub) {
|
||||||
resApk = DynAPK.current(context).path
|
apk = DynAPK.current(context).path
|
||||||
resource.assets.addAssetPath(resApk)
|
resource.assets.addAssetPath(apk)
|
||||||
|
} else {
|
||||||
|
apk = context.packageResourcePath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,7 +132,8 @@ private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler
|
|||||||
val name = service.className
|
val name = service.className
|
||||||
val component = ComponentName(
|
val component = ComponentName(
|
||||||
service.packageName,
|
service.packageName,
|
||||||
Info.stub!!.classToComponent[name] ?: name)
|
Info.stub!!.classToComponent[name] ?: name
|
||||||
|
)
|
||||||
|
|
||||||
javaClass.forceGetDeclaredField("service")?.set(this, component)
|
javaClass.forceGetDeclaredField("service")?.set(this, component)
|
||||||
return this
|
return this
|
||||||
@@ -144,7 +147,6 @@ object ClassMap {
|
|||||||
MainActivity::class.java to a.b::class.java,
|
MainActivity::class.java to a.b::class.java,
|
||||||
SplashActivity::class.java to a.c::class.java,
|
SplashActivity::class.java to a.c::class.java,
|
||||||
FlashActivity::class.java to a.f::class.java,
|
FlashActivity::class.java to a.f::class.java,
|
||||||
UpdateCheckService::class.java to a.g::class.java,
|
|
||||||
GeneralReceiver::class.java to a.h::class.java,
|
GeneralReceiver::class.java to a.h::class.java,
|
||||||
DownloadService::class.java to a.j::class.java,
|
DownloadService::class.java to a.j::class.java,
|
||||||
SuRequestActivity::class.java to a.m::class.java,
|
SuRequestActivity::class.java to a.m::class.java,
|
||||||
@@ -153,3 +155,16 @@ object ClassMap {
|
|||||||
|
|
||||||
operator fun get(c: Class<*>) = map.getOrElse(c) { c }
|
operator fun get(c: Class<*>) = map.getOrElse(c) { c }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Keep a reference to these resources to prevent it from
|
||||||
|
* being removed when running "remove unused resources" */
|
||||||
|
val shouldKeepResources = listOf(
|
||||||
|
/* TODO: The following strings should be used somewhere */
|
||||||
|
R.string.no_apps_found,
|
||||||
|
R.string.no_info_provided,
|
||||||
|
R.string.release_notes,
|
||||||
|
R.string.settings_download_path_error,
|
||||||
|
R.string.invalid_update_channel,
|
||||||
|
R.string.update_available
|
||||||
|
)
|
@@ -1,29 +1,40 @@
|
|||||||
package com.topjohnwu.magisk
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
||||||
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.DynAPK
|
||||||
|
import com.topjohnwu.magisk.core.model.UpdateInfo
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.model.entity.UpdateInfo
|
|
||||||
import com.topjohnwu.magisk.utils.CachedValue
|
import com.topjohnwu.magisk.utils.CachedValue
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils.fastCmd
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
val isRunningAsStub get() = Info.stub != null
|
val isRunningAsStub get() = Info.stub != null
|
||||||
|
val isCanaryVersion = !BuildConfig.VERSION_NAME.contains(".")
|
||||||
|
|
||||||
object Info {
|
object Info {
|
||||||
|
|
||||||
val envRef = CachedValue { loadState() }
|
val envRef = CachedValue { loadState() }
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
val env by envRef // Local
|
val env by envRef // Local
|
||||||
var remote = UpdateInfo() // Remote
|
var remote = UpdateInfo() // Remote
|
||||||
|
@JvmStatic
|
||||||
var stub: DynAPK.Data? = null // Stub
|
var stub: DynAPK.Data? = null // Stub
|
||||||
|
|
||||||
var keepVerity = false
|
// Toggle-able options
|
||||||
var keepEnc = false
|
@JvmStatic var keepVerity = false
|
||||||
var recovery = false
|
@JvmStatic var keepEnc = false
|
||||||
|
@JvmStatic var recovery = false
|
||||||
|
|
||||||
|
// Immutable device state
|
||||||
|
@JvmStatic var isSAR = false
|
||||||
|
@JvmStatic var isAB = false
|
||||||
|
@JvmStatic var ramdisk = false
|
||||||
|
|
||||||
val isConnected by lazy {
|
val isConnected by lazy {
|
||||||
KObservableField(false).also { field ->
|
KObservableField(false).also { field ->
|
||||||
@@ -50,12 +61,11 @@ object Info {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadState() = runCatching {
|
private fun loadState() = Env(
|
||||||
val str = ShellUtils.fastCmd("magisk -v").split(":".toRegex())[0]
|
fastCmd("magisk -v").split(":".toRegex())[0],
|
||||||
val code = ShellUtils.fastCmd("magisk -V").toInt()
|
runCatching { fastCmd("magisk -V").toInt() }.getOrDefault(-1),
|
||||||
val hide = Shell.su("magiskhide --status").exec().isSuccess
|
Shell.su("magiskhide --status").exec().isSuccess
|
||||||
Env(str, code, hide)
|
)
|
||||||
}.getOrElse { Env() }
|
|
||||||
|
|
||||||
class Env(
|
class Env(
|
||||||
val magiskVersionString: String = "",
|
val magiskVersionString: String = "",
|
||||||
@@ -65,7 +75,7 @@ object Info {
|
|||||||
val magiskHide get() = Config.magiskHide
|
val magiskHide get() = Config.magiskHide
|
||||||
val magiskVersionCode = when (code) {
|
val magiskVersionCode = when (code) {
|
||||||
in Int.MIN_VALUE..Const.Version.MIN_VERCODE -> -1
|
in Int.MIN_VALUE..Const.Version.MIN_VERCODE -> -1
|
||||||
else -> if(Shell.rootAccess()) code else -1
|
else -> if (Shell.rootAccess()) code else -1
|
||||||
}
|
}
|
||||||
val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE
|
val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE
|
||||||
val isActive = magiskVersionCode >= 0
|
val isActive = magiskVersionCode >= 0
|
@@ -0,0 +1,64 @@
|
|||||||
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.core.tasks.patchDTB
|
||||||
|
import com.topjohnwu.magisk.core.utils.Utils
|
||||||
|
import com.topjohnwu.magisk.core.view.Notifications
|
||||||
|
import com.topjohnwu.magisk.core.view.Shortcuts
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
|
import com.topjohnwu.magisk.extensions.get
|
||||||
|
import com.topjohnwu.magisk.model.navigation.Navigation
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
|
||||||
|
open class SplashActivity : Activity() {
|
||||||
|
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
super.attachBaseContext(base.wrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
Shell.getShell { Shell.EXECUTOR.execute(this::initAndStart) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleRepackage() {
|
||||||
|
val pkg = Config.suManager
|
||||||
|
if (Config.suManager.isNotEmpty() && packageName == BuildConfig.APPLICATION_ID) {
|
||||||
|
Config.suManager = ""
|
||||||
|
Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec()
|
||||||
|
}
|
||||||
|
if (pkg == packageName) {
|
||||||
|
runCatching {
|
||||||
|
// We are the manager, remove com.topjohnwu.magisk as it could be malware
|
||||||
|
packageManager.getApplicationInfo(BuildConfig.APPLICATION_ID, 0)
|
||||||
|
Shell.su("(pm uninstall ${BuildConfig.APPLICATION_ID})& >/dev/null 2>&1").exec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initAndStart() {
|
||||||
|
Config.initialize()
|
||||||
|
handleRepackage()
|
||||||
|
Notifications.setup(this)
|
||||||
|
Utils.scheduleUpdateCheck(this)
|
||||||
|
Shortcuts.setup(this)
|
||||||
|
|
||||||
|
// Patch DTB partitions if needed
|
||||||
|
patchDTB(this)
|
||||||
|
|
||||||
|
// Pre-fetch network stuffs
|
||||||
|
get<GithubRawServices>()
|
||||||
|
|
||||||
|
DONE = true
|
||||||
|
Navigation.start(intent, this)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
var DONE = false
|
||||||
|
}
|
||||||
|
}
|
@@ -1,19 +1,20 @@
|
|||||||
package com.topjohnwu.magisk.model.update
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
import androidx.work.ListenableWorker
|
import android.content.Context
|
||||||
|
import androidx.work.Worker
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.core.view.Notifications
|
||||||
import com.topjohnwu.magisk.base.DelegateWorker
|
|
||||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
|
||||||
class UpdateCheckService : DelegateWorker() {
|
class UpdateCheckService(context: Context, workerParams: WorkerParameters)
|
||||||
|
: Worker(context, workerParams) {
|
||||||
|
|
||||||
private val magiskRepo: MagiskRepository by inject()
|
private val magiskRepo: MagiskRepository by inject()
|
||||||
|
|
||||||
override fun doWork(): ListenableWorker.Result {
|
override fun doWork(): Result {
|
||||||
// Make sure shell initializer was ran
|
// Make sure shell initializer was ran
|
||||||
Shell.getShell()
|
Shell.getShell()
|
||||||
return runCatching {
|
return runCatching {
|
||||||
@@ -22,9 +23,9 @@ class UpdateCheckService : DelegateWorker() {
|
|||||||
Notifications.managerUpdate(applicationContext)
|
Notifications.managerUpdate(applicationContext)
|
||||||
else if (Info.env.magiskVersionCode < Info.remote.magisk.versionCode)
|
else if (Info.env.magiskVersionCode < Info.remote.magisk.versionCode)
|
||||||
Notifications.magiskUpdate(applicationContext)
|
Notifications.magiskUpdate(applicationContext)
|
||||||
ListenableWorker.Result.success()
|
Result.success()
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
ListenableWorker.Result.failure()
|
Result.failure()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,51 +1,26 @@
|
|||||||
package com.topjohnwu.magisk.base
|
package com.topjohnwu.magisk.core.base
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.collection.SparseArrayCompat
|
import androidx.collection.SparseArrayCompat
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.databinding.DataBindingUtil
|
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||||
import androidx.databinding.ViewDataBinding
|
import com.topjohnwu.magisk.core.wrap
|
||||||
import com.topjohnwu.magisk.BR
|
|
||||||
import com.topjohnwu.magisk.Config
|
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
|
||||||
import com.topjohnwu.magisk.extensions.set
|
import com.topjohnwu.magisk.extensions.set
|
||||||
import com.topjohnwu.magisk.model.events.EventHandler
|
|
||||||
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
||||||
import com.topjohnwu.magisk.utils.currentLocale
|
|
||||||
import com.topjohnwu.magisk.wrap
|
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
typealias RequestCallback = BaseActivity<*, *>.(Int, Intent?) -> Unit
|
typealias RequestCallback = BaseActivity.(Int, Intent?) -> Unit
|
||||||
|
|
||||||
abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
|
abstract class BaseActivity : AppCompatActivity() {
|
||||||
AppCompatActivity(), EventHandler {
|
|
||||||
|
|
||||||
protected lateinit var binding: Binding
|
|
||||||
protected abstract val layoutRes: Int
|
|
||||||
protected abstract val viewModel: ViewModel
|
|
||||||
protected open val themeRes: Int = R.style.MagiskTheme
|
|
||||||
protected open val snackbarView get() = binding.root
|
|
||||||
|
|
||||||
private val resultCallbacks by lazy { SparseArrayCompat<RequestCallback>() }
|
private val resultCallbacks by lazy { SparseArrayCompat<RequestCallback>() }
|
||||||
|
|
||||||
init {
|
|
||||||
val theme = if (Config.darkTheme) {
|
|
||||||
AppCompatDelegate.MODE_NIGHT_YES
|
|
||||||
} else {
|
|
||||||
AppCompatDelegate.MODE_NIGHT_NO
|
|
||||||
}
|
|
||||||
AppCompatDelegate.setDefaultNightMode(theme)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun applyOverrideConfiguration(config: Configuration?) {
|
override fun applyOverrideConfiguration(config: Configuration?) {
|
||||||
// Force applying our preferred local
|
// Force applying our preferred local
|
||||||
config?.setLocale(currentLocale)
|
config?.setLocale(currentLocale)
|
||||||
@@ -56,18 +31,6 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
|
|||||||
super.attachBaseContext(base.wrap(false))
|
super.attachBaseContext(base.wrap(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
setTheme(themeRes)
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
viewModel.viewEvents.observe(this, viewEventObserver)
|
|
||||||
|
|
||||||
binding = DataBindingUtil.setContentView<Binding>(this, layoutRes).apply {
|
|
||||||
setVariable(BR.viewModel, viewModel)
|
|
||||||
lifecycleOwner = this@BaseActivity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun withPermissions(vararg permissions: String, builder: PermissionRequestBuilder.() -> Unit) {
|
fun withPermissions(vararg permissions: String, builder: PermissionRequestBuilder.() -> Unit) {
|
||||||
val request = PermissionRequestBuilder().apply(builder).build()
|
val request = PermissionRequestBuilder().apply(builder).build()
|
||||||
val ungranted = permissions.filter {
|
val ungranted = permissions.filter {
|
||||||
@@ -93,7 +56,7 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(
|
override fun onRequestPermissionsResult(
|
||||||
requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
var success = true
|
var success = true
|
||||||
for (res in grantResults) {
|
for (res in grantResults) {
|
||||||
if (res != PackageManager.PERMISSION_GRANTED) {
|
if (res != PackageManager.PERMISSION_GRANTED) {
|
||||||
@@ -101,18 +64,18 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resultCallbacks[requestCode]?.apply {
|
resultCallbacks[requestCode]?.also {
|
||||||
resultCallbacks.remove(requestCode)
|
resultCallbacks.remove(requestCode)
|
||||||
invoke(this@BaseActivity, if (success) 1 else -1, null)
|
it(this@BaseActivity, if (success) 1 else -1, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
resultCallbacks[requestCode]?.apply {
|
resultCallbacks[requestCode]?.also {
|
||||||
resultCallbacks.remove(requestCode)
|
resultCallbacks.remove(requestCode)
|
||||||
invoke(this@BaseActivity, resultCode, data)
|
it(this@BaseActivity, resultCode, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,4 +84,9 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
|
|||||||
startActivityForResult(intent, requestCode)
|
startActivityForResult(intent, requestCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun recreate() {
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -1,10 +1,10 @@
|
|||||||
package com.topjohnwu.magisk.base
|
package com.topjohnwu.magisk.core.base
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.topjohnwu.magisk.wrap
|
import com.topjohnwu.magisk.core.wrap
|
||||||
import org.koin.core.KoinComponent
|
import org.koin.core.KoinComponent
|
||||||
|
|
||||||
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent {
|
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent {
|
@@ -1,8 +1,8 @@
|
|||||||
package com.topjohnwu.magisk.base
|
package com.topjohnwu.magisk.core.base
|
||||||
|
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.topjohnwu.magisk.wrap
|
import com.topjohnwu.magisk.core.wrap
|
||||||
import org.koin.core.KoinComponent
|
import org.koin.core.KoinComponent
|
||||||
|
|
||||||
abstract class BaseService : Service(), KoinComponent {
|
abstract class BaseService : Service(), KoinComponent {
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.base
|
package com.topjohnwu.magisk.core.base
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Network
|
import android.net.Network
|
||||||
@@ -10,7 +10,7 @@ import androidx.work.ListenableWorker
|
|||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
abstract class DelegateWorker {
|
abstract class BaseWorkerWrapper {
|
||||||
|
|
||||||
private lateinit var worker: ListenableWorker
|
private lateinit var worker: ListenableWorker
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.core.download
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
@@ -8,16 +8,19 @@ import android.content.Intent
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.intent
|
||||||
|
import com.topjohnwu.magisk.core.tasks.EnvFixTask
|
||||||
import com.topjohnwu.magisk.extensions.chooser
|
import com.topjohnwu.magisk.extensions.chooser
|
||||||
import com.topjohnwu.magisk.extensions.exists
|
import com.topjohnwu.magisk.extensions.exists
|
||||||
import com.topjohnwu.magisk.extensions.provide
|
import com.topjohnwu.magisk.extensions.provide
|
||||||
import com.topjohnwu.magisk.intent
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
|
import com.topjohnwu.magisk.legacy.flash.FlashActivity
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.*
|
import com.topjohnwu.magisk.model.entity.internal.Configuration.*
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary
|
import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
|
||||||
import com.topjohnwu.magisk.utils.APKInstall
|
import com.topjohnwu.magisk.utils.APKInstall
|
||||||
|
import io.reactivex.Completable
|
||||||
import org.koin.core.get
|
import org.koin.core.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.random.Random.Default.nextInt
|
import kotlin.random.Random.Default.nextInt
|
||||||
@@ -43,6 +46,7 @@ open class DownloadService : RemoteFileService() {
|
|||||||
id: Int
|
id: Int
|
||||||
) = when (val conf = subject.configuration) {
|
) = when (val conf = subject.configuration) {
|
||||||
Uninstall -> FlashActivity.uninstall(this, subject.file, id)
|
Uninstall -> FlashActivity.uninstall(this, subject.file, id)
|
||||||
|
EnvFix -> { remove(id); EnvFixTask(subject.file).exec() }
|
||||||
is Patch -> FlashActivity.patch(this, subject.file, conf.fileUri, id)
|
is Patch -> FlashActivity.patch(this, subject.file, conf.fileUri, id)
|
||||||
is Flash -> FlashActivity.flash(this, subject.file, conf is Secondary, id)
|
is Flash -> FlashActivity.flash(this, subject.file, conf is Secondary, id)
|
||||||
else -> Unit
|
else -> Unit
|
||||||
@@ -60,10 +64,14 @@ open class DownloadService : RemoteFileService() {
|
|||||||
subject: Manager,
|
subject: Manager,
|
||||||
id: Int
|
id: Int
|
||||||
) {
|
) {
|
||||||
remove(id)
|
Completable.fromAction {
|
||||||
when (subject.configuration) {
|
handleAPK(subject)
|
||||||
is APK.Upgrade -> APKInstall.install(this, subject.file)
|
}.subscribeK {
|
||||||
is APK.Restore -> Unit
|
remove(id)
|
||||||
|
when (subject.configuration) {
|
||||||
|
is APK.Upgrade -> APKInstall.install(this, subject.file)
|
||||||
|
is APK.Restore -> Unit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,13 +122,15 @@ open class DownloadService : RemoteFileService() {
|
|||||||
|
|
||||||
@Suppress("ReplaceSingleLineLet")
|
@Suppress("ReplaceSingleLineLet")
|
||||||
private fun Notification.Builder.setContentIntent(intent: Intent) =
|
private fun Notification.Builder.setContentIntent(intent: Intent) =
|
||||||
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
setContentIntent(
|
||||||
.let { setContentIntent(it) }
|
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
||||||
|
)
|
||||||
|
|
||||||
@Suppress("ReplaceSingleLineLet")
|
@Suppress("ReplaceSingleLineLet")
|
||||||
private fun Notification.Builder.addAction(icon: Int, title: Int, intent: Intent) =
|
private fun Notification.Builder.addAction(icon: Int, title: Int, intent: Intent) =
|
||||||
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
addAction(icon, getString(title),
|
||||||
.let { addAction(icon, getString(title), it) }
|
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
||||||
|
)
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
@@ -1,11 +1,18 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.core.download
|
||||||
|
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.DynAPK
|
||||||
|
import com.topjohnwu.magisk.ProcessPhoenix
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.intent
|
||||||
|
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||||
|
import com.topjohnwu.magisk.core.utils.PatchAPK
|
||||||
import com.topjohnwu.magisk.extensions.writeTo
|
import com.topjohnwu.magisk.extensions.writeTo
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore
|
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade
|
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||||
import com.topjohnwu.magisk.utils.PatchAPK
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -13,14 +20,14 @@ private fun RemoteFileService.patch(apk: File, id: Int) {
|
|||||||
if (packageName == BuildConfig.APPLICATION_ID)
|
if (packageName == BuildConfig.APPLICATION_ID)
|
||||||
return
|
return
|
||||||
|
|
||||||
update(id) { notification ->
|
update(id) {
|
||||||
notification.setProgress(0, 0, true)
|
it.setProgress(0, 0, true)
|
||||||
.setProgress(0, 0, true)
|
.setProgress(0, 0, true)
|
||||||
.setContentTitle(getString(R.string.hide_manager_title))
|
.setContentTitle(getString(R.string.hide_manager_title))
|
||||||
.setContentText("")
|
.setContentText("")
|
||||||
}
|
}
|
||||||
val patched = File(apk.parent, "patched.apk")
|
val patched = File(apk.parent, "patched.apk")
|
||||||
PatchAPK.patch(apk, patched, packageName, applicationInfo.nonLocalizedLabel.toString())
|
PatchAPK.patch(apk.path, patched.path, packageName, applicationInfo.nonLocalizedLabel)
|
||||||
apk.delete()
|
apk.delete()
|
||||||
patched.renameTo(apk)
|
patched.renameTo(apk)
|
||||||
}
|
}
|
||||||
@@ -46,11 +53,11 @@ private fun RemoteFileService.upgrade(apk: File, id: Int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun RemoteFileService.restore(apk: File, id: Int) {
|
private fun RemoteFileService.restore(apk: File, id: Int) {
|
||||||
update(id) { notification ->
|
update(id) {
|
||||||
notification.setProgress(0, 0, true)
|
it.setProgress(0, 0, true)
|
||||||
.setProgress(0, 0, true)
|
.setProgress(0, 0, true)
|
||||||
.setContentTitle(getString(R.string.restore_img_msg))
|
.setContentTitle(getString(R.string.restore_img_msg))
|
||||||
.setContentText("")
|
.setContentText("")
|
||||||
}
|
}
|
||||||
Config.export()
|
Config.export()
|
||||||
// Make it world readable
|
// Make it world readable
|
||||||
@@ -58,8 +65,8 @@ private fun RemoteFileService.restore(apk: File, id: Int) {
|
|||||||
Shell.su("pm install $apk && pm uninstall $packageName").exec()
|
Shell.su("pm install $apk && pm uninstall $packageName").exec()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RemoteFileService.handleAPK(subject: DownloadSubject.Manager)
|
fun RemoteFileService.handleAPK(subject: DownloadSubject.Manager) =
|
||||||
= when (subject.configuration) {
|
when (subject.configuration) {
|
||||||
is Upgrade -> upgrade(subject.file, subject.hashCode())
|
is Upgrade -> upgrade(subject.file, subject.hashCode())
|
||||||
is Restore -> restore(subject.file, subject.hashCode())
|
is Restore -> restore(subject.file, subject.hashCode())
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.core.download
|
||||||
|
|
||||||
import com.topjohnwu.magisk.extensions.withStreams
|
import com.topjohnwu.magisk.extensions.withStreams
|
||||||
import java.io.File
|
import java.io.File
|
@@ -1,22 +1,20 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.core.download
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import com.topjohnwu.magisk.base.BaseService
|
import com.topjohnwu.magisk.core.base.BaseService
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.core.view.Notifications
|
||||||
import org.koin.core.KoinComponent
|
import org.koin.core.KoinComponent
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.collections.HashMap
|
||||||
import kotlin.random.Random.Default.nextInt
|
import kotlin.random.Random.Default.nextInt
|
||||||
|
|
||||||
abstract class NotificationService : BaseService(), KoinComponent {
|
abstract class NotificationService : BaseService(), KoinComponent {
|
||||||
|
|
||||||
abstract val defaultNotification: Notification.Builder
|
|
||||||
|
|
||||||
private val hasNotifications get() = notifications.isNotEmpty()
|
private val hasNotifications get() = notifications.isNotEmpty()
|
||||||
|
|
||||||
private val notifications =
|
private val notifications = Collections.synchronizedMap(HashMap<Int, Notification.Builder>())
|
||||||
Collections.synchronizedMap(mutableMapOf<Int, Notification.Builder>())
|
|
||||||
|
|
||||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||||
super.onTaskRemoved(rootIntent)
|
super.onTaskRemoved(rootIntent)
|
||||||
@@ -24,22 +22,23 @@ abstract class NotificationService : BaseService(), KoinComponent {
|
|||||||
notifications.clear()
|
notifications.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract fun createNotification(): Notification.Builder
|
||||||
|
|
||||||
// --
|
// --
|
||||||
|
|
||||||
fun update(
|
fun update(
|
||||||
id: Int,
|
id: Int,
|
||||||
body: (Notification.Builder) -> Unit = {}
|
body: (Notification.Builder) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val notification = notifications.getOrPut(id) { defaultNotification }
|
val wasEmpty = notifications.isEmpty()
|
||||||
|
val notification = notifications.getOrPut(id, ::createNotification).also(body)
|
||||||
notify(id, notification.also(body).build())
|
if (wasEmpty)
|
||||||
|
|
||||||
if (notifications.size == 1) {
|
|
||||||
updateForeground()
|
updateForeground()
|
||||||
}
|
else
|
||||||
|
notify(id, notification.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun finishNotify(
|
protected fun lastNotify(
|
||||||
id: Int,
|
id: Int,
|
||||||
editBody: (Notification.Builder) -> Notification.Builder? = { null }
|
editBody: (Notification.Builder) -> Notification.Builder? = { null }
|
||||||
) : Int {
|
) : Int {
|
||||||
@@ -57,6 +56,11 @@ abstract class NotificationService : BaseService(), KoinComponent {
|
|||||||
return newId
|
return newId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun remove(id: Int) = notifications.remove(id).also {
|
||||||
|
cancel(id)
|
||||||
|
updateForeground()
|
||||||
|
}
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
private fun notify(id: Int, notification: Notification) {
|
private fun notify(id: Int, notification: Notification) {
|
||||||
@@ -67,16 +71,13 @@ abstract class NotificationService : BaseService(), KoinComponent {
|
|||||||
Notifications.mgr.cancel(id)
|
Notifications.mgr.cancel(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun remove(id: Int) = notifications.remove(id).also {
|
|
||||||
cancel(id)
|
|
||||||
updateForeground()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateForeground() {
|
private fun updateForeground() {
|
||||||
if (hasNotifications)
|
if (hasNotifications) {
|
||||||
startForeground(notifications.keys.first(), notifications.values.first().build())
|
val first = notifications.entries.first()
|
||||||
else
|
startForeground(first.key, first.value.build())
|
||||||
|
} else {
|
||||||
stopForeground(true)
|
stopForeground(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --
|
// --
|
@@ -1,22 +1,26 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.core.download
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
||||||
|
import com.topjohnwu.magisk.core.view.Notifications
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
import com.topjohnwu.magisk.di.NullActivity
|
import com.topjohnwu.magisk.di.NullActivity
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.extensions.writeTo
|
import com.topjohnwu.magisk.extensions.writeTo
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Magisk
|
||||||
import com.topjohnwu.magisk.utils.ProgressInputStream
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Module
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
@@ -24,26 +28,20 @@ abstract class RemoteFileService : NotificationService() {
|
|||||||
|
|
||||||
val service: GithubRawServices by inject()
|
val service: GithubRawServices by inject()
|
||||||
|
|
||||||
override val defaultNotification
|
|
||||||
get() = Notifications.progress(this, "")
|
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
intent?.getParcelableExtra<DownloadSubject>(ARG_URL)?.let { start(it) }
|
intent?.getParcelableExtra<DownloadSubject>(ARG_URL)?.let { start(it) }
|
||||||
return START_REDELIVER_INTENT
|
return START_REDELIVER_INTENT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createNotification() = Notifications.progress(this, "")
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
private fun start(subject: DownloadSubject) = checkExisting(subject)
|
private fun start(subject: DownloadSubject) = checkExisting(subject)
|
||||||
.onErrorResumeNext { download(subject) }
|
.onErrorResumeNext { download(subject) }
|
||||||
.doOnSubscribe { update(subject.hashCode()) { it.setContentTitle(subject.title) } }
|
|
||||||
.subscribeK(onError = {
|
.subscribeK(onError = {
|
||||||
Timber.e(it)
|
Timber.e(it)
|
||||||
finishNotify(subject.hashCode()) { notification ->
|
failNotify(subject)
|
||||||
notification.setContentText(getString(R.string.download_file_error))
|
|
||||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
|
||||||
.setOngoing(false)
|
|
||||||
}
|
|
||||||
}) {
|
}) {
|
||||||
val newId = finishNotify(subject)
|
val newId = finishNotify(subject)
|
||||||
if (get<Activity>() !is NullActivity) {
|
if (get<Activity>() !is NullActivity) {
|
||||||
@@ -53,47 +51,55 @@ abstract class RemoteFileService : NotificationService() {
|
|||||||
|
|
||||||
private fun checkExisting(subject: DownloadSubject) = Completable.fromAction {
|
private fun checkExisting(subject: DownloadSubject) = Completable.fromAction {
|
||||||
check(subject is Magisk) { "Download cache is disabled" }
|
check(subject is Magisk) { "Download cache is disabled" }
|
||||||
|
check(subject.file.exists() &&
|
||||||
subject.file.also {
|
ShellUtils.checkSum("MD5", subject.file, subject.magisk.md5)) {
|
||||||
check(it.exists() && ShellUtils.checkSum("MD5", it, subject.magisk.md5)) {
|
"The given file does not match checksum"
|
||||||
"The given file does not match checksum"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun download(subject: DownloadSubject) = service.fetchFile(subject.url)
|
private fun download(subject: DownloadSubject) = service.fetchFile(subject.url)
|
||||||
.map { it.toStream(subject.hashCode()) }
|
.map { it.toProgressStream(subject) }
|
||||||
.flatMapCompletable { stream ->
|
.flatMapCompletable { stream ->
|
||||||
when (subject) {
|
when (subject) {
|
||||||
is Module -> service.fetchInstaller()
|
is Module -> service.fetchInstaller()
|
||||||
.doOnSuccess { stream.toModule(subject.file, it.byteStream()) }
|
.doOnSuccess { stream.toModule(subject.file, it.byteStream()) }
|
||||||
.ignoreElement()
|
.ignoreElement()
|
||||||
else -> Completable.fromAction { stream.writeTo(subject.file) }
|
else -> Completable.fromAction { stream.writeTo(subject.file) }
|
||||||
}
|
}
|
||||||
}.doOnComplete {
|
|
||||||
if (subject is Manager)
|
|
||||||
handleAPK(subject)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ResponseBody.toStream(id: Int): InputStream {
|
private fun ResponseBody.toProgressStream(subject: DownloadSubject): InputStream {
|
||||||
val maxRaw = contentLength()
|
val maxRaw = contentLength()
|
||||||
val max = maxRaw / 1_000_000f
|
val max = maxRaw / 1_000_000f
|
||||||
|
val id = subject.hashCode()
|
||||||
|
|
||||||
|
update(id) { it.setContentTitle(subject.title) }
|
||||||
|
|
||||||
return ProgressInputStream(byteStream()) {
|
return ProgressInputStream(byteStream()) {
|
||||||
val progress = it / 1_000_000f
|
val progress = it / 1_000_000f
|
||||||
update(id) { notification ->
|
update(id) { notification ->
|
||||||
if (maxRaw > 0) {
|
if (maxRaw > 0) {
|
||||||
|
send(progress / max, subject)
|
||||||
notification
|
notification
|
||||||
.setProgress(maxRaw.toInt(), it.toInt(), false)
|
.setProgress(maxRaw.toInt(), it.toInt(), false)
|
||||||
.setContentText("%.2f / %.2f MB".format(progress, max))
|
.setContentText("%.2f / %.2f MB".format(progress, max))
|
||||||
} else {
|
} else {
|
||||||
|
send(-1f, subject)
|
||||||
notification.setContentText("%.2f MB / ??".format(progress))
|
notification.setContentText("%.2f MB / ??".format(progress))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun finishNotify(subject: DownloadSubject) = finishNotify(subject.hashCode()) {
|
private fun failNotify(subject: DownloadSubject) = lastNotify(subject.hashCode()) {
|
||||||
|
send(0f, subject)
|
||||||
|
it.setContentText(getString(R.string.download_file_error))
|
||||||
|
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||||
|
.setOngoing(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun finishNotify(subject: DownloadSubject) = lastNotify(subject.hashCode()) {
|
||||||
|
send(1f, subject)
|
||||||
it.addActions(subject)
|
it.addActions(subject)
|
||||||
.setContentText(getString(R.string.download_complete))
|
.setContentText(getString(R.string.download_complete))
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
@@ -111,8 +117,19 @@ abstract class RemoteFileService : NotificationService() {
|
|||||||
protected abstract fun Notification.Builder.addActions(subject: DownloadSubject)
|
protected abstract fun Notification.Builder.addActions(subject: DownloadSubject)
|
||||||
: Notification.Builder
|
: Notification.Builder
|
||||||
|
|
||||||
companion object {
|
companion object : KoinComponent {
|
||||||
const val ARG_URL = "arg_url"
|
const val ARG_URL = "arg_url"
|
||||||
|
|
||||||
|
private val internalProgressBroadcast = MutableLiveData<Pair<Float, DownloadSubject>>()
|
||||||
|
val progressBroadcast: LiveData<Pair<Float, DownloadSubject>> get() = internalProgressBroadcast
|
||||||
|
|
||||||
|
fun send(progress: Float, subject: DownloadSubject) {
|
||||||
|
internalProgressBroadcast.postValue(progress to subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
internalProgressBroadcast.value = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.data.database.magiskdb
|
package com.topjohnwu.magisk.core.magiskdb
|
||||||
|
|
||||||
import androidx.annotation.StringDef
|
import androidx.annotation.StringDef
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
@@ -1,16 +1,12 @@
|
|||||||
package com.topjohnwu.magisk.data.database
|
package com.topjohnwu.magisk.core.magiskdb
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
|
import com.topjohnwu.magisk.core.model.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Delete
|
import com.topjohnwu.magisk.core.model.toMap
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Replace
|
import com.topjohnwu.magisk.core.model.toPolicy
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Select
|
|
||||||
import com.topjohnwu.magisk.extensions.now
|
import com.topjohnwu.magisk.extensions.now
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
|
||||||
import com.topjohnwu.magisk.model.entity.toMap
|
|
||||||
import com.topjohnwu.magisk.model.entity.toPolicy
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.data.database.magiskdb
|
package com.topjohnwu.magisk.core.magiskdb
|
||||||
|
|
||||||
import androidx.annotation.StringDef
|
import androidx.annotation.StringDef
|
||||||
|
|
@@ -1,9 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.data.database
|
package com.topjohnwu.magisk.core.magiskdb
|
||||||
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Delete
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Replace
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Select
|
|
||||||
|
|
||||||
class SettingsDao : BaseDao() {
|
class SettingsDao : BaseDao() {
|
||||||
|
|
@@ -1,9 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.data.database
|
package com.topjohnwu.magisk.core.magiskdb
|
||||||
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Delete
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Replace
|
|
||||||
import com.topjohnwu.magisk.data.database.magiskdb.Select
|
|
||||||
|
|
||||||
class StringDao : BaseDao() {
|
class StringDao : BaseDao() {
|
||||||
|
|
@@ -1,9 +1,9 @@
|
|||||||
package com.topjohnwu.magisk.model.entity
|
package com.topjohnwu.magisk.core.model
|
||||||
|
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.INTERACTIVE
|
||||||
import com.topjohnwu.magisk.extensions.getLabel
|
import com.topjohnwu.magisk.extensions.getLabel
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.INTERACTIVE
|
|
||||||
|
|
||||||
|
|
||||||
data class MagiskPolicy(
|
data class MagiskPolicy(
|
@@ -1,10 +1,10 @@
|
|||||||
package com.topjohnwu.magisk.model.entity
|
package com.topjohnwu.magisk.core.model
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import se.ansman.kotshi.JsonSerializable
|
|
||||||
|
|
||||||
@JsonSerializable
|
@JsonClass(generateAdapter = true)
|
||||||
data class UpdateInfo(
|
data class UpdateInfo(
|
||||||
val app: ManagerJson = ManagerJson(),
|
val app: ManagerJson = ManagerJson(),
|
||||||
val uninstaller: UninstallerJson = UninstallerJson(),
|
val uninstaller: UninstallerJson = UninstallerJson(),
|
||||||
@@ -12,12 +12,12 @@ data class UpdateInfo(
|
|||||||
val stub: StubJson = StubJson()
|
val stub: StubJson = StubJson()
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonSerializable
|
@JsonClass(generateAdapter = true)
|
||||||
data class UninstallerJson(
|
data class UninstallerJson(
|
||||||
val link: String = ""
|
val link: String = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonSerializable
|
@JsonClass(generateAdapter = true)
|
||||||
data class MagiskJson(
|
data class MagiskJson(
|
||||||
val version: String = "",
|
val version: String = "",
|
||||||
val versionCode: Int = -1,
|
val versionCode: Int = -1,
|
||||||
@@ -27,7 +27,7 @@ data class MagiskJson(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@JsonSerializable
|
@JsonClass(generateAdapter = true)
|
||||||
data class ManagerJson(
|
data class ManagerJson(
|
||||||
val version: String = "",
|
val version: String = "",
|
||||||
val versionCode: Int = -1,
|
val versionCode: Int = -1,
|
||||||
@@ -35,7 +35,7 @@ data class ManagerJson(
|
|||||||
val note: String = ""
|
val note: String = ""
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
@JsonSerializable
|
@JsonClass(generateAdapter = true)
|
||||||
data class StubJson(
|
data class StubJson(
|
||||||
val versionCode: Int = -1,
|
val versionCode: Int = -1,
|
||||||
val link: String = ""
|
val link: String = ""
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.module
|
package com.topjohnwu.magisk.core.model.module
|
||||||
|
|
||||||
abstract class BaseModule : Comparable<BaseModule> {
|
abstract class BaseModule : Comparable<BaseModule> {
|
||||||
abstract var id: String
|
abstract var id: String
|
@@ -1,7 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.module
|
package com.topjohnwu.magisk.core.model.module
|
||||||
|
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.io.SuFile
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
|
|
@@ -1,9 +1,9 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.module
|
package com.topjohnwu.magisk.core.model.module
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.data.repository.StringRepository
|
import com.topjohnwu.magisk.data.repository.StringRepository
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.legalFilename
|
import com.topjohnwu.magisk.extensions.legalFilename
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.su
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -6,20 +6,26 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.ProviderCallHandler
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.intent
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy
|
||||||
|
import com.topjohnwu.magisk.core.model.toPolicy
|
||||||
|
import com.topjohnwu.magisk.core.utils.Utils
|
||||||
|
import com.topjohnwu.magisk.core.wrap
|
||||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.startActivity
|
import com.topjohnwu.magisk.extensions.startActivity
|
||||||
import com.topjohnwu.magisk.extensions.startActivityWithRoot
|
import com.topjohnwu.magisk.extensions.startActivityWithRoot
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
import com.topjohnwu.magisk.legacy.surequest.SuRequestActivity
|
||||||
import com.topjohnwu.magisk.model.entity.toLog
|
import com.topjohnwu.magisk.model.entity.toLog
|
||||||
import com.topjohnwu.magisk.model.entity.toPolicy
|
|
||||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
object SuHandler : ProviderCallHandler {
|
object SuCallbackHandler : ProviderCallHandler {
|
||||||
|
|
||||||
const val REQUEST = "request"
|
const val REQUEST = "request"
|
||||||
const val LOG = "log"
|
const val LOG = "log"
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.su
|
||||||
|
|
||||||
import android.net.LocalSocket
|
import android.net.LocalSocket
|
||||||
import android.net.LocalSocketAddress
|
import android.net.LocalSocketAddress
|
@@ -0,0 +1,103 @@
|
|||||||
|
package com.topjohnwu.magisk.core.su
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.CountDownTimer
|
||||||
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy
|
||||||
|
import com.topjohnwu.magisk.core.model.toPolicy
|
||||||
|
import com.topjohnwu.magisk.extensions.now
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
abstract class SuRequestHandler(
|
||||||
|
private val packageManager: PackageManager,
|
||||||
|
private val policyDB: PolicyDao
|
||||||
|
) {
|
||||||
|
protected var timer: CountDownTimer = object : CountDownTimer(
|
||||||
|
TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(1)) {
|
||||||
|
override fun onFinish() {
|
||||||
|
respond(MagiskPolicy.DENY, 0)
|
||||||
|
}
|
||||||
|
override fun onTick(remains: Long) {}
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
field.cancel()
|
||||||
|
field = value
|
||||||
|
field.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected lateinit var policy: MagiskPolicy
|
||||||
|
|
||||||
|
private val cleanupTasks = mutableListOf<() -> Unit>()
|
||||||
|
private lateinit var connector: SuConnector
|
||||||
|
|
||||||
|
abstract fun onStart()
|
||||||
|
abstract fun onRespond()
|
||||||
|
|
||||||
|
fun start(intent: Intent): Boolean {
|
||||||
|
val socketName = intent.getStringExtra("socket") ?: return false
|
||||||
|
|
||||||
|
try {
|
||||||
|
connector = object : SuConnector(socketName) {
|
||||||
|
override fun onResponse() {
|
||||||
|
out.writeInt(policy.policy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val map = connector.readRequest()
|
||||||
|
val uid = map["uid"]?.toIntOrNull() ?: return false
|
||||||
|
policy = uid.toPolicy(packageManager)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never allow com.topjohnwu.magisk (could be malware)
|
||||||
|
if (policy.packageName == BuildConfig.APPLICATION_ID)
|
||||||
|
return false
|
||||||
|
|
||||||
|
when (Config.suAutoReponse) {
|
||||||
|
Config.Value.SU_AUTO_DENY -> {
|
||||||
|
respond(MagiskPolicy.DENY, 0)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
Config.Value.SU_AUTO_ALLOW -> {
|
||||||
|
respond(MagiskPolicy.ALLOW, 0)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.start()
|
||||||
|
cleanupTasks.add {
|
||||||
|
timer.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
onStart()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun respond() {
|
||||||
|
connector.response()
|
||||||
|
cleanupTasks.forEach { it() }
|
||||||
|
onRespond()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun respond(action: Int, time: Int) {
|
||||||
|
val until = if (time > 0)
|
||||||
|
TimeUnit.MILLISECONDS.toSeconds(now) + TimeUnit.MINUTES.toSeconds(time.toLong())
|
||||||
|
else
|
||||||
|
time.toLong()
|
||||||
|
|
||||||
|
policy.policy = action
|
||||||
|
policy.until = until
|
||||||
|
policy.uid = policy.uid % 100000 + Const.USER_ID * 100000
|
||||||
|
|
||||||
|
if (until >= 0)
|
||||||
|
policyDB.update(policy).blockingAwait()
|
||||||
|
|
||||||
|
respond()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,37 @@
|
|||||||
|
package com.topjohnwu.magisk.core.tasks
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.base.BaseReceiver
|
||||||
|
import com.topjohnwu.magisk.core.view.Notifications
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
private const val DTB_PATCH_RESULT = "dtb_result"
|
||||||
|
private const val DTB_PATCH_ACTION = "com.topjohnwu.magisk.DTBO_PATCH"
|
||||||
|
|
||||||
|
private class DTBPatchReceiver : BaseReceiver() {
|
||||||
|
override fun onReceive(context: ContextWrapper, intent: Intent?) {
|
||||||
|
intent?.also {
|
||||||
|
val result = it.getIntExtra(DTB_PATCH_RESULT, 1)
|
||||||
|
Timber.d("result=[$result]")
|
||||||
|
if (result == 0)
|
||||||
|
Notifications.dtboPatched(context)
|
||||||
|
}
|
||||||
|
context.unregisterReceiver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun patchDTB(context: Context) {
|
||||||
|
if (Info.isNewReboot) {
|
||||||
|
val c = context.applicationContext
|
||||||
|
c.registerReceiver(DTBPatchReceiver(), IntentFilter(DTB_PATCH_ACTION))
|
||||||
|
val broadcastCmd = "am broadcast --user ${Const.USER_ID} -p ${c.packageName} " +
|
||||||
|
"-a $DTB_PATCH_ACTION --ei $DTB_PATCH_RESULT \$result"
|
||||||
|
Shell.su("mm_patch_dtb '$broadcastCmd'").submit()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,10 @@
|
|||||||
|
package com.topjohnwu.magisk.core.tasks
|
||||||
|
|
||||||
|
import androidx.annotation.MainThread
|
||||||
|
|
||||||
|
interface FlashResultListener {
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
fun onResult(success: Boolean)
|
||||||
|
|
||||||
|
}
|
@@ -1,13 +1,13 @@
|
|||||||
package com.topjohnwu.magisk.tasks
|
package com.topjohnwu.magisk.core.tasks
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.utils.unzip
|
||||||
import com.topjohnwu.magisk.extensions.fileName
|
import com.topjohnwu.magisk.extensions.fileName
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
import com.topjohnwu.magisk.extensions.readUri
|
import com.topjohnwu.magisk.extensions.readUri
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.utils.unzip
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -18,7 +18,7 @@ abstract class FlashZip(
|
|||||||
private val mUri: Uri,
|
private val mUri: Uri,
|
||||||
private val console: MutableList<String>,
|
private val console: MutableList<String>,
|
||||||
private val logs: MutableList<String>
|
private val logs: MutableList<String>
|
||||||
) {
|
) : FlashResultListener {
|
||||||
|
|
||||||
private val context: Context by inject()
|
private val context: Context by inject()
|
||||||
private val installFolder = File(context.cacheDir, "flash").apply {
|
private val installFolder = File(context.cacheDir, "flash").apply {
|
||||||
@@ -94,6 +94,4 @@ abstract class FlashZip(
|
|||||||
.subscribeK(onError = { onResult(false) }) { onResult(it) }
|
.subscribeK(onError = { onResult(false) }) { onResult(it) }
|
||||||
.let { Unit } // ignores result disposable
|
.let { Unit } // ignores result disposable
|
||||||
|
|
||||||
|
|
||||||
protected abstract fun onResult(success: Boolean)
|
|
||||||
}
|
}
|
@@ -1,10 +1,9 @@
|
|||||||
package com.topjohnwu.magisk.model.flash
|
package com.topjohnwu.magisk.core.tasks
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.os.postDelayed
|
import androidx.core.os.postDelayed
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
import com.topjohnwu.magisk.tasks.FlashZip
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
|
|
||||||
@@ -28,16 +27,7 @@ sealed class Flashing(
|
|||||||
console: MutableList<String>,
|
console: MutableList<String>,
|
||||||
log: MutableList<String>,
|
log: MutableList<String>,
|
||||||
resultListener: FlashResultListener
|
resultListener: FlashResultListener
|
||||||
) : Flashing(uri, console, log, resultListener) {
|
) : Flashing(uri, console, log, resultListener)
|
||||||
|
|
||||||
override fun onResult(success: Boolean) {
|
|
||||||
if (success) {
|
|
||||||
//Utils.loadModules()
|
|
||||||
}
|
|
||||||
super.onResult(success)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class Uninstall(
|
class Uninstall(
|
||||||
uri: Uri,
|
uri: Uri,
|
@@ -1,13 +1,13 @@
|
|||||||
package com.topjohnwu.magisk.tasks
|
package com.topjohnwu.magisk.core.tasks
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import androidx.annotation.MainThread
|
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.topjohnwu.magisk.Config
|
import androidx.core.net.toUri
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
import com.topjohnwu.magisk.di.Protected
|
import com.topjohnwu.magisk.di.Protected
|
||||||
import com.topjohnwu.magisk.extensions.*
|
import com.topjohnwu.magisk.extensions.*
|
||||||
@@ -32,19 +32,19 @@ import java.nio.ByteBuffer
|
|||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
abstract class MagiskInstaller {
|
abstract class MagiskInstallImpl : FlashResultListener {
|
||||||
|
|
||||||
protected lateinit var srcBoot: String
|
|
||||||
protected lateinit var destFile: File
|
|
||||||
protected lateinit var installDir: File
|
protected lateinit var installDir: File
|
||||||
protected lateinit var zipUri: Uri
|
private lateinit var srcBoot: String
|
||||||
|
private lateinit var destFile: File
|
||||||
|
private lateinit var zipUri: Uri
|
||||||
|
|
||||||
private val console: MutableList<String>
|
private val console: MutableList<String>
|
||||||
private val logs: MutableList<String>
|
private val logs: MutableList<String>
|
||||||
private var tarOut: TarOutputStream? = null
|
private var tarOut: TarOutputStream? = null
|
||||||
|
|
||||||
private val service: GithubRawServices by inject()
|
private val service: GithubRawServices by inject()
|
||||||
private val context: Context by inject()
|
protected val context: Context by inject()
|
||||||
|
|
||||||
protected constructor() {
|
protected constructor() {
|
||||||
console = NOPList.getInstance()
|
console = NOPList.getInstance()
|
||||||
@@ -60,7 +60,7 @@ abstract class MagiskInstaller {
|
|||||||
installDir.mkdirs()
|
installDir.mkdirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun findImage(): Boolean {
|
private fun findImage(): Boolean {
|
||||||
srcBoot = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
|
srcBoot = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
|
||||||
if (srcBoot.isEmpty()) {
|
if (srcBoot.isEmpty()) {
|
||||||
console.add("! Unable to detect target image")
|
console.add("! Unable to detect target image")
|
||||||
@@ -70,7 +70,7 @@ abstract class MagiskInstaller {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun findSecondaryImage(): Boolean {
|
private fun findSecondaryImage(): Boolean {
|
||||||
val slot = "echo \$SLOT".fsh()
|
val slot = "echo \$SLOT".fsh()
|
||||||
val target = if (slot == "_a") "_b" else "_a"
|
val target = if (slot == "_a") "_b" else "_a"
|
||||||
console.add("- Target slot: $target")
|
console.add("- Target slot: $target")
|
||||||
@@ -87,7 +87,7 @@ abstract class MagiskInstaller {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun extractZip(): Boolean {
|
private fun extractZip(): Boolean {
|
||||||
val arch: String
|
val arch: String
|
||||||
arch = if (Build.VERSION.SDK_INT >= 21) {
|
arch = if (Build.VERSION.SDK_INT >= 21) {
|
||||||
val abis = listOf(*Build.SUPPORTED_ABIS)
|
val abis = listOf(*Build.SUPPORTED_ABIS)
|
||||||
@@ -164,7 +164,7 @@ abstract class MagiskInstaller {
|
|||||||
FileOutputStream(extract).use { tarIn.copyTo(it) }
|
FileOutputStream(extract).use { tarIn.copyTo(it) }
|
||||||
if (name.contains(".lz4")) {
|
if (name.contains(".lz4")) {
|
||||||
console.add("-- Decompressing: $name")
|
console.add("-- Decompressing: $name")
|
||||||
"./magiskboot --decompress $extract".sh()
|
"./magiskboot decompress $extract".sh()
|
||||||
}
|
}
|
||||||
} else if (entry.name.contains("vbmeta.img")) {
|
} else if (entry.name.contains("vbmeta.img")) {
|
||||||
vbmeta = true
|
vbmeta = true
|
||||||
@@ -189,9 +189,9 @@ abstract class MagiskInstaller {
|
|||||||
srcBoot = recovery.path
|
srcBoot = recovery.path
|
||||||
// Repack boot image to prevent restore
|
// Repack boot image to prevent restore
|
||||||
arrayOf(
|
arrayOf(
|
||||||
"./magiskboot --unpack boot.img",
|
"./magiskboot unpack boot.img",
|
||||||
"./magiskboot --repack boot.img",
|
"./magiskboot repack boot.img",
|
||||||
"./magiskboot --cleanup",
|
"./magiskboot cleanup",
|
||||||
"mv new-boot.img boot.img").sh()
|
"mv new-boot.img boot.img").sh()
|
||||||
SuFileInputStream(boot).use {
|
SuFileInputStream(boot).use {
|
||||||
tarOut.putNextEntry(newEntry("boot.img", boot.length()))
|
tarOut.putNextEntry(newEntry("boot.img", boot.length()))
|
||||||
@@ -208,7 +208,7 @@ abstract class MagiskInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun handleFile(uri: Uri): Boolean {
|
private fun handleFile(uri: Uri): Boolean {
|
||||||
try {
|
try {
|
||||||
context.readUri(uri).buffered().use {
|
context.readUri(uri).buffered().use {
|
||||||
it.mark(500)
|
it.mark(500)
|
||||||
@@ -238,7 +238,7 @@ abstract class MagiskInstaller {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun patchBoot(): Boolean {
|
private fun patchBoot(): Boolean {
|
||||||
var isSigned = false
|
var isSigned = false
|
||||||
try {
|
try {
|
||||||
SuFileInputStream(srcBoot).use {
|
SuFileInputStream(srcBoot).use {
|
||||||
@@ -259,7 +259,7 @@ abstract class MagiskInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val job = Shell.sh(
|
val job = Shell.sh(
|
||||||
"./magiskboot --cleanup",
|
"./magiskboot cleanup",
|
||||||
"mv bin/busybox busybox",
|
"mv bin/busybox busybox",
|
||||||
"rm -rf magisk.apk bin boot.img update-binary",
|
"rm -rf magisk.apk bin boot.img update-binary",
|
||||||
"cd /")
|
"cd /")
|
||||||
@@ -284,7 +284,7 @@ abstract class MagiskInstaller {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun flashBoot(): Boolean {
|
private fun flashBoot(): Boolean {
|
||||||
if (!"direct_install $installDir $srcBoot".sh().isSuccess)
|
if (!"direct_install $installDir $srcBoot".sh().isSuccess)
|
||||||
return false
|
return false
|
||||||
arrayOf(
|
arrayOf(
|
||||||
@@ -294,13 +294,13 @@ abstract class MagiskInstaller {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun storeBoot(): Boolean {
|
private fun storeBoot(): Boolean {
|
||||||
val patched = SuFile.open(installDir, "new-boot.img")
|
val patched = SuFile.open(installDir, "new-boot.img")
|
||||||
try {
|
try {
|
||||||
val os = tarOut?.let {
|
val os = tarOut?.let {
|
||||||
it.putNextEntry(newEntry(
|
it.putNextEntry(newEntry(
|
||||||
if (srcBoot.contains("recovery")) "recovery.img" else "boot.img",
|
if (srcBoot.contains("recovery")) "recovery.img" else "boot.img",
|
||||||
patched.length()))
|
patched.length()))
|
||||||
tarOut = null
|
tarOut = null
|
||||||
it
|
it
|
||||||
} ?: destFile.outputStream()
|
} ?: destFile.outputStream()
|
||||||
@@ -320,7 +320,7 @@ abstract class MagiskInstaller {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun postOTA(): Boolean {
|
private fun postOTA(): Boolean {
|
||||||
val bootctl = SuFile("/data/adb/bootctl")
|
val bootctl = SuFile("/data/adb/bootctl")
|
||||||
try {
|
try {
|
||||||
withStreams(service.fetchBootctl().blockingGet().byteStream(), bootctl.suOutputStream()) {
|
withStreams(service.fetchBootctl().blockingGet().byteStream(), bootctl.suOutputStream()) {
|
||||||
@@ -345,12 +345,24 @@ abstract class MagiskInstaller {
|
|||||||
private fun String.fsh() = ShellUtils.fastCmd(this)
|
private fun String.fsh() = ShellUtils.fastCmd(this)
|
||||||
private fun Array<String>.fsh() = ShellUtils.fastCmd(*this)
|
private fun Array<String>.fsh() = ShellUtils.fastCmd(*this)
|
||||||
|
|
||||||
|
protected fun doPatchFile(patchFile: Uri) =
|
||||||
|
extractZip() && handleFile(patchFile) && patchBoot() && storeBoot()
|
||||||
|
|
||||||
|
protected fun direct() = findImage() && extractZip() && patchBoot() && flashBoot()
|
||||||
|
|
||||||
|
protected fun secondSlot() =
|
||||||
|
findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA()
|
||||||
|
|
||||||
|
protected fun fixEnv(zip: File): Boolean {
|
||||||
|
installDir = SuFile("/data/adb/magisk")
|
||||||
|
Shell.su("rm -rf /data/adb/magisk/*").exec()
|
||||||
|
zipUri = zip.toUri()
|
||||||
|
return extractZip() && Shell.su("fix_env").exec().isSuccess
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
protected abstract fun operations(): Boolean
|
protected abstract fun operations(): Boolean
|
||||||
|
|
||||||
@MainThread
|
|
||||||
protected abstract fun onResult(success: Boolean)
|
|
||||||
|
|
||||||
fun exec() {
|
fun exec() {
|
||||||
Single.fromCallable { operations() }.subscribeK { onResult(it) }
|
Single.fromCallable { operations() }.subscribeK { onResult(it) }
|
||||||
}
|
}
|
@@ -0,0 +1,77 @@
|
|||||||
|
package com.topjohnwu.magisk.core.tasks
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.os.postDelayed
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.utils.Utils
|
||||||
|
import com.topjohnwu.magisk.extensions.reboot
|
||||||
|
import com.topjohnwu.magisk.model.events.dialog.EnvFixDialog
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
sealed class MagiskInstaller(
|
||||||
|
file: Uri,
|
||||||
|
private val console: MutableList<String>,
|
||||||
|
logs: MutableList<String>,
|
||||||
|
private val resultListener: FlashResultListener
|
||||||
|
) : MagiskInstallImpl(file, console, logs) {
|
||||||
|
|
||||||
|
override fun onResult(success: Boolean) {
|
||||||
|
if (success) {
|
||||||
|
console.add("- All done!")
|
||||||
|
} else {
|
||||||
|
Shell.sh("rm -rf $installDir").submit()
|
||||||
|
console.add("! Installation failed")
|
||||||
|
}
|
||||||
|
resultListener.onResult(success)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Patch(
|
||||||
|
file: Uri,
|
||||||
|
private val uri: Uri,
|
||||||
|
console: MutableList<String>,
|
||||||
|
logs: MutableList<String>,
|
||||||
|
resultListener: FlashResultListener
|
||||||
|
) : MagiskInstaller(file, console, logs, resultListener) {
|
||||||
|
override fun operations() = doPatchFile(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
class SecondSlot(
|
||||||
|
file: Uri,
|
||||||
|
console: MutableList<String>,
|
||||||
|
logs: MutableList<String>,
|
||||||
|
resultListener: FlashResultListener
|
||||||
|
) : MagiskInstaller(file, console, logs, resultListener) {
|
||||||
|
override fun operations() = secondSlot()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Direct(
|
||||||
|
file: Uri,
|
||||||
|
console: MutableList<String>,
|
||||||
|
logs: MutableList<String>,
|
||||||
|
resultListener: FlashResultListener
|
||||||
|
) : MagiskInstaller(file, console, logs, resultListener) {
|
||||||
|
override fun operations() = direct()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class EnvFixTask(
|
||||||
|
private val zip: File
|
||||||
|
) : MagiskInstallImpl() {
|
||||||
|
override fun operations() = fixEnv(zip)
|
||||||
|
|
||||||
|
override fun onResult(success: Boolean) {
|
||||||
|
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(EnvFixDialog.DISMISS))
|
||||||
|
Utils.toast(
|
||||||
|
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
)
|
||||||
|
if (success)
|
||||||
|
UiThreadHandler.handler.postDelayed(5000) { reboot() }
|
||||||
|
}
|
||||||
|
}
|
101
app/src/main/java/com/topjohnwu/magisk/core/tasks/RepoUpdater.kt
Normal file
101
app/src/main/java/com/topjohnwu/magisk/core/tasks/RepoUpdater.kt
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package com.topjohnwu.magisk.core.tasks
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.model.module.Repo
|
||||||
|
import com.topjohnwu.magisk.data.database.RepoDao
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubApiServices
|
||||||
|
import io.reactivex.Completable
|
||||||
|
import io.reactivex.Flowable
|
||||||
|
import io.reactivex.rxkotlin.toFlowable
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.HashSet
|
||||||
|
|
||||||
|
class RepoUpdater(
|
||||||
|
private val api: GithubApiServices,
|
||||||
|
private val repoDB: RepoDao
|
||||||
|
) {
|
||||||
|
private fun loadRepos(repos: List<GithubRepoInfo>, cached: MutableSet<String>) =
|
||||||
|
repos.toFlowable().parallel().runOn(Schedulers.io()).map {
|
||||||
|
// Skip submission
|
||||||
|
if (it.id == "submission")
|
||||||
|
return@map
|
||||||
|
val repo = repoDB.getRepo(it.id)?.apply { cached.remove(it.id) } ?: Repo(it.id)
|
||||||
|
repo.runCatching {
|
||||||
|
update(it.pushDate)
|
||||||
|
repoDB.addRepo(this)
|
||||||
|
}.getOrElse(Timber::e)
|
||||||
|
}.sequential()
|
||||||
|
|
||||||
|
private fun loadPage(
|
||||||
|
cached: MutableSet<String>,
|
||||||
|
page: Int = 1,
|
||||||
|
etag: String = ""
|
||||||
|
): Flowable<Unit> = api.fetchRepos(page, etag).flatMap {
|
||||||
|
it.error()?.also { throw it }
|
||||||
|
it.response()?.run {
|
||||||
|
if (code() == HttpURLConnection.HTTP_NOT_MODIFIED)
|
||||||
|
return@run Flowable.error<Unit>(CachedException())
|
||||||
|
|
||||||
|
if (page == 1)
|
||||||
|
repoDB.etagKey = headers()[Const.Key.ETAG_KEY].orEmpty().trimEtag()
|
||||||
|
|
||||||
|
val flow = loadRepos(body()!!, cached)
|
||||||
|
if (headers()[Const.Key.LINK_KEY].orEmpty().contains("next")) {
|
||||||
|
flow.mergeWith(loadPage(cached, page + 1))
|
||||||
|
} else {
|
||||||
|
flow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun forcedReload(cached: MutableSet<String>) =
|
||||||
|
cached.toFlowable().parallel().runOn(Schedulers.io()).map {
|
||||||
|
runCatching {
|
||||||
|
Repo(it).update()
|
||||||
|
}.getOrElse(Timber::e)
|
||||||
|
}.sequential()
|
||||||
|
|
||||||
|
private fun String.trimEtag() = substring(indexOf('\"'), lastIndexOf('\"') + 1)
|
||||||
|
|
||||||
|
@Suppress("RedundantLambdaArrow")
|
||||||
|
operator fun invoke(forced: Boolean) : Completable {
|
||||||
|
return Flowable
|
||||||
|
.fromCallable { Collections.synchronizedSet(HashSet(repoDB.repoIDList)) }
|
||||||
|
.flatMap { cached ->
|
||||||
|
loadPage(cached, etag = repoDB.etagKey).doOnComplete {
|
||||||
|
repoDB.removeRepos(cached)
|
||||||
|
}.onErrorResumeNext { it: Throwable ->
|
||||||
|
if (it is CachedException) {
|
||||||
|
if (forced)
|
||||||
|
return@onErrorResumeNext forcedReload(cached)
|
||||||
|
} else {
|
||||||
|
Timber.e(it)
|
||||||
|
}
|
||||||
|
Flowable.empty()
|
||||||
|
}
|
||||||
|
}.ignoreElements()
|
||||||
|
}
|
||||||
|
|
||||||
|
class CachedException : Exception()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dateFormat: SimpleDateFormat =
|
||||||
|
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply {
|
||||||
|
timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class GithubRepoInfo(
|
||||||
|
val name: String,
|
||||||
|
val pushed_at: String
|
||||||
|
) {
|
||||||
|
val id get() = name
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
val pushDate = dateFormat.parse(pushed_at)!!
|
||||||
|
}
|
@@ -1,11 +1,11 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import androidx.biometric.BiometricManager
|
import androidx.biometric.BiometricManager
|
||||||
import androidx.biometric.BiometricPrompt
|
import androidx.biometric.BiometricPrompt
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.topjohnwu.magisk.Config
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
import org.koin.core.KoinComponent
|
import org.koin.core.KoinComponent
|
||||||
import org.koin.core.get
|
import org.koin.core.get
|
||||||
|
|
@@ -1,21 +1,17 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.Base64OutputStream
|
import android.util.Base64OutputStream
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.di.koinModules
|
import com.topjohnwu.magisk.core.utils.PatchAPK.ALPHANUM
|
||||||
import com.topjohnwu.magisk.utils.PatchAPK.ALPHANUM
|
|
||||||
import com.topjohnwu.signing.CryptoUtils.readCertificate
|
import com.topjohnwu.signing.CryptoUtils.readCertificate
|
||||||
import com.topjohnwu.signing.CryptoUtils.readPrivateKey
|
import com.topjohnwu.signing.CryptoUtils.readPrivateKey
|
||||||
import com.topjohnwu.superuser.internal.InternalUtils
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||||
import org.koin.core.context.GlobalContext
|
|
||||||
import org.koin.core.context.startKoin
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
@@ -33,7 +29,7 @@ private interface CertKeyProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
class Keygen: CertKeyProvider {
|
class Keygen(context: Context) : CertKeyProvider {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ALIAS = "magisk"
|
private const val ALIAS = "magisk"
|
||||||
@@ -50,10 +46,14 @@ class Keygen: CertKeyProvider {
|
|||||||
|
|
||||||
private val provider: CertKeyProvider
|
private val provider: CertKeyProvider
|
||||||
|
|
||||||
inner class KeyStoreProvider : CertKeyProvider {
|
inner class KeyStoreProvider :
|
||||||
|
CertKeyProvider {
|
||||||
private val ks by lazy { init() }
|
private val ks by lazy { init() }
|
||||||
override val cert by lazy { ks.getCertificate(ALIAS) as X509Certificate }
|
override val cert by lazy { ks.getCertificate(ALIAS) as X509Certificate }
|
||||||
override val key by lazy { ks.getKey(ALIAS, PASSWORD) as PrivateKey }
|
override val key by lazy { ks.getKey(
|
||||||
|
ALIAS,
|
||||||
|
PASSWORD
|
||||||
|
) as PrivateKey }
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestProvider : CertKeyProvider {
|
class TestProvider : CertKeyProvider {
|
||||||
@@ -66,9 +66,6 @@ class Keygen: CertKeyProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// This object could possibly be accessed from an external app
|
|
||||||
// Get context from reflection into Android's framework
|
|
||||||
val context = InternalUtils.getContext()
|
|
||||||
val pm = context.packageManager
|
val pm = context.packageManager
|
||||||
val info = pm.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
|
val info = pm.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
|
||||||
val sig = info.signatures[0]
|
val sig = info.signatures[0]
|
||||||
@@ -100,21 +97,17 @@ class Keygen: CertKeyProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun init(): KeyStore {
|
private fun init(): KeyStore {
|
||||||
GlobalContext.getOrNull() ?: {
|
|
||||||
// Invoked externally, do some basic initialization
|
|
||||||
startKoin {
|
|
||||||
modules(koinModules)
|
|
||||||
}
|
|
||||||
Timber.plant(Timber.DebugTree())
|
|
||||||
}()
|
|
||||||
|
|
||||||
val raw = Config.keyStoreRaw
|
val raw = Config.keyStoreRaw
|
||||||
val ks = KeyStore.getInstance("PKCS12")
|
val ks = KeyStore.getInstance("PKCS12")
|
||||||
if (raw.isEmpty()) {
|
if (raw.isEmpty()) {
|
||||||
ks.load(null)
|
ks.load(null)
|
||||||
} else {
|
} else {
|
||||||
GZIPInputStream(Base64.decode(raw, BASE64_FLAG).inputStream()).use {
|
GZIPInputStream(Base64.decode(raw,
|
||||||
ks.load(it, PASSWORD)
|
BASE64_FLAG
|
||||||
|
).inputStream()).use {
|
||||||
|
ks.load(it,
|
||||||
|
PASSWORD
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,14 +120,20 @@ class Keygen: CertKeyProvider {
|
|||||||
val dname = X500Name("CN=${randomString()}")
|
val dname = X500Name("CN=${randomString()}")
|
||||||
val builder = JcaX509v3CertificateBuilder(dname, BigInteger(160, Random()),
|
val builder = JcaX509v3CertificateBuilder(dname, BigInteger(160, Random()),
|
||||||
start.time, end.time, dname, kp.public)
|
start.time, end.time, dname, kp.public)
|
||||||
val signer = JcaContentSignerBuilder("SHA256WithRSA").build(kp.private)
|
val signer = JcaContentSignerBuilder("SHA1WithRSA").build(kp.private)
|
||||||
val cert = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
val cert = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
||||||
|
|
||||||
// Store them into keystore
|
// Store them into keystore
|
||||||
ks.setKeyEntry(ALIAS, kp.private, PASSWORD, arrayOf(cert))
|
ks.setKeyEntry(
|
||||||
|
ALIAS, kp.private,
|
||||||
|
PASSWORD, arrayOf(cert))
|
||||||
val bytes = ByteArrayOutputStream()
|
val bytes = ByteArrayOutputStream()
|
||||||
GZIPOutputStream(Base64OutputStream(bytes, BASE64_FLAG)).use {
|
GZIPOutputStream(Base64OutputStream(bytes,
|
||||||
ks.store(it, PASSWORD)
|
BASE64_FLAG
|
||||||
|
)).use {
|
||||||
|
ks.store(it,
|
||||||
|
PASSWORD
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Config.keyStoreRaw = bytes.toString("UTF-8")
|
Config.keyStoreRaw = bytes.toString("UTF-8")
|
||||||
|
|
@@ -1,13 +1,16 @@
|
|||||||
@file:Suppress("DEPRECATION")
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.res.AssetManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import com.topjohnwu.magisk.Config
|
import android.util.DisplayMetrics
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.ResourceMgr
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.ResMgr
|
||||||
|
import com.topjohnwu.magisk.core.addAssetPath
|
||||||
import com.topjohnwu.magisk.extensions.langTagToLocale
|
import com.topjohnwu.magisk.extensions.langTagToLocale
|
||||||
import com.topjohnwu.magisk.extensions.toLangTag
|
import com.topjohnwu.magisk.extensions.toLangTag
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
@@ -22,41 +25,37 @@ val defaultLocale: Locale = Locale.getDefault()
|
|||||||
|
|
||||||
val availableLocales = Single.fromCallable {
|
val availableLocales = Single.fromCallable {
|
||||||
val compareId = R.string.app_changelog
|
val compareId = R.string.app_changelog
|
||||||
val config = ResourceMgr.resource.configuration
|
|
||||||
val metrics = ResourceMgr.resource.displayMetrics
|
|
||||||
val res = Resources(ResourceMgr.resource.assets, metrics, config)
|
|
||||||
|
|
||||||
val locales = mutableListOf<Locale>().apply {
|
// Create a completely new resource to prevent cross talk over app's configs
|
||||||
|
val asset = AssetManager::class.java.newInstance().apply { addAssetPath(ResMgr.apk) }
|
||||||
|
val config = Configuration(ResMgr.resource.configuration)
|
||||||
|
val metrics = DisplayMetrics().apply { setTo(ResMgr.resource.displayMetrics) }
|
||||||
|
val res = Resources(asset, metrics, config)
|
||||||
|
|
||||||
|
val locales = ArrayList<String>().apply {
|
||||||
// Add default locale
|
// Add default locale
|
||||||
add(Locale.ENGLISH)
|
add("en")
|
||||||
|
|
||||||
// Add some special locales
|
// Add some special locales
|
||||||
add(Locale.TAIWAN)
|
add("zh-TW")
|
||||||
add(Locale("pt", "BR"))
|
add("pt-BR")
|
||||||
|
|
||||||
// Other locales
|
// Then add all supported locales
|
||||||
val otherLocales = ResourceMgr.resource.assets.locales
|
addAll(res.assets.locales)
|
||||||
.map { it.langTagToLocale() }
|
}.map {
|
||||||
.distinctBy {
|
it.langTagToLocale()
|
||||||
config.setLocale(it)
|
}.distinctBy {
|
||||||
res.updateConfiguration(config, metrics)
|
config.setLocale(it)
|
||||||
res.getString(compareId)
|
res.updateConfiguration(config, metrics)
|
||||||
}
|
res.getString(compareId)
|
||||||
|
|
||||||
addAll(otherLocales)
|
|
||||||
}.sortedWith(Comparator { a, b ->
|
}.sortedWith(Comparator { a, b ->
|
||||||
a.getDisplayName(a).toLowerCase(a)
|
a.getDisplayName(a).compareTo(b.getDisplayName(b), true)
|
||||||
.compareTo(b.getDisplayName(b).toLowerCase(b))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
config.setLocale(defaultLocale)
|
config.setLocale(defaultLocale)
|
||||||
res.updateConfiguration(config, metrics)
|
res.updateConfiguration(config, metrics)
|
||||||
val defName = res.getString(R.string.system_default)
|
val defName = res.getString(R.string.system_default)
|
||||||
|
|
||||||
// Restore back to current locale
|
|
||||||
config.setLocale(currentLocale)
|
|
||||||
res.updateConfiguration(config, metrics)
|
|
||||||
|
|
||||||
Pair(locales, defName)
|
Pair(locales, defName)
|
||||||
}.map { (locales, defName) ->
|
}.map { (locales, defName) ->
|
||||||
val names = ArrayList<String>(locales.size + 1)
|
val names = ArrayList<String>(locales.size + 1)
|
||||||
@@ -85,5 +84,5 @@ fun refreshLocale() {
|
|||||||
else -> localeConfig.langTagToLocale()
|
else -> localeConfig.langTagToLocale()
|
||||||
}
|
}
|
||||||
Locale.setDefault(currentLocale)
|
Locale.setDefault(currentLocale)
|
||||||
ResourceMgr.resource.updateConfig()
|
ResMgr.resource.updateConfig()
|
||||||
}
|
}
|
@@ -1,19 +1,22 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build.VERSION.SDK_INT
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||||
|
import com.topjohnwu.magisk.core.view.Notifications
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
import com.topjohnwu.magisk.extensions.DynamicClassLoader
|
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.extensions.writeTo
|
import com.topjohnwu.magisk.extensions.writeTo
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
|
||||||
import com.topjohnwu.signing.JarMap
|
import com.topjohnwu.signing.JarMap
|
||||||
import com.topjohnwu.signing.SignAPK
|
import com.topjohnwu.signing.SignAPK
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Single
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
@@ -23,14 +26,15 @@ import java.security.SecureRandom
|
|||||||
|
|
||||||
object PatchAPK {
|
object PatchAPK {
|
||||||
|
|
||||||
private const val LOWERALPHA = "abcdefghijklmnopqrstuvwxyz"
|
private const val ALPHA = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
private val UPPERALPHA = LOWERALPHA.toUpperCase()
|
|
||||||
private val ALPHA = LOWERALPHA + UPPERALPHA
|
|
||||||
private const val DIGITS = "0123456789"
|
private const val DIGITS = "0123456789"
|
||||||
val ALPHANUM = ALPHA + DIGITS
|
const val ALPHANUM = ALPHA + DIGITS
|
||||||
private val ALPHANUMDOTS = "$ALPHANUM............"
|
private const val ALPHANUMDOTS = "$ALPHANUM............"
|
||||||
|
|
||||||
private fun genPackageName(prefix: String, length: Int): String {
|
private const val APP_ID = "com.topjohnwu.magisk"
|
||||||
|
private const val APP_NAME = "Magisk Manager"
|
||||||
|
|
||||||
|
private fun genPackageName(prefix: String, length: Int): CharSequence {
|
||||||
val builder = StringBuilder(length)
|
val builder = StringBuilder(length)
|
||||||
builder.append(prefix)
|
builder.append(prefix)
|
||||||
val len = length - prefix.length
|
val len = length - prefix.length
|
||||||
@@ -46,10 +50,10 @@ object PatchAPK {
|
|||||||
builder.append(next)
|
builder.append(next)
|
||||||
prev = next
|
prev = next
|
||||||
}
|
}
|
||||||
return builder.toString()
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findAndPatch(xml: ByteArray, from: String, to: String): Boolean {
|
private fun findAndPatch(xml: ByteArray, from: CharSequence, to: CharSequence): Boolean {
|
||||||
if (to.length > from.length)
|
if (to.length > from.length)
|
||||||
return false
|
return false
|
||||||
val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer()
|
val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer()
|
||||||
@@ -68,7 +72,7 @@ object PatchAPK {
|
|||||||
if (offList.isEmpty())
|
if (offList.isEmpty())
|
||||||
return false
|
return false
|
||||||
|
|
||||||
val toBuf = to.toCharArray().copyOf(from.length)
|
val toBuf = to.toString().toCharArray().copyOf(from.length)
|
||||||
for (off in offList) {
|
for (off in offList) {
|
||||||
buf.position(off)
|
buf.position(off)
|
||||||
buf.put(toBuf)
|
buf.put(toBuf)
|
||||||
@@ -76,18 +80,40 @@ object PatchAPK {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun patch(apk: String, out: String, pkg: CharSequence, label: CharSequence): Boolean {
|
||||||
|
try {
|
||||||
|
val jar = JarMap.open(apk)
|
||||||
|
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
|
||||||
|
val xml = jar.getRawData(je)
|
||||||
|
|
||||||
|
if (!findAndPatch(xml, APP_ID, pkg) ||
|
||||||
|
!findAndPatch(xml, APP_NAME, label))
|
||||||
|
return false
|
||||||
|
|
||||||
|
// Write apk changes
|
||||||
|
jar.getOutputStream(je).write(xml)
|
||||||
|
val keys = Keygen(get())
|
||||||
|
SignAPK.sign(keys.cert, keys.key, jar, FileOutputStream(out).buffered())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun patchAndHide(context: Context, label: String): Boolean {
|
private fun patchAndHide(context: Context, label: String): Boolean {
|
||||||
val src = if (!isRunningAsStub && SDK_INT >= 28 &&
|
val dlStub = !isRunningAsStub && SDK_INT >= 28 &&
|
||||||
Info.env.magiskVersionCode >= Const.Version.PROVIDER_CONNECT) {
|
Info.env.magiskVersionCode >= Const.Version.PROVIDER_CONNECT
|
||||||
// If not running as stub, and we are compatible with stub, use stub
|
val src = if (dlStub) {
|
||||||
val stub = File(context.cacheDir, "stub.apk")
|
val stub = File(context.cacheDir, "stub.apk")
|
||||||
val svc = get<GithubRawServices>()
|
val svc = get<GithubRawServices>()
|
||||||
runCatching {
|
try {
|
||||||
svc.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use {
|
svc.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use {
|
||||||
it.writeTo(stub)
|
it.writeTo(stub)
|
||||||
}
|
}
|
||||||
}.onFailure {
|
} catch (e: Exception) {
|
||||||
Timber.e(it)
|
Timber.e(e)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
stub.path
|
stub.path
|
||||||
@@ -97,7 +123,7 @@ object PatchAPK {
|
|||||||
|
|
||||||
// Generate a new random package name and signature
|
// Generate a new random package name and signature
|
||||||
val repack = File(context.cacheDir, "patched.apk")
|
val repack = File(context.cacheDir, "patched.apk")
|
||||||
val pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length)
|
val pkg = genPackageName("com.", APP_ID.length)
|
||||||
Config.keyStoreRaw = ""
|
Config.keyStoreRaw = ""
|
||||||
|
|
||||||
if (!patch(src, repack.path, pkg, label))
|
if (!patch(src, repack.path, pkg, label))
|
||||||
@@ -108,69 +134,22 @@ object PatchAPK {
|
|||||||
if (!Shell.su("force_pm_install $repack").exec().isSuccess)
|
if (!Shell.su("force_pm_install $repack").exec().isSuccess)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
Config.suManager = pkg
|
Config.suManager = pkg.toString()
|
||||||
Config.export()
|
Config.export()
|
||||||
Shell.su("pm uninstall ${BuildConfig.APPLICATION_ID}").submit()
|
Shell.su("pm uninstall $APP_ID").submit()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@JvmOverloads
|
|
||||||
fun patch(apk: String, out: String, pkg: String, label: String = "Manager"): Boolean {
|
|
||||||
try {
|
|
||||||
val jar = JarMap.open(apk)
|
|
||||||
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
|
|
||||||
val xml = jar.getRawData(je)
|
|
||||||
|
|
||||||
if (!findAndPatch(xml, BuildConfig.APPLICATION_ID, pkg) ||
|
|
||||||
!findAndPatch(xml, "Magisk Manager", label))
|
|
||||||
return false
|
|
||||||
|
|
||||||
// Write apk changes
|
|
||||||
jar.getOutputStream(je).write(xml)
|
|
||||||
val keys = Keygen()
|
|
||||||
SignAPK.sign(keys.cert, keys.key, jar, FileOutputStream(out).buffered())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun patch(apk: File, out: File, pkg: String, label: String): Boolean {
|
|
||||||
try {
|
|
||||||
if (apk.length() < 1 shl 18) {
|
|
||||||
// APK is smaller than 256K, must be stub
|
|
||||||
return patch(apk.path, out.path, pkg, label)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try using the new APK to patch itself
|
|
||||||
val loader = DynamicClassLoader(apk)
|
|
||||||
val cls = loader.loadClass("a.a")
|
|
||||||
|
|
||||||
for (m in cls.declaredMethods) {
|
|
||||||
val pars = m.parameterTypes
|
|
||||||
if (pars.size == 4 && pars[0] == String::class.java) {
|
|
||||||
return m.invoke(null, apk.path, out.path, pkg, label) as Boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw Exception("No matching method found")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e)
|
|
||||||
// Fallback to use the current implementation
|
|
||||||
return patch(apk.path, out.path, pkg, label)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hideManager(context: Context, label: String) {
|
fun hideManager(context: Context, label: String) {
|
||||||
Completable.fromAction {
|
val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title))
|
||||||
val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title))
|
Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build())
|
||||||
Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build())
|
Single.fromCallable {
|
||||||
if (!patchAndHide(context, label))
|
patchAndHide(context, label)
|
||||||
|
}.subscribeK {
|
||||||
|
if (!it)
|
||||||
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
|
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
|
||||||
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID)
|
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID)
|
||||||
}.subscribeK()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
import java.io.FilterInputStream
|
import java.io.FilterInputStream
|
@@ -0,0 +1,46 @@
|
|||||||
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.wrap
|
||||||
|
import com.topjohnwu.magisk.extensions.rawResource
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
|
|
||||||
|
class RootInit : Shell.Initializer() {
|
||||||
|
|
||||||
|
override fun onInit(context: Context, shell: Shell): Boolean {
|
||||||
|
return init(context.wrap(), shell)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(context: Context, shell: Shell): Boolean {
|
||||||
|
val job = shell.newJob()
|
||||||
|
job.add(context.rawResource(R.raw.manager))
|
||||||
|
if (shell.isRoot) {
|
||||||
|
job.add(context.rawResource(R.raw.util_functions))
|
||||||
|
.add("SHA1=`grep_prop SHA1 /sbin/.magisk/config`")
|
||||||
|
Const.MAGISK_DISABLE_FILE = SuFile("/cache/.disable_magisk")
|
||||||
|
}
|
||||||
|
|
||||||
|
job.add(
|
||||||
|
"export BOOTMODE=true",
|
||||||
|
"mount_partitions",
|
||||||
|
"get_flags",
|
||||||
|
"run_migrations"
|
||||||
|
).exec()
|
||||||
|
|
||||||
|
fun getvar(name: String) = ShellUtils.fastCmd(shell, "echo \$$name").toBoolean()
|
||||||
|
|
||||||
|
Info.keepVerity = getvar("KEEPVERITY")
|
||||||
|
Info.keepEnc = getvar("KEEPFORCEENCRYPT")
|
||||||
|
Info.isSAR = getvar("SYSTEM_ROOT")
|
||||||
|
Info.ramdisk = shell.newJob().add("check_boot_ramdisk").exec().isSuccess
|
||||||
|
Info.recovery = getvar("RECOVERYMODE")
|
||||||
|
Info.isAB = getvar("ISAB")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
|
interface SafetyNetHelper {
|
||||||
|
|
||||||
|
val version: Int
|
||||||
|
|
||||||
|
fun attest()
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onResponse(responseCode: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val RESPONSE_ERR = 0x01
|
||||||
|
const val CONNECTION_FAIL = 0x02
|
||||||
|
|
||||||
|
const val BASIC_PASS = 0x10
|
||||||
|
const val CTS_PASS = 0x20
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -7,18 +7,18 @@ import android.net.Uri
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
import com.topjohnwu.magisk.*
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.UpdateCheckService
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.model.update.UpdateCheckService
|
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
object Utils {
|
object Utils {
|
||||||
|
|
||||||
val isCanary: Boolean = BuildConfig.VERSION_NAME.contains("-")
|
|
||||||
|
|
||||||
fun toast(msg: CharSequence, duration: Int) {
|
fun toast(msg: CharSequence, duration: Int) {
|
||||||
UiThreadHandler.run { Toast.makeText(get(), msg, duration).show() }
|
UiThreadHandler.run { Toast.makeText(get(), msg, duration).show() }
|
||||||
}
|
}
|
||||||
@@ -40,19 +40,20 @@ object Utils {
|
|||||||
fun scheduleUpdateCheck(context: Context) {
|
fun scheduleUpdateCheck(context: Context) {
|
||||||
if (Config.checkUpdate) {
|
if (Config.checkUpdate) {
|
||||||
val constraints = Constraints.Builder()
|
val constraints = Constraints.Builder()
|
||||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||||
.setRequiresDeviceIdle(true)
|
.setRequiresDeviceIdle(true)
|
||||||
.build()
|
.build()
|
||||||
val request = PeriodicWorkRequest
|
val request = PeriodicWorkRequest
|
||||||
.Builder(ClassMap[UpdateCheckService::class.java] as Class<Worker>, 12, TimeUnit.HOURS)
|
.Builder(UpdateCheckService::class.java, 12, TimeUnit.HOURS)
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
.build()
|
.build()
|
||||||
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
|
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
|
||||||
Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID,
|
Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID,
|
||||||
ExistingPeriodicWorkPolicy.REPLACE, request)
|
ExistingPeriodicWorkPolicy.REPLACE, request
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
WorkManager.getInstance(context)
|
WorkManager.getInstance(context)
|
||||||
.cancelUniqueWork(Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID)
|
.cancelUniqueWork(Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,11 +63,14 @@ object Utils {
|
|||||||
if (intent.resolveActivity(context.packageManager) != null) {
|
if (intent.resolveActivity(context.packageManager) != null) {
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.open_link_failed_toast, Toast.LENGTH_SHORT)
|
toast(
|
||||||
|
R.string.open_link_failed_toast,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ensureDownloadPath(path : String) =
|
fun ensureDownloadPath(path: String) =
|
||||||
File(Environment.getExternalStorageDirectory(), path).run {
|
File(Environment.getExternalStorageDirectory(), path).run {
|
||||||
if ((exists() && isDirectory) || mkdirs()) this else null
|
if ((exists() && isDirectory) || mkdirs()) this else null
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import com.topjohnwu.superuser.io.SuFile
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
import com.topjohnwu.superuser.io.SuFileOutputStream
|
import com.topjohnwu.superuser.io.SuFileOutputStream
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.view
|
package com.topjohnwu.magisk.core.view
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
@@ -9,13 +9,12 @@ import android.os.Build.VERSION.SDK_INT
|
|||||||
import androidx.core.app.TaskStackBuilder
|
import androidx.core.app.TaskStackBuilder
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.graphics.drawable.toIcon
|
import androidx.core.graphics.drawable.toIcon
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.Const.ID.PROGRESS_NOTIFICATION_CHANNEL
|
import com.topjohnwu.magisk.core.*
|
||||||
import com.topjohnwu.magisk.Const.ID.UPDATE_NOTIFICATION_CHANNEL
|
import com.topjohnwu.magisk.core.Const.ID.PROGRESS_NOTIFICATION_CHANNEL
|
||||||
|
import com.topjohnwu.magisk.core.Const.ID.UPDATE_NOTIFICATION_CHANNEL
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.getBitmap
|
import com.topjohnwu.magisk.extensions.getBitmap
|
||||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity
|
|
||||||
|
|
||||||
object Notifications {
|
object Notifications {
|
||||||
|
|
||||||
@@ -52,10 +51,13 @@ object Notifications {
|
|||||||
val stackBuilder = TaskStackBuilder.create(context)
|
val stackBuilder = TaskStackBuilder.create(context)
|
||||||
stackBuilder.addParentStack(SplashActivity::class.java.cmp(context.packageName))
|
stackBuilder.addParentStack(SplashActivity::class.java.cmp(context.packageName))
|
||||||
stackBuilder.addNextIntent(intent)
|
stackBuilder.addNextIntent(intent)
|
||||||
val pendingIntent = stackBuilder.getPendingIntent(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
|
val pendingIntent = stackBuilder.getPendingIntent(
|
||||||
|
Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
|
||||||
val builder = updateBuilder(context)
|
val builder = updateBuilder(
|
||||||
|
context
|
||||||
|
)
|
||||||
.setContentTitle(context.getString(R.string.magisk_update_title))
|
.setContentTitle(context.getString(R.string.magisk_update_title))
|
||||||
.setContentText(context.getString(R.string.manager_download_install))
|
.setContentText(context.getString(R.string.manager_download_install))
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
@@ -72,7 +74,9 @@ object Notifications {
|
|||||||
val pendingIntent = PendingIntent.getBroadcast(context,
|
val pendingIntent = PendingIntent.getBroadcast(context,
|
||||||
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
|
||||||
val builder = updateBuilder(context)
|
val builder = updateBuilder(
|
||||||
|
context
|
||||||
|
)
|
||||||
.setContentTitle(context.getString(R.string.manager_update_title))
|
.setContentTitle(context.getString(R.string.manager_update_title))
|
||||||
.setContentText(context.getString(R.string.manager_download_install))
|
.setContentText(context.getString(R.string.manager_download_install))
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
@@ -87,7 +91,9 @@ object Notifications {
|
|||||||
val pendingIntent = PendingIntent.getBroadcast(context,
|
val pendingIntent = PendingIntent.getBroadcast(context,
|
||||||
Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
|
||||||
val builder = updateBuilder(context)
|
val builder = updateBuilder(
|
||||||
|
context
|
||||||
|
)
|
||||||
.setContentTitle(context.getString(R.string.dtbo_patched_title))
|
.setContentTitle(context.getString(R.string.dtbo_patched_title))
|
||||||
.setContentText(context.getString(R.string.dtbo_patched_reboot))
|
.setContentText(context.getString(R.string.dtbo_patched_reboot))
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.view
|
package com.topjohnwu.magisk.core.view
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -10,17 +10,18 @@ import androidx.annotation.RequiresApi
|
|||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.graphics.drawable.toAdaptiveIcon
|
import androidx.core.graphics.drawable.toAdaptiveIcon
|
||||||
import androidx.core.graphics.drawable.toIcon
|
import androidx.core.graphics.drawable.toIcon
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.*
|
||||||
|
import com.topjohnwu.magisk.core.utils.Utils
|
||||||
import com.topjohnwu.magisk.extensions.getBitmap
|
import com.topjohnwu.magisk.extensions.getBitmap
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity
|
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
|
||||||
|
|
||||||
object Shortcuts {
|
object Shortcuts {
|
||||||
|
|
||||||
fun setup(context: Context) {
|
fun setup(context: Context) {
|
||||||
if (Build.VERSION.SDK_INT >= 25) {
|
if (Build.VERSION.SDK_INT >= 25) {
|
||||||
val manager = context.getSystemService<ShortcutManager>()
|
val manager = context.getSystemService<ShortcutManager>()
|
||||||
manager?.dynamicShortcuts = getShortCuts(context)
|
manager?.dynamicShortcuts =
|
||||||
|
getShortCuts(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,19 +78,6 @@ object Shortcuts {
|
|||||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
)
|
)
|
||||||
.setIcon(getIcon(R.drawable.sc_extension))
|
.setIcon(getIcon(R.drawable.sc_extension))
|
||||||
.setRank(3)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
shortCuts.add(
|
|
||||||
ShortcutInfo.Builder(context, "downloads")
|
|
||||||
.setShortLabel(context.getString(R.string.downloads))
|
|
||||||
.setIntent(
|
|
||||||
Intent(intent)
|
|
||||||
.putExtra(Const.Key.OPEN_SECTION, "downloads")
|
|
||||||
.setAction(Intent.ACTION_VIEW)
|
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
|
||||||
)
|
|
||||||
.setIcon(getIcon(R.drawable.sc_cloud_download))
|
|
||||||
.setRank(2)
|
.setRank(2)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
67
app/src/main/java/com/topjohnwu/magisk/data/database/Repo.kt
Normal file
67
app/src/main/java/com/topjohnwu/magisk/data/database/Repo.kt
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
@file:JvmMultifileClass
|
||||||
|
|
||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.topjohnwu.magisk.core.model.module.Repo
|
||||||
|
|
||||||
|
interface RepoBase {
|
||||||
|
|
||||||
|
fun getRepos(offset: Int, limit: Int = LIMIT): List<Repo>
|
||||||
|
fun searchRepos(query: String, offset: Int, limit: Int = LIMIT): List<Repo>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM repos WHERE id = :id AND versionCode > :versionCode LIMIT 1")
|
||||||
|
fun getUpdatableRepoById(id: String, versionCode: Int): Repo?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM repos WHERE id = :id LIMIT 1")
|
||||||
|
fun getRepoById(id: String): Repo?
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LIMIT = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface RepoByUpdatedDao : RepoBase {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM repos ORDER BY last_update DESC LIMIT :limit OFFSET :offset")
|
||||||
|
override fun getRepos(offset: Int, limit: Int): List<Repo>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""SELECT *
|
||||||
|
FROM repos
|
||||||
|
WHERE
|
||||||
|
(author LIKE '%' || :query || '%') ||
|
||||||
|
(name LIKE '%' || :query || '%') ||
|
||||||
|
(description LIKE '%' || :query || '%')
|
||||||
|
ORDER BY last_update DESC
|
||||||
|
LIMIT :limit
|
||||||
|
OFFSET :offset"""
|
||||||
|
)
|
||||||
|
override fun searchRepos(query: String, offset: Int, limit: Int): List<Repo>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface RepoByNameDao : RepoBase {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM repos ORDER BY name COLLATE NOCASE LIMIT :limit OFFSET :offset")
|
||||||
|
override fun getRepos(offset: Int, limit: Int): List<Repo>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""SELECT *
|
||||||
|
FROM repos
|
||||||
|
WHERE
|
||||||
|
(author LIKE '%' || :query || '%') ||
|
||||||
|
(name LIKE '%' || :query || '%') ||
|
||||||
|
(description LIKE '%' || :query || '%')
|
||||||
|
ORDER BY name COLLATE NOCASE
|
||||||
|
LIMIT :limit
|
||||||
|
OFFSET :offset"""
|
||||||
|
)
|
||||||
|
override fun searchRepos(query: String, offset: Int, limit: Int): List<Repo>
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -1,13 +1,15 @@
|
|||||||
package com.topjohnwu.magisk.data.database
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.*
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
import com.topjohnwu.magisk.core.model.module.Repo
|
||||||
|
|
||||||
@Database(version = 6, entities = [Repo::class, RepoEtag::class])
|
@Database(version = 6, entities = [Repo::class, RepoEtag::class], exportSchema = false)
|
||||||
abstract class RepoDatabase : RoomDatabase() {
|
abstract class RepoDatabase : RoomDatabase() {
|
||||||
|
|
||||||
abstract fun repoDao() : RepoDao
|
abstract fun repoDao() : RepoDao
|
||||||
|
abstract fun repoByUpdatedDao(): RepoByUpdatedDao
|
||||||
|
abstract fun repoByNameDao(): RepoByNameDao
|
||||||
}
|
}
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
|
@@ -6,7 +6,7 @@ import io.reactivex.Completable
|
|||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@Database(version = 1, entities = [MagiskLog::class])
|
@Database(version = 1, entities = [MagiskLog::class], exportSchema = false)
|
||||||
abstract class SuLogDatabase : RoomDatabase() {
|
abstract class SuLogDatabase : RoomDatabase() {
|
||||||
|
|
||||||
abstract fun suLogDao(): SuLogDao
|
abstract fun suLogDao(): SuLogDao
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
package com.topjohnwu.magisk.data.network
|
package com.topjohnwu.magisk.data.network
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.model.entity.UpdateInfo
|
import com.topjohnwu.magisk.core.model.UpdateInfo
|
||||||
import com.topjohnwu.magisk.tasks.GithubRepoInfo
|
import com.topjohnwu.magisk.core.tasks.GithubRepoInfo
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.data.repository
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
import com.topjohnwu.magisk.data.database.SettingsDao
|
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
|
||||||
import com.topjohnwu.magisk.data.database.StringDao
|
import com.topjohnwu.magisk.core.magiskdb.StringDao
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import kotlin.properties.ReadWriteProperty
|
import kotlin.properties.ReadWriteProperty
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
@@ -1,20 +1,18 @@
|
|||||||
package com.topjohnwu.magisk.data.repository
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.data.database.SuLogDao
|
import com.topjohnwu.magisk.data.database.SuLogDao
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskLog
|
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||||
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
|
|
||||||
class LogRepository(
|
class LogRepository(
|
||||||
private val logDao: SuLogDao
|
private val logDao: SuLogDao
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun fetchLogs() = logDao.fetchAll().map { it.wrap() }
|
fun fetchLogs() = logDao.fetchAll()
|
||||||
|
|
||||||
fun fetchMagiskLogs() = Single.fromCallable {
|
fun fetchMagiskLogs() = Single.fromCallable {
|
||||||
Shell.su("tail -n 5000 ${Const.MAGISK_LOG}").exec().out
|
Shell.su("tail -n 5000 ${Const.MAGISK_LOG}").exec().out
|
||||||
@@ -28,11 +26,4 @@ class LogRepository(
|
|||||||
|
|
||||||
fun insert(log: MagiskLog) = logDao.insert(log)
|
fun insert(log: MagiskLog) = logDao.insert(log)
|
||||||
|
|
||||||
private fun List<MagiskLog>.wrap(): List<WrappedMagiskLog> {
|
|
||||||
val day = TimeUnit.DAYS.toMillis(1)
|
|
||||||
return groupBy { it.time / day }
|
|
||||||
.map { WrappedMagiskLog(it.key * day, it.value) }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
package com.topjohnwu.magisk.data.repository
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
import com.topjohnwu.magisk.extensions.getLabel
|
import com.topjohnwu.magisk.extensions.getLabel
|
||||||
import com.topjohnwu.magisk.extensions.packageName
|
import com.topjohnwu.magisk.extensions.packageName
|
||||||
@@ -24,7 +24,8 @@ class MagiskRepository(
|
|||||||
Config.Value.BETA_CHANNEL -> apiRaw.fetchBetaUpdate()
|
Config.Value.BETA_CHANNEL -> apiRaw.fetchBetaUpdate()
|
||||||
Config.Value.CANARY_CHANNEL -> apiRaw.fetchCanaryUpdate()
|
Config.Value.CANARY_CHANNEL -> apiRaw.fetchCanaryUpdate()
|
||||||
Config.Value.CANARY_DEBUG_CHANNEL -> apiRaw.fetchCanaryDebugUpdate()
|
Config.Value.CANARY_DEBUG_CHANNEL -> apiRaw.fetchCanaryDebugUpdate()
|
||||||
Config.Value.CUSTOM_CHANNEL -> apiRaw.fetchCustomUpdate(Config.customChannelUrl)
|
Config.Value.CUSTOM_CHANNEL -> apiRaw.fetchCustomUpdate(
|
||||||
|
Config.customChannelUrl)
|
||||||
else -> throw IllegalArgumentException()
|
else -> throw IllegalArgumentException()
|
||||||
}.flatMap {
|
}.flatMap {
|
||||||
// If remote version is lower than current installed, try switching to beta
|
// If remote version is lower than current installed, try switching to beta
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.data.repository
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.core.model.module.Repo
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
|
||||||
|
|
||||||
class StringRepository(
|
class StringRepository(
|
||||||
private val api: GithubRawServices
|
private val api: GithubRawServices
|
||||||
|
@@ -6,7 +6,9 @@ import android.app.Application
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.topjohnwu.magisk.core.ResMgr
|
||||||
import com.topjohnwu.magisk.utils.RxBus
|
import com.topjohnwu.magisk.utils.RxBus
|
||||||
import org.koin.core.qualifier.named
|
import org.koin.core.qualifier.named
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
@@ -16,13 +18,14 @@ val Protected = named("protected")
|
|||||||
|
|
||||||
val applicationModule = module {
|
val applicationModule = module {
|
||||||
single { RxBus() }
|
single { RxBus() }
|
||||||
factory { get<Context>().resources }
|
factory { ResMgr.resource }
|
||||||
factory { get<Context>().packageManager }
|
factory { get<Context>().packageManager }
|
||||||
factory(Protected) { createDEContext(get()) }
|
factory(Protected) { createDEContext(get()) }
|
||||||
single(SUTimeout) { get<Context>(Protected).getSharedPreferences("su_timeout", 0) }
|
single(SUTimeout) { get<Context>(Protected).getSharedPreferences("su_timeout", 0) }
|
||||||
single { PreferenceManager.getDefaultSharedPreferences(get<Context>(Protected)) }
|
single { PreferenceManager.getDefaultSharedPreferences(get<Context>(Protected)) }
|
||||||
single { ActivityTracker() }
|
single { ActivityTracker() }
|
||||||
factory { get<ActivityTracker>().foreground ?: NullActivity }
|
factory { get<ActivityTracker>().foreground ?: NullActivity }
|
||||||
|
single { LocalBroadcastManager.getInstance(get()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDEContext(context: Context): Context {
|
private fun createDEContext(context: Context): Context {
|
||||||
|
@@ -2,8 +2,12 @@ package com.topjohnwu.magisk.di
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import com.topjohnwu.magisk.data.database.*
|
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||||
import com.topjohnwu.magisk.tasks.RepoUpdater
|
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
|
||||||
|
import com.topjohnwu.magisk.core.magiskdb.StringDao
|
||||||
|
import com.topjohnwu.magisk.core.tasks.RepoUpdater
|
||||||
|
import com.topjohnwu.magisk.data.database.RepoDatabase
|
||||||
|
import com.topjohnwu.magisk.data.database.SuLogDatabase
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|
||||||
@@ -11,7 +15,10 @@ val databaseModule = module {
|
|||||||
single { PolicyDao(get()) }
|
single { PolicyDao(get()) }
|
||||||
single { SettingsDao() }
|
single { SettingsDao() }
|
||||||
single { StringDao() }
|
single { StringDao() }
|
||||||
single { createRepoDatabase(get()).repoDao() }
|
single { createRepoDatabase(get()) }
|
||||||
|
single { get<RepoDatabase>().repoDao() }
|
||||||
|
single { get<RepoDatabase>().repoByNameDao() }
|
||||||
|
single { get<RepoDatabase>().repoByUpdatedDao() }
|
||||||
single { createSuLogDatabase(get(Protected)).suLogDao() }
|
single { createSuLogDatabase(get(Protected)).suLogDao() }
|
||||||
single { RepoUpdater(get(), get()) }
|
single { RepoUpdater(get(), get()) }
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.squareup.moshi.JsonAdapter
|
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.data.network.GithubApiServices
|
import com.topjohnwu.magisk.data.network.GithubApiServices
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
import com.topjohnwu.magisk.net.Networking
|
import com.topjohnwu.magisk.net.Networking
|
||||||
@@ -20,7 +19,6 @@ import retrofit2.Retrofit
|
|||||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
import retrofit2.converter.scalars.ScalarsConverterFactory
|
import retrofit2.converter.scalars.ScalarsConverterFactory
|
||||||
import se.ansman.kotshi.KotshiJsonAdapterFactory
|
|
||||||
|
|
||||||
val networkingModule = module {
|
val networkingModule = module {
|
||||||
single { createOkHttpClient(get()) }
|
single { createOkHttpClient(get()) }
|
||||||
@@ -49,9 +47,7 @@ fun createOkHttpClient(context: Context): OkHttpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createMoshiConverterFactory(): MoshiConverterFactory {
|
fun createMoshiConverterFactory(): MoshiConverterFactory {
|
||||||
val moshi = Moshi.Builder()
|
val moshi = Moshi.Builder().build()
|
||||||
.add(KotshiJsonAdapterFactory)
|
|
||||||
.build()
|
|
||||||
return MoshiConverterFactory.create(moshi)
|
return MoshiConverterFactory.create(moshi)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,9 +59,6 @@ fun createRetrofit(okHttpClient: OkHttpClient): Retrofit.Builder {
|
|||||||
.client(okHttpClient)
|
.client(okHttpClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
@KotshiJsonAdapterFactory
|
|
||||||
abstract class JsonAdapterFactory : JsonAdapter.Factory
|
|
||||||
|
|
||||||
inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseUrl: String): T {
|
inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseUrl: String): T {
|
||||||
return retrofitBuilder
|
return retrofitBuilder
|
||||||
.baseUrl(baseUrl)
|
.baseUrl(baseUrl)
|
||||||
@@ -75,9 +68,9 @@ inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseU
|
|||||||
|
|
||||||
fun createMarkwon(context: Context, okHttpClient: OkHttpClient): Markwon {
|
fun createMarkwon(context: Context, okHttpClient: OkHttpClient): Markwon {
|
||||||
return Markwon.builder(context)
|
return Markwon.builder(context)
|
||||||
.usePlugin(HtmlPlugin.create())
|
.usePlugin(HtmlPlugin.create())
|
||||||
.usePlugin(ImagesPlugin.create {
|
.usePlugin(ImagesPlugin.create {
|
||||||
it.addSchemeHandler(OkHttpNetworkSchemeHandler.create(okHttpClient))
|
it.addSchemeHandler(OkHttpNetworkSchemeHandler.create(okHttpClient))
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
@@ -1,27 +1,35 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
import android.net.Uri
|
import com.topjohnwu.magisk.legacy.flash.FlashViewModel
|
||||||
|
import com.topjohnwu.magisk.legacy.surequest.SuRequestViewModel
|
||||||
import com.topjohnwu.magisk.ui.MainViewModel
|
import com.topjohnwu.magisk.ui.MainViewModel
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashViewModel
|
|
||||||
import com.topjohnwu.magisk.ui.hide.HideViewModel
|
import com.topjohnwu.magisk.ui.hide.HideViewModel
|
||||||
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.install.InstallViewModel
|
||||||
import com.topjohnwu.magisk.ui.log.LogViewModel
|
import com.topjohnwu.magisk.ui.log.LogViewModel
|
||||||
import com.topjohnwu.magisk.ui.module.ModuleViewModel
|
import com.topjohnwu.magisk.ui.module.ModuleViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.request.RequestViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.safetynet.SafetynetViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.settings.SettingsViewModel
|
||||||
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
|
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
|
||||||
import com.topjohnwu.magisk.ui.surequest.SuRequestViewModel
|
import com.topjohnwu.magisk.ui.theme.ThemeViewModel
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|
||||||
val viewModelModules = module {
|
val viewModelModules = module {
|
||||||
viewModel { MainViewModel() }
|
viewModel { HideViewModel(get()) }
|
||||||
viewModel { HomeViewModel(get()) }
|
viewModel { HomeViewModel(get()) }
|
||||||
viewModel { SuperuserViewModel(get(), get(), get(), get()) }
|
viewModel { LogViewModel(get()) }
|
||||||
viewModel { HideViewModel(get(), get()) }
|
|
||||||
viewModel { ModuleViewModel(get(), get(), get()) }
|
viewModel { ModuleViewModel(get(), get(), get()) }
|
||||||
viewModel { LogViewModel(get(), get()) }
|
viewModel { RequestViewModel() }
|
||||||
viewModel { (action: String, file: Uri, additional: Uri) ->
|
viewModel { SafetynetViewModel(get()) }
|
||||||
FlashViewModel(action, file, additional, get())
|
viewModel { SettingsViewModel(get()) }
|
||||||
}
|
viewModel { SuperuserViewModel(get(), get(), get()) }
|
||||||
|
viewModel { ThemeViewModel() }
|
||||||
|
viewModel { InstallViewModel() }
|
||||||
|
viewModel { MainViewModel() }
|
||||||
|
|
||||||
|
// Legacy
|
||||||
|
viewModel { FlashViewModel(get()) }
|
||||||
viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) }
|
viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) }
|
||||||
}
|
}
|
||||||
|
@@ -52,9 +52,9 @@ fun <T> Observable<T>.subscribeK(
|
|||||||
|
|
||||||
fun <T> Single<T>.subscribeK(
|
fun <T> Single<T>.subscribeK(
|
||||||
onError: OnErrorListener = { it.printStackTrace() },
|
onError: OnErrorListener = { it.printStackTrace() },
|
||||||
onSuccess: OnSuccessListener<T> = {}
|
onNext: OnSuccessListener<T> = {}
|
||||||
) = applySchedulers()
|
) = applySchedulers()
|
||||||
.subscribe(onSuccess, onError)
|
.subscribe(onNext, onError)
|
||||||
|
|
||||||
fun <T> Maybe<T>.subscribeK(
|
fun <T> Maybe<T>.subscribeK(
|
||||||
onError: OnErrorListener = { it.printStackTrace() },
|
onError: OnErrorListener = { it.printStackTrace() },
|
||||||
@@ -198,5 +198,8 @@ fun <T> ObservableField<T>.toObservable(): Observable<T> {
|
|||||||
|
|
||||||
fun <T : Any> T.toSingle() = Single.just(this)
|
fun <T : Any> T.toSingle() = Single.just(this)
|
||||||
|
|
||||||
fun <T1, T2, R> zip(t1: Single<T1>, t2: Single<T2>, zipper: (T1, T2) -> R) =
|
inline fun <T1, T2, R> zip(
|
||||||
Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })
|
t1: Single<T1>,
|
||||||
|
t2: Single<T2>,
|
||||||
|
crossinline zipper: (T1, T2) -> R
|
||||||
|
) = Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })
|
@@ -1,5 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
@@ -22,17 +23,20 @@ import android.os.Build
|
|||||||
import android.os.Build.VERSION.SDK_INT
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
import androidx.annotation.ColorRes
|
import androidx.annotation.ColorRes
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.net.toFile
|
import androidx.core.net.toFile
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.topjohnwu.magisk.Const
|
import androidx.fragment.app.Fragment
|
||||||
import com.topjohnwu.magisk.FileProvider
|
import com.topjohnwu.magisk.FileProvider
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.utils.Utils
|
||||||
|
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||||
import com.topjohnwu.magisk.utils.DynamicClassLoader
|
import com.topjohnwu.magisk.utils.DynamicClassLoader
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
|
||||||
import com.topjohnwu.magisk.utils.currentLocale
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
@@ -283,7 +287,7 @@ fun Context.drawableCompat(@DrawableRes id: Int) = ContextCompat.getDrawable(thi
|
|||||||
* with respect to RTL layout direction
|
* with respect to RTL layout direction
|
||||||
*/
|
*/
|
||||||
fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
|
fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
|
if (SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
|
||||||
resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
||||||
) {
|
) {
|
||||||
return end to start
|
return end to start
|
||||||
@@ -294,10 +298,10 @@ fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
|
|||||||
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
|
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
inline fun <reified T> T.DynamicClassLoader(apk: File)
|
inline fun <reified T> T.DynamicClassLoader(apk: File) =
|
||||||
= DynamicClassLoader(apk, T::class.java.classLoader)
|
DynamicClassLoader(apk, T::class.java.classLoader)
|
||||||
|
|
||||||
fun Context.unwrap() : Context {
|
fun Context.unwrap(): Context {
|
||||||
var context = this
|
var context = this
|
||||||
while (true) {
|
while (true) {
|
||||||
if (context is ContextWrapper)
|
if (context is ContextWrapper)
|
||||||
@@ -309,3 +313,18 @@ fun Context.unwrap() : Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Uri.writeTo(file: File) = toFile().copyTo(file)
|
fun Uri.writeTo(file: File) = toFile().copyTo(file)
|
||||||
|
|
||||||
|
fun Context.hasPermissions(vararg permissions: String) = permissions.all {
|
||||||
|
ContextCompat.checkSelfPermission(this, it) == PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Activity.hideKeyboard() {
|
||||||
|
val view = currentFocus ?: return
|
||||||
|
getSystemService<InputMethodManager>()
|
||||||
|
?.hideSoftInputFromWindow(view.windowToken, 0)
|
||||||
|
view.clearFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Fragment.hideKeyboard() {
|
||||||
|
activity?.hideKeyboard()
|
||||||
|
}
|
||||||
|
@@ -1,8 +1,71 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import androidx.databinding.ObservableList
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
|
||||||
|
|
||||||
fun KObservableField<Boolean>.toggle() {
|
fun KObservableField<Boolean>.toggle() {
|
||||||
value = !value
|
value = !value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> ObservableList<T>.addOnListChangedCallback(
|
||||||
|
onChanged: ((sender: ObservableList<T>) -> Unit)? = null,
|
||||||
|
onItemRangeRemoved: ((sender: ObservableList<T>, positionStart: Int, itemCount: Int) -> Unit)? = null,
|
||||||
|
onItemRangeMoved: ((sender: ObservableList<T>, fromPosition: Int, toPosition: Int, itemCount: Int) -> Unit)? = null,
|
||||||
|
onItemRangeInserted: ((sender: ObservableList<T>, positionStart: Int, itemCount: Int) -> Unit)? = null,
|
||||||
|
onItemRangeChanged: ((sender: ObservableList<T>, positionStart: Int, itemCount: Int) -> Unit)? = null
|
||||||
|
) = addOnListChangedCallback(object : ObservableList.OnListChangedCallback<ObservableList<T>>() {
|
||||||
|
override fun onChanged(sender: ObservableList<T>?) {
|
||||||
|
onChanged?.invoke(sender ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeRemoved(
|
||||||
|
sender: ObservableList<T>?,
|
||||||
|
positionStart: Int,
|
||||||
|
itemCount: Int
|
||||||
|
) {
|
||||||
|
onItemRangeRemoved?.invoke(
|
||||||
|
sender ?: return,
|
||||||
|
positionStart,
|
||||||
|
itemCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeMoved(
|
||||||
|
sender: ObservableList<T>?,
|
||||||
|
fromPosition: Int,
|
||||||
|
toPosition: Int,
|
||||||
|
itemCount: Int
|
||||||
|
) {
|
||||||
|
onItemRangeMoved?.invoke(
|
||||||
|
sender ?: return,
|
||||||
|
fromPosition,
|
||||||
|
toPosition,
|
||||||
|
itemCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeInserted(
|
||||||
|
sender: ObservableList<T>?,
|
||||||
|
positionStart: Int,
|
||||||
|
itemCount: Int
|
||||||
|
) {
|
||||||
|
onItemRangeInserted?.invoke(
|
||||||
|
sender ?: return,
|
||||||
|
positionStart,
|
||||||
|
itemCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeChanged(
|
||||||
|
sender: ObservableList<T>?,
|
||||||
|
positionStart: Int,
|
||||||
|
itemCount: Int
|
||||||
|
) {
|
||||||
|
onItemRangeChanged?.invoke(
|
||||||
|
sender ?: return,
|
||||||
|
positionStart,
|
||||||
|
itemCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
@@ -1,11 +1,13 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
@@ -100,6 +102,9 @@ fun Locale.toLangTag(): String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun SimpleDateFormat.parseOrNull(date: String) =
|
||||||
|
runCatching { parse(date) }.onFailure { Timber.e(it) }.getOrNull()
|
||||||
|
|
||||||
// Reflection hacks
|
// Reflection hacks
|
||||||
|
|
||||||
private val loadClass = ClassLoader::class.java.getMethod("loadClass", String::class.java)
|
private val loadClass = ClassLoader::class.java.getMethod("loadClass", String::class.java)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.io.SuFileInputStream
|
import com.topjohnwu.superuser.io.SuFileInputStream
|
||||||
import com.topjohnwu.superuser.io.SuFileOutputStream
|
import com.topjohnwu.superuser.io.SuFileOutputStream
|
||||||
@@ -12,3 +12,5 @@ fun reboot(reason: String = if (Info.recovery) "recovery" else "") {
|
|||||||
|
|
||||||
fun File.suOutputStream() = SuFileOutputStream(this)
|
fun File.suOutputStream() = SuFileOutputStream(this)
|
||||||
fun File.suInputStream() = SuFileInputStream(this)
|
fun File.suInputStream() = SuFileInputStream(this)
|
||||||
|
|
||||||
|
val hasRoot get() = Shell.rootAccess()
|
||||||
|
@@ -1,15 +1,38 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.os.Build
|
||||||
|
|
||||||
val specialChars = arrayOf('!', '@', '#', '$', '%', '&', '?')
|
val specialChars = arrayOf('!', '@', '#', '$', '%', '&', '?')
|
||||||
|
val fullSpecialChars = arrayOf('!', '@', '#', '$', '%', '&', '?')
|
||||||
|
|
||||||
|
fun String.isCJK(): Boolean {
|
||||||
|
for (i in 0 until length)
|
||||||
|
if (isCJK(codePointAt(i)))
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isCJK(codepoint: Int): Boolean {
|
||||||
|
return if (Build.VERSION.SDK_INT < 19) false /* Pre 5.0 don't need to be pretty.. */
|
||||||
|
else Character.isIdeographic(codepoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.replaceRandomWithSpecial(passes: Int): String {
|
||||||
|
var string = this
|
||||||
|
repeat(passes) {
|
||||||
|
string = string.replaceRandomWithSpecial()
|
||||||
|
}
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
fun String.replaceRandomWithSpecial(): String {
|
fun String.replaceRandomWithSpecial(): String {
|
||||||
|
val sp = if (isCJK()) fullSpecialChars else specialChars
|
||||||
var random: Char
|
var random: Char
|
||||||
do {
|
do {
|
||||||
random = random()
|
random = random()
|
||||||
} while (random == '.')
|
} while (random == '.')
|
||||||
return replace(random, specialChars.random())
|
return replace(random, sp.random())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun StringBuilder.appendIf(condition: Boolean, builder: StringBuilder.() -> Unit) =
|
fun StringBuilder.appendIf(condition: Boolean, builder: StringBuilder.() -> Unit) =
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.currentLocale
|
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@@ -14,7 +14,22 @@ fun String.toTime(format: DateFormat) = try {
|
|||||||
-1L
|
-1L
|
||||||
}
|
}
|
||||||
|
|
||||||
val timeFormatFull by lazy { SimpleDateFormat("yyyy/MM/dd_HH:mm:ss", currentLocale) }
|
val timeFormatFull by lazy { SimpleDateFormat("yyyy/MM/dd_HH:mm:ss",
|
||||||
val timeFormatStandard by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", currentLocale) }
|
currentLocale
|
||||||
val timeFormatMedium by lazy { DateFormat.getDateInstance(DateFormat.MEDIUM, currentLocale) }
|
) }
|
||||||
val timeFormatTime by lazy { SimpleDateFormat("h:mm a", currentLocale) }
|
val timeFormatStandard by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'",
|
||||||
|
currentLocale
|
||||||
|
) }
|
||||||
|
val timeFormatMedium by lazy { DateFormat.getDateInstance(DateFormat.MEDIUM,
|
||||||
|
currentLocale
|
||||||
|
) }
|
||||||
|
val timeFormatTime by lazy { SimpleDateFormat("h:mm a",
|
||||||
|
currentLocale
|
||||||
|
) }
|
||||||
|
val timeDateFormat by lazy {
|
||||||
|
DateFormat.getDateTimeInstance(
|
||||||
|
DateFormat.DEFAULT,
|
||||||
|
DateFormat.DEFAULT,
|
||||||
|
currentLocale
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@@ -1,7 +1,11 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||||
|
import androidx.transition.AutoTransition
|
||||||
|
import androidx.transition.TransitionManager
|
||||||
|
|
||||||
fun View.setOnViewReadyListener(callback: () -> Unit) = addOnGlobalLayoutListener(true, callback)
|
fun View.setOnViewReadyListener(callback: () -> Unit) = addOnGlobalLayoutListener(true, callback)
|
||||||
|
|
||||||
@@ -12,3 +16,8 @@ fun View.addOnGlobalLayoutListener(oneShot: Boolean = false, callback: () -> Uni
|
|||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
fun ViewGroup.startAnimations() {
|
||||||
|
val transition = AutoTransition().setInterpolator(FastOutSlowInInterpolator()).setDuration(400)
|
||||||
|
TransitionManager.beginDelayedTransition(this, transition)
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.ui.flash
|
package com.topjohnwu.magisk.legacy.flash
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -6,31 +6,27 @@ import android.content.pm.ActivityInfo
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.topjohnwu.magisk.Const
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.base.BaseActivity
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.intent
|
||||||
|
import com.topjohnwu.magisk.core.view.Notifications
|
||||||
import com.topjohnwu.magisk.databinding.ActivityFlashBinding
|
import com.topjohnwu.magisk.databinding.ActivityFlashBinding
|
||||||
import com.topjohnwu.magisk.extensions.snackbar
|
import com.topjohnwu.magisk.extensions.snackbar
|
||||||
import com.topjohnwu.magisk.intent
|
|
||||||
import com.topjohnwu.magisk.model.events.BackPressEvent
|
|
||||||
import com.topjohnwu.magisk.model.events.PermissionEvent
|
import com.topjohnwu.magisk.model.events.PermissionEvent
|
||||||
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.ui.base.BaseUIActivity
|
||||||
|
import com.topjohnwu.magisk.ui.base.CompatNavigationDelegate
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
import org.koin.core.parameter.parametersOf
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>() {
|
open class FlashActivity : BaseUIActivity<FlashViewModel, ActivityFlashBinding>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.activity_flash
|
override val layoutRes: Int = R.layout.activity_flash
|
||||||
override val themeRes: Int = R.style.MagiskTheme_Flashing
|
override val viewModel: FlashViewModel by viewModel()
|
||||||
override val viewModel: FlashViewModel by viewModel {
|
|
||||||
val uri = intent.data ?: let { finish(); Uri.EMPTY }
|
override val navigation: CompatNavigationDelegate<BaseUIActivity<FlashViewModel, ActivityFlashBinding>>? =
|
||||||
val additionalUri = intent.getParcelableExtra(Const.Key.FLASH_DATA) ?: uri
|
null
|
||||||
val action = intent.getStringExtra(Const.Key.FLASH_ACTION) ?: let { finish();"" }
|
|
||||||
parametersOf(action, uri, additionalUri)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||||
@@ -38,6 +34,7 @@ open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>()
|
|||||||
val id = intent.getIntExtra(Const.Key.DISMISS_ID, -1)
|
val id = intent.getIntExtra(Const.Key.DISMISS_ID, -1)
|
||||||
if (id != -1)
|
if (id != -1)
|
||||||
Notifications.mgr.cancel(id)
|
Notifications.mgr.cancel(id)
|
||||||
|
viewModel.startFlashing(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
@@ -49,7 +46,6 @@ open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>()
|
|||||||
super.onEventDispatched(event)
|
super.onEventDispatched(event)
|
||||||
when (event) {
|
when (event) {
|
||||||
is SnackbarEvent -> snackbar(snackbarView, event.message(this), event.length, event.f)
|
is SnackbarEvent -> snackbar(snackbarView, event.message(this), event.length, event.f)
|
||||||
is BackPressEvent -> onBackPressed()
|
|
||||||
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
|
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
|
||||||
onSuccess { event.callback.onNext(true) }
|
onSuccess { event.callback.onNext(true) }
|
||||||
onFailure {
|
onFailure {
|
@@ -0,0 +1,117 @@
|
|||||||
|
package com.topjohnwu.magisk.legacy.flash
|
||||||
|
|
||||||
|
import android.Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Handler
|
||||||
|
import android.view.MenuItem
|
||||||
|
import androidx.core.os.postDelayed
|
||||||
|
import androidx.databinding.ObservableArrayList
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.tasks.FlashResultListener
|
||||||
|
import com.topjohnwu.magisk.core.tasks.Flashing
|
||||||
|
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||||
|
import com.topjohnwu.magisk.extensions.*
|
||||||
|
import com.topjohnwu.magisk.model.binding.BindingAdapter
|
||||||
|
import com.topjohnwu.magisk.model.entity.recycler.ConsoleItem
|
||||||
|
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||||
|
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.base.diffListOf
|
||||||
|
import com.topjohnwu.magisk.ui.base.itemBindingOf
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class FlashViewModel(
|
||||||
|
private val resources: Resources
|
||||||
|
) : BaseViewModel(), FlashResultListener {
|
||||||
|
|
||||||
|
val canShowReboot = Shell.rootAccess()
|
||||||
|
val showRestartTitle = KObservableField(false)
|
||||||
|
|
||||||
|
val behaviorText = KObservableField(resources.getString(R.string.flashing))
|
||||||
|
|
||||||
|
val adapter = BindingAdapter<ConsoleItem>()
|
||||||
|
val items = diffListOf<ConsoleItem>()
|
||||||
|
val itemBinding = itemBindingOf<ConsoleItem>()
|
||||||
|
|
||||||
|
private val outItems = ObservableArrayList<String>()
|
||||||
|
private val logItems = Collections.synchronizedList(mutableListOf<String>())
|
||||||
|
|
||||||
|
init {
|
||||||
|
outItems.sendUpdatesTo(items) { it.map { ConsoleItem(it) } }
|
||||||
|
outItems.copyNewInputInto(logItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startFlashing(intent: Intent) {
|
||||||
|
val installer = intent.data ?: return
|
||||||
|
val uri: Uri? = intent.getParcelableExtra(Const.Key.FLASH_DATA)
|
||||||
|
val action = intent.getStringExtra(Const.Key.FLASH_ACTION) ?: return
|
||||||
|
|
||||||
|
when (action) {
|
||||||
|
Const.Value.FLASH_ZIP -> Flashing
|
||||||
|
.Install(installer, outItems, logItems, this)
|
||||||
|
.exec()
|
||||||
|
Const.Value.UNINSTALL -> Flashing
|
||||||
|
.Uninstall(installer, outItems, logItems, this)
|
||||||
|
.exec()
|
||||||
|
Const.Value.FLASH_MAGISK -> MagiskInstaller
|
||||||
|
.Direct(installer, outItems, logItems, this)
|
||||||
|
.exec()
|
||||||
|
Const.Value.FLASH_INACTIVE_SLOT -> MagiskInstaller
|
||||||
|
.SecondSlot(installer, outItems, logItems, this)
|
||||||
|
.exec()
|
||||||
|
Const.Value.PATCH_FILE -> MagiskInstaller
|
||||||
|
.Patch(installer, uri ?: return, outItems, logItems, this)
|
||||||
|
.exec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResult(success: Boolean) {
|
||||||
|
state = if (success) State.LOADED else State.LOADING_FAILED
|
||||||
|
behaviorText.value = when {
|
||||||
|
success -> resources.getString(R.string.done)
|
||||||
|
else -> resources.getString(R.string.failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
Handler().postDelayed(500) {
|
||||||
|
showRestartTitle.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMenuItemClicked(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.action_save -> savePressed()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun savePressed() = withPermissions(READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE)
|
||||||
|
.map { now }
|
||||||
|
.map { it.toTime(timeFormatStandard) }
|
||||||
|
.map { Const.MAGISK_INSTALL_LOG_FILENAME.format(it) }
|
||||||
|
.map { File(Config.downloadDirectory, it) }
|
||||||
|
.map { file ->
|
||||||
|
file.bufferedWriter().use { writer ->
|
||||||
|
logItems.forEach {
|
||||||
|
writer.write(it)
|
||||||
|
writer.newLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.path
|
||||||
|
}
|
||||||
|
.subscribeK { SnackbarEvent(it).publish() }
|
||||||
|
.add()
|
||||||
|
|
||||||
|
fun restartPressed() = reboot()
|
||||||
|
|
||||||
|
fun backPressed() = back()
|
||||||
|
|
||||||
|
}
|
@@ -1,26 +1,31 @@
|
|||||||
package com.topjohnwu.magisk.ui.surequest
|
package com.topjohnwu.magisk.legacy.surequest
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.content.res.Resources
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
|
import android.view.WindowManager
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.base.BaseActivity
|
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||||
|
import com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST
|
||||||
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
|
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
|
||||||
import com.topjohnwu.magisk.model.events.DieEvent
|
import com.topjohnwu.magisk.model.events.DieEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
import com.topjohnwu.magisk.utils.SuHandler
|
import com.topjohnwu.magisk.ui.base.BaseUIActivity
|
||||||
import com.topjohnwu.magisk.utils.SuHandler.REQUEST
|
import com.topjohnwu.magisk.ui.base.CompatNavigationDelegate
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestBinding>() {
|
open class SuRequestActivity : BaseUIActivity<SuRequestViewModel, ActivityRequestBinding>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.activity_request
|
override val layoutRes: Int = R.layout.activity_request
|
||||||
override val themeRes: Int = R.style.MagiskTheme_SU
|
|
||||||
override val viewModel: SuRequestViewModel by viewModel()
|
override val viewModel: SuRequestViewModel by viewModel()
|
||||||
|
|
||||||
|
override val navigation: CompatNavigationDelegate<BaseUIActivity<SuRequestViewModel, ActivityRequestBinding>>? =
|
||||||
|
null
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
viewModel.denyPressed()
|
viewModel.denyPressed()
|
||||||
}
|
}
|
||||||
@@ -28,6 +33,8 @@ open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestB
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
|
supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
lockOrientation()
|
lockOrientation()
|
||||||
|
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE,
|
||||||
|
WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
fun showRequest() {
|
fun showRequest() {
|
||||||
@@ -36,7 +43,7 @@ open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestB
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun runHandler(action: String?) {
|
fun runHandler(action: String?) {
|
||||||
SuHandler(this, action, intent.extras)
|
SuCallbackHandler(this, action, intent.extras)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +61,12 @@ open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestB
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getTheme(): Resources.Theme {
|
||||||
|
val theme = super.getTheme()
|
||||||
|
theme.applyStyle(R.style.Foundation_Floating, true)
|
||||||
|
return theme
|
||||||
|
}
|
||||||
|
|
||||||
override fun onEventDispatched(event: ViewEvent) {
|
override fun onEventDispatched(event: ViewEvent) {
|
||||||
super.onEventDispatched(event)
|
super.onEventDispatched(event)
|
||||||
when (event) {
|
when (event) {
|
@@ -0,0 +1,128 @@
|
|||||||
|
package com.topjohnwu.magisk.legacy.surequest
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.CountDownTimer
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.ALLOW
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.DENY
|
||||||
|
import com.topjohnwu.magisk.core.su.SuRequestHandler
|
||||||
|
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||||
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
|
import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem
|
||||||
|
import com.topjohnwu.magisk.model.events.DieEvent
|
||||||
|
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
|
||||||
|
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||||
|
import java.util.concurrent.TimeUnit.SECONDS
|
||||||
|
|
||||||
|
class SuRequestViewModel(
|
||||||
|
private val pm: PackageManager,
|
||||||
|
private val policyDB: PolicyDao,
|
||||||
|
private val timeoutPrefs: SharedPreferences,
|
||||||
|
private val res: Resources
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
val icon = KObservableField<Drawable?>(null)
|
||||||
|
val title = KObservableField("")
|
||||||
|
val packageName = KObservableField("")
|
||||||
|
|
||||||
|
val denyText = KObservableField(res.getString(R.string.deny))
|
||||||
|
val warningText = KObservableField<CharSequence>(res.getString(R.string.su_warning))
|
||||||
|
|
||||||
|
val selectedItemPosition = KObservableField(0)
|
||||||
|
|
||||||
|
val grantEnabled = KObservableField(false)
|
||||||
|
|
||||||
|
private val items = DiffObservableList(ComparableRvItem.callback)
|
||||||
|
private val itemBinding = ItemBinding.of<ComparableRvItem<*>> { binding, _, item ->
|
||||||
|
item.bind(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
val adapter = BindingListViewAdapter<ComparableRvItem<*>>(1).apply {
|
||||||
|
itemBinding = this@SuRequestViewModel.itemBinding
|
||||||
|
setItems(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val handler = Handler()
|
||||||
|
|
||||||
|
fun grantPressed() {
|
||||||
|
handler.cancelTimer()
|
||||||
|
if (BiometricHelper.isEnabled) {
|
||||||
|
withView {
|
||||||
|
BiometricHelper.authenticate(this) {
|
||||||
|
handler.respond(ALLOW)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handler.respond(ALLOW)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun denyPressed() {
|
||||||
|
handler.respond(DENY)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun spinnerTouched(): Boolean {
|
||||||
|
handler.cancelTimer()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleRequest(intent: Intent): Boolean {
|
||||||
|
return handler.start(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class Handler : SuRequestHandler(pm, policyDB) {
|
||||||
|
|
||||||
|
fun respond(action: Int) {
|
||||||
|
val pos = selectedItemPosition.value
|
||||||
|
timeoutPrefs.edit().putInt(policy.packageName, pos).apply()
|
||||||
|
respond(action, Config.Value.TIMEOUT_LIST[pos])
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelTimer() {
|
||||||
|
timer.cancel()
|
||||||
|
denyText.value = res.getString(R.string.deny)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
res.getStringArray(R.array.allow_timeout)
|
||||||
|
.map { SpinnerRvItem(it) }
|
||||||
|
.let { items.update(it) }
|
||||||
|
|
||||||
|
icon.value = policy.applicationInfo.loadIcon(pm)
|
||||||
|
title.value = policy.appName
|
||||||
|
packageName.value = policy.packageName
|
||||||
|
selectedItemPosition.value = timeoutPrefs.getInt(policy.packageName, 0)
|
||||||
|
|
||||||
|
// Override timer
|
||||||
|
val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
|
||||||
|
timer = object : CountDownTimer(millis, 1000) {
|
||||||
|
override fun onTick(remains: Long) {
|
||||||
|
if (remains <= millis - 1000) {
|
||||||
|
grantEnabled.value = true
|
||||||
|
}
|
||||||
|
denyText.value = "${res.getString(R.string.deny)} (${(remains / 1000) + 1})"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinish() {
|
||||||
|
denyText.value = res.getString(R.string.deny)
|
||||||
|
respond(DENY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRespond() {
|
||||||
|
// Kill activity after response
|
||||||
|
DieEvent().publish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -6,7 +6,7 @@ import com.topjohnwu.magisk.databinding.ComparableRvItem
|
|||||||
import com.topjohnwu.magisk.model.entity.recycler.LenientRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.LenientRvItem
|
||||||
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
|
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
|
||||||
|
|
||||||
class BindingAdapter : BindingRecyclerViewAdapter<ComparableRvItem<*>>() {
|
class BindingAdapter <T : ComparableRvItem<*>> : BindingRecyclerViewAdapter<T>() {
|
||||||
|
|
||||||
private var recyclerView: RecyclerView? = null
|
private var recyclerView: RecyclerView? = null
|
||||||
|
|
||||||
@@ -15,12 +15,12 @@ class BindingAdapter : BindingRecyclerViewAdapter<ComparableRvItem<*>>() {
|
|||||||
variableId: Int,
|
variableId: Int,
|
||||||
layoutRes: Int,
|
layoutRes: Int,
|
||||||
position: Int,
|
position: Int,
|
||||||
item: ComparableRvItem<*>
|
item: T
|
||||||
) {
|
) {
|
||||||
super.onBindBinding(binding, variableId, layoutRes, position, item)
|
super.onBindBinding(binding, variableId, layoutRes, position, item)
|
||||||
|
|
||||||
when (item) {
|
when (item) {
|
||||||
is LenientRvItem -> {
|
is LenientRvItem<*> -> {
|
||||||
val recycler = recyclerView ?: return
|
val recycler = recyclerView ?: return
|
||||||
item.onBindingBound(binding)
|
item.onBindingBound(binding)
|
||||||
item.onBindingBound(binding, recycler)
|
item.onBindingBound(binding, recycler)
|
||||||
|
@@ -14,3 +14,14 @@ class HideAppInfo(
|
|||||||
val processes = info.packageInfo?.processes?.distinct() ?: listOf(info.packageName)
|
val processes = info.packageInfo?.processes?.distinct() ?: listOf(info.packageName)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class StatefulProcess(
|
||||||
|
val name: String,
|
||||||
|
val packageName: String,
|
||||||
|
val isHidden: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
class ProcessHideApp(
|
||||||
|
val info: HideAppInfo,
|
||||||
|
val processes: List<StatefulProcess>
|
||||||
|
)
|
@@ -3,10 +3,11 @@ package com.topjohnwu.magisk.model.entity
|
|||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.Ignore
|
import androidx.room.Ignore
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy
|
||||||
|
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.ALLOW
|
||||||
import com.topjohnwu.magisk.extensions.now
|
import com.topjohnwu.magisk.extensions.now
|
||||||
import com.topjohnwu.magisk.extensions.timeFormatTime
|
import com.topjohnwu.magisk.extensions.timeFormatTime
|
||||||
import com.topjohnwu.magisk.extensions.toTime
|
import com.topjohnwu.magisk.extensions.toTime
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.ALLOW
|
|
||||||
|
|
||||||
@Entity(tableName = "logs")
|
@Entity(tableName = "logs")
|
||||||
data class MagiskLog(
|
data class MagiskLog(
|
||||||
@@ -23,11 +24,6 @@ data class MagiskLog(
|
|||||||
@Ignore val timeString = time.toTime(timeFormatTime)
|
@Ignore val timeString = time.toTime(timeFormatTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class WrappedMagiskLog(
|
|
||||||
val time: Long,
|
|
||||||
val items: List<MagiskLog>
|
|
||||||
)
|
|
||||||
|
|
||||||
fun MagiskPolicy.toLog(
|
fun MagiskPolicy.toLog(
|
||||||
toUid: Int,
|
toUid: Int,
|
||||||
fromPid: Int,
|
fromPid: Int,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user