mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-14 22:37:28 +00:00
Compare commits
369 Commits
manager-v7
...
v19.4
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9329094a4e | ||
![]() |
b44f5122fd | ||
![]() |
17981730a4 | ||
![]() |
53de6da26c | ||
![]() |
3e30ccdeee | ||
![]() |
baaaf7d5de | ||
![]() |
45d8d139a9 | ||
![]() |
fe644e10d0 | ||
![]() |
f383d11d10 | ||
![]() |
ef1b928532 | ||
![]() |
6e46d394b1 | ||
![]() |
f109038d12 | ||
![]() |
e31e687602 | ||
![]() |
86bfb22d4c | ||
![]() |
3f057367e3 | ||
![]() |
3d7ed5820e | ||
![]() |
0118f2efa7 | ||
![]() |
15312e4709 | ||
![]() |
bf1568a73a | ||
![]() |
13a2520ea5 | ||
![]() |
f53238f206 | ||
![]() |
9375748d9b | ||
![]() |
201df54e79 | ||
![]() |
0b54fe477b | ||
![]() |
4119e6669e | ||
![]() |
d33e5226b3 | ||
![]() |
d73f39c706 | ||
![]() |
087b451e17 | ||
![]() |
86481c74ff | ||
![]() |
5b937fb1fa | ||
![]() |
ff828116bc | ||
![]() |
ee39616a8b | ||
![]() |
cdb53ca049 | ||
![]() |
8cf475f708 | ||
![]() |
0cb449e1d6 | ||
![]() |
e6adb7abca | ||
![]() |
cfad7dd317 | ||
![]() |
dd35224f92 | ||
![]() |
1283590eeb | ||
![]() |
dca3fe396f | ||
![]() |
8d87eae11b | ||
![]() |
fd7eaacae0 | ||
![]() |
fba33cbbe9 | ||
![]() |
950ffcd790 | ||
![]() |
c178299013 | ||
![]() |
5d17c1f588 | ||
![]() |
a75c00d94e | ||
![]() |
cd19517414 | ||
![]() |
155f39aab5 | ||
![]() |
4514d0b467 | ||
![]() |
6f4a938a31 | ||
![]() |
1303ea95dd | ||
![]() |
727fe1bd15 | ||
![]() |
64ebc977e9 | ||
![]() |
e89c50d934 | ||
![]() |
c859ddfb8f | ||
![]() |
a6126c5eda | ||
![]() |
85d9bd9106 | ||
![]() |
39e9622205 | ||
![]() |
021994c9f3 | ||
![]() |
2e7ce2a769 | ||
![]() |
84f0ff2fad | ||
![]() |
e6561e5f84 | ||
![]() |
5fa452aa74 | ||
![]() |
2225ccb146 | ||
![]() |
5aafc78847 | ||
![]() |
0d03833cff | ||
![]() |
a797d5d396 | ||
![]() |
f2494374f8 | ||
![]() |
48395ba860 | ||
![]() |
5ba5f5f94e | ||
![]() |
42ce6fd334 | ||
![]() |
f5c3ee3ae1 | ||
![]() |
3c7ece1605 | ||
![]() |
870efc49ea | ||
![]() |
085ede6d93 | ||
![]() |
4ef19d17da | ||
![]() |
223913c30a | ||
![]() |
010e4de4e1 | ||
![]() |
41134466ed | ||
![]() |
8f07747452 | ||
![]() |
eb5ce5be1e | ||
![]() |
71d855e836 | ||
![]() |
33b7ab593c | ||
![]() |
8706d834b4 | ||
![]() |
7cfab33ebb | ||
![]() |
1ababc8c7f | ||
![]() |
1f75e63c37 | ||
![]() |
cb3f9b9740 | ||
![]() |
9784353223 | ||
![]() |
7d93ca5c73 | ||
![]() |
ac20063e86 | ||
![]() |
debaec32af | ||
![]() |
0e9b71e7a9 | ||
![]() |
85f5ff3c14 | ||
![]() |
3d81f167ea | ||
![]() |
fb70a2e52d | ||
![]() |
460e85a1b5 | ||
![]() |
539b64bd57 | ||
![]() |
90e38a06a2 | ||
![]() |
09ab910630 | ||
![]() |
c15f80b33f | ||
![]() |
b2e6ba3c4a | ||
![]() |
b16f696b0e | ||
![]() |
9adfb382e8 | ||
![]() |
44368383f4 | ||
![]() |
d1ff7e0ffe | ||
![]() |
42e7db8d13 | ||
![]() |
0c17ea5755 | ||
![]() |
cdaff5b39c | ||
![]() |
2b1b970e78 | ||
![]() |
0aebc0a8e3 | ||
![]() |
c3a89f589e | ||
![]() |
971cd73fb3 | ||
![]() |
1947860d61 | ||
![]() |
55aaa421e8 | ||
![]() |
a8932706d8 | ||
![]() |
a97972aac0 | ||
![]() |
094c3d559a | ||
![]() |
6fb032b3c2 | ||
![]() |
8ca188f4d4 | ||
![]() |
746a1d8d59 | ||
![]() |
63c5e00d86 | ||
![]() |
9d2e5d6665 | ||
![]() |
f6045bf8b5 | ||
![]() |
e83f40d5c5 | ||
![]() |
e5118418b2 | ||
![]() |
7cd814d917 | ||
![]() |
78282c1a49 | ||
![]() |
fd4214ccf3 | ||
![]() |
0785945635 | ||
![]() |
967bdeae7b | ||
![]() |
452db51669 | ||
![]() |
5875ced367 | ||
![]() |
fbac6bcfd0 | ||
![]() |
0dcd3ece9d | ||
![]() |
224fff89e3 | ||
![]() |
22e73644f9 | ||
![]() |
6a0f6ab319 | ||
![]() |
88a394836f | ||
![]() |
f822c1c2e4 | ||
![]() |
1d16d980b3 | ||
![]() |
501b18f986 | ||
![]() |
21ed759e53 | ||
![]() |
8d50dfd93c | ||
![]() |
51e40dd98c | ||
![]() |
b2048379af | ||
![]() |
011539f6f1 | ||
![]() |
5457c3803f | ||
![]() |
b3d777bb6c | ||
![]() |
12e00c3054 | ||
![]() |
40b683111c | ||
![]() |
9542ca773f | ||
![]() |
8af832a496 | ||
![]() |
6836130fda | ||
![]() |
724893879f | ||
![]() |
736729f5ef | ||
![]() |
aa47966347 | ||
![]() |
d64d12afe8 | ||
![]() |
1f8df419c4 | ||
![]() |
7ba8202af5 | ||
![]() |
d7b691cf59 | ||
![]() |
7058d5e4cd | ||
![]() |
52fd508fea | ||
![]() |
41045b62dc | ||
![]() |
188ea2644a | ||
![]() |
4c8f357978 | ||
![]() |
4bb2fd6ba6 | ||
![]() |
33c9f74508 | ||
![]() |
f53fe67372 | ||
![]() |
51ff724691 | ||
![]() |
291bf93f9d | ||
![]() |
5fcd629f16 | ||
![]() |
ab90901793 | ||
![]() |
4f206fd918 | ||
![]() |
7233285437 | ||
![]() |
8e348a11c2 | ||
![]() |
085ea6d0a1 | ||
![]() |
aaf88b1895 | ||
![]() |
4f4a9412a3 | ||
![]() |
a92e039363 | ||
![]() |
33aa4ca4b7 | ||
![]() |
05658cafc7 | ||
![]() |
ff3710de66 | ||
![]() |
db8dd9f186 | ||
![]() |
e8b73ba6d1 | ||
![]() |
f1112fdf37 | ||
![]() |
a48c4f9e05 | ||
![]() |
19a521d2e9 | ||
![]() |
dd6e55ac31 | ||
![]() |
b1e63f0f14 | ||
![]() |
b0e49a4cc8 | ||
![]() |
1e94517a72 | ||
![]() |
98f60216ac | ||
![]() |
e29b712108 | ||
![]() |
a462435f2f | ||
![]() |
911b8273fe | ||
![]() |
09935e591a | ||
![]() |
4a212dba35 | ||
![]() |
aac9e85e04 | ||
![]() |
bb67a837d3 | ||
![]() |
6cde695194 | ||
![]() |
a1a1ac0bbb | ||
![]() |
9ec8bc2166 | ||
![]() |
28cd6a75e7 | ||
![]() |
4cc7aced15 | ||
![]() |
1058aeb04f | ||
![]() |
cfec0db947 | ||
![]() |
120bd6cd68 | ||
![]() |
9aef06d1b8 | ||
![]() |
e6e9dd751c | ||
![]() |
5dd677756f | ||
![]() |
b77c590910 | ||
![]() |
7e5f2822ae | ||
![]() |
12bbc7fd6b | ||
![]() |
bf9ac8252b | ||
![]() |
4a3f5dc619 | ||
![]() |
ca156befbd | ||
![]() |
4db41e2ac4 | ||
![]() |
982a43fce1 | ||
![]() |
dd76a74e1c | ||
![]() |
70cb52b2c7 | ||
![]() |
5c7f69acaa | ||
![]() |
f1d9015e5f | ||
![]() |
e8d900c58e | ||
![]() |
a6241ae912 | ||
![]() |
4a697ca2ec | ||
![]() |
58bec7f2c9 | ||
![]() |
213f84985c | ||
![]() |
074b1f8c61 | ||
![]() |
326eee8c83 | ||
![]() |
00bff4912e | ||
![]() |
0ce1720516 | ||
![]() |
ee407472cf | ||
![]() |
f341f3b2dd | ||
![]() |
8513946e09 | ||
![]() |
8ebd9c8927 | ||
![]() |
1d54c5144e | ||
![]() |
e40d4318fa | ||
![]() |
7756e10779 | ||
![]() |
3e58d502d0 | ||
![]() |
1c8846dc57 | ||
![]() |
2f320c7239 | ||
![]() |
e799918ab6 | ||
![]() |
86c4928e0f | ||
![]() |
0293eb5c51 | ||
![]() |
1ee75b6aa6 | ||
![]() |
4b30b224b5 | ||
![]() |
16b232d2a3 | ||
![]() |
3f3b1f5b1d | ||
![]() |
cec017b7bf | ||
![]() |
3123cc1059 | ||
![]() |
caa9df86bc | ||
![]() |
f417389a7a | ||
![]() |
662a5c8ea6 | ||
![]() |
7edfbfb764 | ||
![]() |
c1602d2554 | ||
![]() |
9f8d4e1022 | ||
![]() |
d1dfda405f | ||
![]() |
28efded624 | ||
![]() |
06c86ee267 | ||
![]() |
5892780871 | ||
![]() |
4fcdcd9a8a | ||
![]() |
80d834fb55 | ||
![]() |
4122ebe18f | ||
![]() |
7d87777bf8 | ||
![]() |
4a73d634e0 | ||
![]() |
373dc10a40 | ||
![]() |
ed43ec8ea2 | ||
![]() |
7918fc3528 | ||
![]() |
bf58205b0a | ||
![]() |
c0d1ce96d1 | ||
![]() |
b31d3802eb | ||
![]() |
be1228c3b4 | ||
![]() |
15c94c6b34 | ||
![]() |
202d23426a | ||
![]() |
fc26de48b2 | ||
![]() |
76c88913f9 | ||
![]() |
a3a1aed723 | ||
![]() |
81aa56f60f | ||
![]() |
73bb850209 | ||
![]() |
8dfec12330 | ||
![]() |
ae24397793 | ||
![]() |
3b0f888407 | ||
![]() |
845d1e02b0 | ||
![]() |
5d357bc41f | ||
![]() |
6a54672b13 | ||
![]() |
3d9a15df44 | ||
![]() |
449c7fda2f | ||
![]() |
8b7b05da68 | ||
![]() |
92400ebcab | ||
![]() |
23d3e56967 | ||
![]() |
6785dc4967 | ||
![]() |
dad20f6a2d | ||
![]() |
bb15671046 | ||
![]() |
21984fac8b | ||
![]() |
f392afe87f | ||
![]() |
6a243ec7bc | ||
![]() |
8cd3b603df | ||
![]() |
6e1aefe6d8 | ||
![]() |
1c90b6eca3 | ||
![]() |
c33cf9f878 | ||
![]() |
27cb40eec9 | ||
![]() |
c06081b75d | ||
![]() |
a7eec2f0a0 | ||
![]() |
4fd0fe3194 | ||
![]() |
cc74593ddd | ||
![]() |
fdb7c5dba1 | ||
![]() |
77470c7cfa | ||
![]() |
f0a734fdab | ||
![]() |
75405b2b25 | ||
![]() |
90ed4b3c49 | ||
![]() |
290a17a764 | ||
![]() |
aaabd836e4 | ||
![]() |
076e5cea3b | ||
![]() |
8515971ccf | ||
![]() |
d86fb033ea | ||
![]() |
99d7d8ddbc | ||
![]() |
df78fd2d41 | ||
![]() |
dabe6267b9 | ||
![]() |
0119ebddbe | ||
![]() |
3216ef9f47 | ||
![]() |
b79d1bcded | ||
![]() |
17e234f9d5 | ||
![]() |
ea1f75f80e | ||
![]() |
8c40db5730 | ||
![]() |
80855e89ec | ||
![]() |
0850401dc4 | ||
![]() |
337fda2023 | ||
![]() |
64f238191e | ||
![]() |
eb169cb133 | ||
![]() |
92789c3113 | ||
![]() |
c1c677e161 | ||
![]() |
2fe917ff82 | ||
![]() |
0e6c205732 | ||
![]() |
125ae0a173 | ||
![]() |
0245e13591 | ||
![]() |
d546733287 | ||
![]() |
c275326d59 | ||
![]() |
d4561507b8 | ||
![]() |
2624706c69 | ||
![]() |
d39d885ec2 | ||
![]() |
d83c744725 | ||
![]() |
843995cdb9 | ||
![]() |
9491ba77e0 | ||
![]() |
58a449d437 | ||
![]() |
7f55e0f05b | ||
![]() |
4f9e8d2e8a | ||
![]() |
21be2f46f3 | ||
![]() |
a6e7680212 | ||
![]() |
e79e744e08 | ||
![]() |
7abdac72a4 | ||
![]() |
90d85eaf7d | ||
![]() |
e65f9740fb | ||
![]() |
7538f89b56 | ||
![]() |
7c755a3991 | ||
![]() |
10e903c9fc | ||
![]() |
b018124226 | ||
![]() |
5d632d0d90 | ||
![]() |
4eecaea601 | ||
![]() |
63055818ec | ||
![]() |
0beb08b687 | ||
![]() |
bbc9e60a12 | ||
![]() |
6c975ecc4c | ||
![]() |
23e8a4ce4b | ||
![]() |
50134a2f9b | ||
![]() |
628b37c4fa | ||
![]() |
1b4ae70a43 | ||
![]() |
065051a360 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -11,7 +11,7 @@
|
|||||||
*.bat text eol=crlf
|
*.bat text eol=crlf
|
||||||
|
|
||||||
# Denote all files that are truly binary and should not be modified.
|
# Denote all files that are truly binary and should not be modified.
|
||||||
chromeos/** binary
|
tools/** binary
|
||||||
*.jar binary
|
*.jar binary
|
||||||
*.exe binary
|
*.exe binary
|
||||||
*.apk binary
|
*.apk binary
|
||||||
|
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -19,3 +19,9 @@
|
|||||||
[submodule "nanopb"]
|
[submodule "nanopb"]
|
||||||
path = native/jni/external/nanopb
|
path = native/jni/external/nanopb
|
||||||
url = https://github.com/nanopb/nanopb.git
|
url = https://github.com/nanopb/nanopb.git
|
||||||
|
[submodule "mincrypt"]
|
||||||
|
path = native/jni/external/mincrypt
|
||||||
|
url = https://github.com/topjohnwu/mincrypt.git
|
||||||
|
[submodule "termux-elf-cleaner"]
|
||||||
|
path = tools/termux-elf-cleaner
|
||||||
|
url = https://github.com/termux/termux-elf-cleaner.git
|
||||||
|
@@ -10,7 +10,7 @@ Furthermore, Magisk provides a **Systemless Interface** to alter the system (or
|
|||||||
|
|
||||||
## 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 is already fixed upstream. Follow the instructions in the [Canary Channel XDA Thread](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337), and report a bug either by opening an issue on GitHub or directly in the thread.
|
**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.
|
||||||
|
|
||||||
## Building Environment Requirements
|
## Building Environment Requirements
|
||||||
|
|
||||||
|
@@ -17,18 +17,16 @@ android {
|
|||||||
applicationId 'com.topjohnwu.magisk'
|
applicationId 'com.topjohnwu.magisk'
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
versionName configProps['appVersion']
|
versionName props['appVersion']
|
||||||
versionCode configProps['appVersionCode'] as Integer
|
versionCode props['appVersionCode'] as Integer
|
||||||
javaCompileOptions {
|
|
||||||
annotationProcessorOptions {
|
|
||||||
argument('butterknife.debuggable', 'false')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
minifyEnabled true
|
||||||
|
shrinkResources true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
|
||||||
|
'proguard-rules.pro', 'proguard-kotlin.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,15 +39,22 @@ android {
|
|||||||
exclude '/META-INF/*.kotlin_module'
|
exclude '/META-INF/*.kotlin_module'
|
||||||
exclude '/META-INF/rxkotlin.properties'
|
exclude '/META-INF/rxkotlin.properties'
|
||||||
exclude '/androidsupportmultidexversion.txt'
|
exclude '/androidsupportmultidexversion.txt'
|
||||||
exclude '/org/**'
|
exclude '/org/bouncycastle/**'
|
||||||
exclude '/kotlin/**'
|
exclude '/kotlin/**'
|
||||||
exclude '/kotlinx/**'
|
exclude '/kotlinx/**'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
androidExtensions {
|
||||||
|
experimental = true
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
implementation project(':net')
|
|
||||||
implementation project(':shared')
|
implementation project(':shared')
|
||||||
implementation project(':signing')
|
implementation project(':signing')
|
||||||
|
|
||||||
@@ -57,27 +62,58 @@ dependencies {
|
|||||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||||
implementation 'com.github.skoumalcz:teanity:0.3.3'
|
implementation 'com.github.skoumalcz:teanity:0.3.3'
|
||||||
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'
|
||||||
|
|
||||||
def markwonVersion = '3.0.1'
|
def vMarkwon = '3.1.0'
|
||||||
implementation "ru.noties.markwon:core:${markwonVersion}"
|
implementation "ru.noties.markwon:core:${vMarkwon}"
|
||||||
implementation "ru.noties.markwon:html:${markwonVersion}"
|
implementation "ru.noties.markwon:html:${vMarkwon}"
|
||||||
implementation "ru.noties.markwon:image-svg:${markwonVersion}"
|
implementation "ru.noties.markwon:image-svg:${vMarkwon}"
|
||||||
|
|
||||||
def libsuVersion = '2.5.0'
|
def vLibsu = '2.5.1'
|
||||||
implementation "com.github.topjohnwu.libsu:core:${libsuVersion}"
|
implementation "com.github.topjohnwu.libsu:core:${vLibsu}"
|
||||||
implementation "com.github.topjohnwu.libsu:io:${libsuVersion}"
|
implementation "com.github.topjohnwu.libsu:io:${vLibsu}"
|
||||||
|
|
||||||
def koin = "2.0.0-rc-2"
|
def vKoin = "2.0.1"
|
||||||
implementation "org.koin:koin-core:${koin}"
|
implementation "org.koin:koin-core:${vKoin}"
|
||||||
implementation "org.koin:koin-android:${koin}"
|
implementation "org.koin:koin-android:${vKoin}"
|
||||||
implementation "org.koin:koin-androidx-viewmodel:${koin}"
|
implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
|
||||||
|
|
||||||
|
def vRetrofit = '2.6.1'
|
||||||
|
implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
|
||||||
|
implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
|
||||||
|
implementation "com.squareup.retrofit2:converter-scalars:${vRetrofit}"
|
||||||
|
implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
|
||||||
|
|
||||||
|
def vOkHttp = '3.12.2'
|
||||||
|
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
|
||||||
|
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
|
||||||
|
|
||||||
|
def vMoshi = "1.8.0"
|
||||||
|
implementation "com.squareup.moshi:moshi:${vMoshi}"
|
||||||
|
|
||||||
|
def vKotshi = "2.0.1"
|
||||||
|
implementation "se.ansman.kotshi:api:${vKotshi}"
|
||||||
|
kapt "se.ansman.kotshi:compiler:${vKotshi}"
|
||||||
|
|
||||||
|
modules {
|
||||||
|
module('androidx.room:room-runtime') {
|
||||||
|
replacedBy('com.github.topjohnwu:room-runtime')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def vRoom = "2.1.0"
|
||||||
|
implementation "com.github.topjohnwu:room-runtime:${vRoom}"
|
||||||
|
kapt "androidx.room:room-compiler:${vRoom}"
|
||||||
|
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlinVer}"
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVer}"
|
||||||
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.preference:preference:1.0.0'
|
implementation 'androidx.browser:browser:1.0.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha05'
|
implementation 'androidx.preference:preference:1.1.0'
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.1.0-beta04'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'com.google.android.material:material:1.1.0-alpha06'
|
implementation 'androidx.work:work-runtime:2.2.0'
|
||||||
implementation 'androidx.work:work-runtime:2.0.1'
|
implementation 'androidx.transition:transition:1.2.0-rc01'
|
||||||
implementation 'androidx.transition:transition:1.2.0-alpha01'
|
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
|
implementation 'com.google.android.material:material:1.1.0-alpha10'
|
||||||
}
|
}
|
||||||
|
20
app/proguard-kotlin.pro
Normal file
20
app/proguard-kotlin.pro
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
## So every class is case insensitive to avoid some bizare problems
|
||||||
|
-dontusemixedcaseclassnames
|
||||||
|
|
||||||
|
## If reflection issues come up uncomment this, that should temporarily fix it
|
||||||
|
#-keep class kotlin.** { *; }
|
||||||
|
#-keep class kotlin.Metadata { *; }
|
||||||
|
#-keepclassmembers class kotlin.Metadata {
|
||||||
|
# public <methods>;
|
||||||
|
#}
|
||||||
|
|
||||||
|
## Never warn about Kotlin, it should work as-is
|
||||||
|
-dontwarn kotlin.**
|
||||||
|
|
||||||
|
## Removes runtime null checks - doesn't really matter if it crashes on kotlin or java NPE
|
||||||
|
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
||||||
|
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
|
||||||
|
}
|
||||||
|
|
||||||
|
## Useless option for dex
|
||||||
|
-dontpreverify
|
10
app/proguard-rules.pro
vendored
10
app/proguard-rules.pro
vendored
@@ -17,9 +17,9 @@
|
|||||||
#}
|
#}
|
||||||
|
|
||||||
# Snet
|
# Snet
|
||||||
-keepclassmembers class com.topjohnwu.magisk.utils.ISafetyNetHelper { *; }
|
-keepclassmembers class com.topjohnwu.magisk.utils.SafetyNetHelper { *; }
|
||||||
-keep,allowobfuscation interface com.topjohnwu.magisk.utils.ISafetyNetHelper$Callback
|
-keep,allowobfuscation interface com.topjohnwu.magisk.utils.SafetyNetHelper$Callback
|
||||||
-keepclassmembers class * implements com.topjohnwu.magisk.utils.ISafetyNetHelper$Callback {
|
-keepclassmembers class * implements com.topjohnwu.magisk.utils.SafetyNetHelper$Callback {
|
||||||
void onResponse(int);
|
void onResponse(int);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,9 +35,7 @@
|
|||||||
-keepclassmembers class com.topjohnwu.signing.BootSigner { *; }
|
-keepclassmembers class com.topjohnwu.signing.BootSigner { *; }
|
||||||
|
|
||||||
# Strip logging
|
# Strip logging
|
||||||
-assumenosideeffects class com.topjohnwu.magisk.utils.Logger {
|
-assumenosideeffects class timber.log.Timber.Tree { *; }
|
||||||
public *** debug(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
# Excessive obfuscation
|
# Excessive obfuscation
|
||||||
-repackageclasses 'a'
|
-repackageclasses 'a'
|
||||||
|
@@ -3,14 +3,15 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.topjohnwu.magisk">
|
package="com.topjohnwu.magisk">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
|
||||||
android:name="a.e"
|
android:name="a.e"
|
||||||
|
android:allowBackup="true"
|
||||||
android:theme="@style/MagiskTheme"
|
android:theme="@style/MagiskTheme"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
|
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
|
||||||
@@ -41,9 +42,9 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="a.m"
|
android:name="a.m"
|
||||||
android:exported="false"
|
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="false"
|
||||||
android:theme="@style/MagiskTheme.SU" />
|
android:theme="@style/MagiskTheme.SU" />
|
||||||
|
|
||||||
<!-- Receiver -->
|
<!-- Receiver -->
|
||||||
@@ -65,7 +66,8 @@
|
|||||||
|
|
||||||
<!-- Service -->
|
<!-- Service -->
|
||||||
|
|
||||||
<service android:name="a.j" />
|
<service android:name="a.j"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<!-- Hardcode GMS version -->
|
<!-- Hardcode GMS version -->
|
||||||
<meta-data
|
<meta-data
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package a;
|
package a;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.model.download.DownloadModuleService;
|
import com.topjohnwu.magisk.model.download.DownloadService;
|
||||||
|
|
||||||
public class j extends DownloadModuleService {
|
public class j extends DownloadService {
|
||||||
/* stub */
|
/* stub */
|
||||||
}
|
}
|
||||||
|
@@ -2,14 +2,14 @@ package a;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.model.worker.DelegateWorker;
|
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.work.Worker;
|
import androidx.work.Worker;
|
||||||
import androidx.work.WorkerParameters;
|
import androidx.work.WorkerParameters;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.model.worker.DelegateWorker;
|
||||||
|
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
|
||||||
public abstract class w<T extends DelegateWorker> extends Worker {
|
public abstract class w<T extends DelegateWorker> extends Worker {
|
||||||
|
|
||||||
/* Wrapper class to workaround Proguard -keep class * extends Worker */
|
/* Wrapper class to workaround Proguard -keep class * extends Worker */
|
||||||
@@ -22,7 +22,7 @@ public abstract class w<T extends DelegateWorker> extends Worker {
|
|||||||
try {
|
try {
|
||||||
base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
|
base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
|
||||||
.getActualTypeArguments()[0]).newInstance();
|
.getActualTypeArguments()[0]).newInstance();
|
||||||
base.setActualWorker(this);
|
base.attachWorker(this);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,43 +1,42 @@
|
|||||||
package com.topjohnwu.magisk
|
package com.topjohnwu.magisk
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.AsyncTask
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.multidex.MultiDex
|
import androidx.multidex.MultiDex
|
||||||
import com.topjohnwu.magisk.data.database.MagiskDB
|
import androidx.room.Room
|
||||||
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper
|
import androidx.work.impl.WorkDatabase
|
||||||
|
import androidx.work.impl.WorkDatabase_Impl
|
||||||
|
import com.topjohnwu.magisk.data.database.RepoDatabase
|
||||||
|
import com.topjohnwu.magisk.data.database.RepoDatabase_Impl
|
||||||
|
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.net.Networking
|
||||||
import com.topjohnwu.magisk.utils.LocaleManager
|
import com.topjohnwu.magisk.utils.LocaleManager
|
||||||
import com.topjohnwu.magisk.utils.RootUtils
|
import com.topjohnwu.magisk.utils.RootUtils
|
||||||
import com.topjohnwu.magisk.utils.inject
|
|
||||||
import com.topjohnwu.net.Networking
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import org.koin.android.ext.android.inject
|
|
||||||
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 java.util.concurrent.ThreadPoolExecutor
|
|
||||||
|
|
||||||
open class App : Application(), Application.ActivityLifecycleCallbacks {
|
open class App : Application() {
|
||||||
|
|
||||||
lateinit var protectedContext: Context
|
init {
|
||||||
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||||
@Deprecated("Use dependency injection")
|
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX)
|
||||||
val prefs: SharedPreferences by inject()
|
Shell.Config.verboseLogging(BuildConfig.DEBUG)
|
||||||
@Deprecated("Use dependency injection")
|
Shell.Config.addInitializers(RootUtils::class.java)
|
||||||
val DB: MagiskDB by inject()
|
Shell.Config.setTimeout(2)
|
||||||
@Deprecated("Use dependency injection")
|
Room.setFactory {
|
||||||
val repoDB: RepoDatabaseHelper by inject()
|
when (it) {
|
||||||
|
WorkDatabase::class.java -> WorkDatabase_Impl()
|
||||||
@Volatile
|
RepoDatabase::class.java -> RepoDatabase_Impl()
|
||||||
private var foreground: Activity? = null
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
override fun attachBaseContext(base: Context) {
|
||||||
super.attachBaseContext(base)
|
super.attachBaseContext(base)
|
||||||
@@ -50,17 +49,7 @@ open class App : Application(), Application.ActivityLifecycleCallbacks {
|
|||||||
modules(koinModules)
|
modules(koinModules)
|
||||||
}
|
}
|
||||||
|
|
||||||
protectedContext = baseContext
|
registerActivityLifecycleCallbacks(get<ActivityTracker>())
|
||||||
self = this
|
|
||||||
deContext = base
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 24) {
|
|
||||||
protectedContext = base.createDeviceProtectedStorageContext()
|
|
||||||
deContext = protectedContext
|
|
||||||
deContext.moveSharedPreferencesFrom(base, base.defaultPrefsName)
|
|
||||||
}
|
|
||||||
|
|
||||||
registerActivityLifecycleCallbacks(this)
|
|
||||||
|
|
||||||
Networking.init(base)
|
Networking.init(base)
|
||||||
LocaleManager.setLocale(this)
|
LocaleManager.setLocale(this)
|
||||||
@@ -70,61 +59,4 @@ open class App : Application(), Application.ActivityLifecycleCallbacks {
|
|||||||
super.onConfigurationChanged(newConfig)
|
super.onConfigurationChanged(newConfig)
|
||||||
LocaleManager.setLocale(this)
|
LocaleManager.setLocale(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
//region ActivityLifecycleCallbacks
|
|
||||||
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
|
|
||||||
|
|
||||||
override fun onActivityStarted(activity: Activity) {}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun onActivityResumed(activity: Activity) {
|
|
||||||
foreground = activity
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun onActivityPaused(activity: Activity) {
|
|
||||||
foreground = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityStopped(activity: Activity) {}
|
|
||||||
|
|
||||||
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
|
|
||||||
|
|
||||||
override fun onActivityDestroyed(activity: Activity) {}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
private val Context.defaultPrefsName get() = "${packageName}_preferences"
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
@Deprecated("Use dependency injection")
|
|
||||||
@JvmStatic
|
|
||||||
lateinit var self: App
|
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
@Deprecated("Use dependency injection; replace with protectedContext")
|
|
||||||
@JvmStatic
|
|
||||||
lateinit var deContext: Context
|
|
||||||
|
|
||||||
@Deprecated("Use Rx or similar")
|
|
||||||
@JvmField
|
|
||||||
var THREAD_POOL: ThreadPoolExecutor
|
|
||||||
|
|
||||||
init {
|
|
||||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
|
||||||
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX)
|
|
||||||
Shell.Config.verboseLogging(BuildConfig.DEBUG)
|
|
||||||
Shell.Config.addInitializers(RootUtils::class.java)
|
|
||||||
Shell.Config.setTimeout(2)
|
|
||||||
THREAD_POOL = AsyncTask.THREAD_POOL_EXECUTOR as ThreadPoolExecutor
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("")
|
|
||||||
@JvmStatic
|
|
||||||
fun foreground(): Activity? {
|
|
||||||
val app: App by inject()
|
|
||||||
return app.foreground
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,31 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.model.download.DownloadModuleService;
|
|
||||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver;
|
|
||||||
import com.topjohnwu.magisk.model.update.UpdateCheckService;
|
|
||||||
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 java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class ClassMap {
|
|
||||||
private static Map<Class, Class> classMap = new HashMap<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
classMap.put(App.class, a.e.class);
|
|
||||||
classMap.put(MainActivity.class, a.b.class);
|
|
||||||
classMap.put(SplashActivity.class, a.c.class);
|
|
||||||
classMap.put(FlashActivity.class, a.f.class);
|
|
||||||
classMap.put(UpdateCheckService.class, a.g.class);
|
|
||||||
classMap.put(GeneralReceiver.class, a.h.class);
|
|
||||||
classMap.put(DownloadModuleService.class, a.j.class);
|
|
||||||
classMap.put(SuRequestActivity.class, a.m.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> Class<T> get(Class c) {
|
|
||||||
return classMap.get(c);
|
|
||||||
}
|
|
||||||
}
|
|
26
app/src/main/java/com/topjohnwu/magisk/ClassMap.kt
Normal file
26
app/src/main/java/com/topjohnwu/magisk/ClassMap.kt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package com.topjohnwu.magisk
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.model.download.DownloadService
|
||||||
|
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
||||||
|
import com.topjohnwu.magisk.model.update.UpdateCheckService
|
||||||
|
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
|
||||||
|
|
||||||
|
object ClassMap {
|
||||||
|
private val map = mapOf(
|
||||||
|
App::class.java to a.e::class.java,
|
||||||
|
MainActivity::class.java to a.b::class.java,
|
||||||
|
SplashActivity::class.java to a.c::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,
|
||||||
|
DownloadService::class.java to a.j::class.java,
|
||||||
|
SuRequestActivity::class.java to a.m::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
operator fun <T : Class<*>>get(c: Class<*>): T {
|
||||||
|
return map.getOrElse(c) { throw IllegalArgumentException() } as T
|
||||||
|
}
|
||||||
|
}
|
@@ -1,396 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.util.Xml;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
import com.topjohnwu.superuser.Shell;
|
|
||||||
import com.topjohnwu.superuser.ShellUtils;
|
|
||||||
import com.topjohnwu.superuser.io.SuFile;
|
|
||||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import androidx.collection.ArrayMap;
|
|
||||||
|
|
||||||
public class Config {
|
|
||||||
|
|
||||||
// Current status
|
|
||||||
public static String magiskVersionString;
|
|
||||||
public static int magiskVersionCode = -1;
|
|
||||||
private static boolean magiskHide;
|
|
||||||
|
|
||||||
// Update Info
|
|
||||||
public static String remoteMagiskVersionString;
|
|
||||||
public static int remoteMagiskVersionCode = -1;
|
|
||||||
public static String magiskLink;
|
|
||||||
public static String magiskNoteLink;
|
|
||||||
public static String magiskMD5;
|
|
||||||
public static String remoteManagerVersionString;
|
|
||||||
public static int remoteManagerVersionCode = -1;
|
|
||||||
public static String managerLink;
|
|
||||||
public static String managerNoteLink;
|
|
||||||
public static String uninstallerLink;
|
|
||||||
|
|
||||||
// Install flags
|
|
||||||
public static boolean keepVerity = false;
|
|
||||||
public static boolean keepEnc = false;
|
|
||||||
public static boolean recovery = false;
|
|
||||||
|
|
||||||
public static int suLogTimeout = 14;
|
|
||||||
|
|
||||||
public static class Key {
|
|
||||||
// su configs
|
|
||||||
public static final String ROOT_ACCESS = "root_access";
|
|
||||||
public static final String SU_MULTIUSER_MODE = "multiuser_mode";
|
|
||||||
public static final String SU_MNT_NS = "mnt_ns";
|
|
||||||
public static final String SU_MANAGER = "requester";
|
|
||||||
public static final String SU_REQUEST_TIMEOUT = "su_request_timeout";
|
|
||||||
public static final String SU_AUTO_RESPONSE = "su_auto_response";
|
|
||||||
public static final String SU_NOTIFICATION = "su_notification";
|
|
||||||
public static final String SU_REAUTH = "su_reauth";
|
|
||||||
public static final String SU_FINGERPRINT = "su_fingerprint";
|
|
||||||
|
|
||||||
// prefs
|
|
||||||
public static final String CHECK_UPDATES = "check_update";
|
|
||||||
public static final String UPDATE_CHANNEL = "update_channel";
|
|
||||||
public static final String CUSTOM_CHANNEL = "custom_channel";
|
|
||||||
public static final String LOCALE = "locale";
|
|
||||||
public static final String DARK_THEME = "dark_theme";
|
|
||||||
public static final String ETAG_KEY = "ETag";
|
|
||||||
public static final String REPO_ORDER = "repo_order";
|
|
||||||
public static final String SHOW_SYSTEM_APP = "show_system";
|
|
||||||
|
|
||||||
// system state
|
|
||||||
public static final String UPDATE_SERVICE_VER = "update_service_version";
|
|
||||||
public static final String MAGISKHIDE = "magiskhide";
|
|
||||||
public static final String COREONLY = "disable";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Value {
|
|
||||||
public static final int DEFAULT_CHANNEL = -1;
|
|
||||||
public static final int STABLE_CHANNEL = 0;
|
|
||||||
public static final int BETA_CHANNEL = 1;
|
|
||||||
public static final int CUSTOM_CHANNEL = 2;
|
|
||||||
public static final int CANARY_CHANNEL = 3;
|
|
||||||
public static final int CANARY_DEBUG_CHANNEL = 4;
|
|
||||||
public static final int ROOT_ACCESS_DISABLED = 0;
|
|
||||||
public static final int ROOT_ACCESS_APPS_ONLY = 1;
|
|
||||||
public static final int ROOT_ACCESS_ADB_ONLY = 2;
|
|
||||||
public static final int ROOT_ACCESS_APPS_AND_ADB = 3;
|
|
||||||
public static final int MULTIUSER_MODE_OWNER_ONLY = 0;
|
|
||||||
public static final int MULTIUSER_MODE_OWNER_MANAGED = 1;
|
|
||||||
public static final int MULTIUSER_MODE_USER = 2;
|
|
||||||
public static final int NAMESPACE_MODE_GLOBAL = 0;
|
|
||||||
public static final int NAMESPACE_MODE_REQUESTER = 1;
|
|
||||||
public static final int NAMESPACE_MODE_ISOLATE = 2;
|
|
||||||
public static final int NO_NOTIFICATION = 0;
|
|
||||||
public static final int NOTIFICATION_TOAST = 1;
|
|
||||||
public static final int SU_PROMPT = 0;
|
|
||||||
public static final int SU_AUTO_DENY = 1;
|
|
||||||
public static final int SU_AUTO_ALLOW = 2;
|
|
||||||
public static final int[] TIMEOUT_LIST = {0, -1, 10, 20, 30, 60};
|
|
||||||
public static final int ORDER_NAME = 0;
|
|
||||||
public static final int ORDER_DATE = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void loadMagiskInfo() {
|
|
||||||
try {
|
|
||||||
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
|
|
||||||
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
|
|
||||||
magiskHide = Shell.su("magiskhide --status").exec().isSuccess();
|
|
||||||
} catch (NumberFormatException ignored) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void export() {
|
|
||||||
// Flush prefs to disk
|
|
||||||
App app = App.self;
|
|
||||||
app.getPrefs().edit().commit();
|
|
||||||
File xml = new File(App.deContext.getFilesDir().getParent() + "/shared_prefs",
|
|
||||||
app.getPackageName() + "_preferences.xml");
|
|
||||||
Shell.su(Utils.fmt("cat %s > /data/adb/%s", xml, Const.MANAGER_CONFIGS)).exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void initialize() {
|
|
||||||
SharedPreferences pref = App.self.getPrefs();
|
|
||||||
SharedPreferences.Editor editor = pref.edit();
|
|
||||||
File config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS);
|
|
||||||
if (config.exists()) {
|
|
||||||
try {
|
|
||||||
SuFileInputStream is = new SuFileInputStream(config);
|
|
||||||
XmlPullParser parser = Xml.newPullParser();
|
|
||||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
|
|
||||||
parser.setInput(is, "UTF-8");
|
|
||||||
parser.nextTag();
|
|
||||||
parser.require(XmlPullParser.START_TAG, null, "map");
|
|
||||||
while (parser.next() != XmlPullParser.END_TAG) {
|
|
||||||
if (parser.getEventType() != XmlPullParser.START_TAG)
|
|
||||||
continue;
|
|
||||||
String key = parser.getAttributeValue(null, "name");
|
|
||||||
String value = parser.getAttributeValue(null, "value");
|
|
||||||
switch (parser.getName()) {
|
|
||||||
case "string":
|
|
||||||
parser.require(XmlPullParser.START_TAG, null, "string");
|
|
||||||
editor.putString(key, parser.nextText());
|
|
||||||
parser.require(XmlPullParser.END_TAG, null, "string");
|
|
||||||
break;
|
|
||||||
case "boolean":
|
|
||||||
parser.require(XmlPullParser.START_TAG, null, "boolean");
|
|
||||||
editor.putBoolean(key, Boolean.parseBoolean(value));
|
|
||||||
parser.nextTag();
|
|
||||||
parser.require(XmlPullParser.END_TAG, null, "boolean");
|
|
||||||
break;
|
|
||||||
case "int":
|
|
||||||
parser.require(XmlPullParser.START_TAG, null, "int");
|
|
||||||
editor.putInt(key, Integer.parseInt(value));
|
|
||||||
parser.nextTag();
|
|
||||||
parser.require(XmlPullParser.END_TAG, null, "int");
|
|
||||||
break;
|
|
||||||
case "long":
|
|
||||||
parser.require(XmlPullParser.START_TAG, null, "long");
|
|
||||||
editor.putLong(key, Long.parseLong(value));
|
|
||||||
parser.nextTag();
|
|
||||||
parser.require(XmlPullParser.END_TAG, null, "long");
|
|
||||||
break;
|
|
||||||
case "float":
|
|
||||||
parser.require(XmlPullParser.START_TAG, null, "int");
|
|
||||||
editor.putFloat(key, Float.parseFloat(value));
|
|
||||||
parser.nextTag();
|
|
||||||
parser.require(XmlPullParser.END_TAG, null, "int");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
parser.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException | XmlPullParserException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
editor.remove(Key.ETAG_KEY);
|
|
||||||
editor.apply();
|
|
||||||
editor = pref.edit();
|
|
||||||
config.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set defaults if not set
|
|
||||||
setDefs(pref, editor);
|
|
||||||
|
|
||||||
// These settings are from actual device state
|
|
||||||
editor.putBoolean(Key.MAGISKHIDE, magiskHide)
|
|
||||||
.putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
|
|
||||||
.putInt(Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
|
|
||||||
.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final int PREF_INT = 0;
|
|
||||||
private static final int PREF_STR_INT = 1;
|
|
||||||
private static final int PREF_BOOL = 2;
|
|
||||||
private static final int PREF_STR = 3;
|
|
||||||
private static final int DB_INT = 4;
|
|
||||||
private static final int DB_BOOL = 5;
|
|
||||||
private static final int DB_STR = 6;
|
|
||||||
|
|
||||||
private static int getConfigType(String key) {
|
|
||||||
switch (key) {
|
|
||||||
case Key.REPO_ORDER:
|
|
||||||
return PREF_INT;
|
|
||||||
|
|
||||||
case Key.SU_REQUEST_TIMEOUT:
|
|
||||||
case Key.SU_AUTO_RESPONSE:
|
|
||||||
case Key.SU_NOTIFICATION:
|
|
||||||
case Key.UPDATE_CHANNEL:
|
|
||||||
return PREF_STR_INT;
|
|
||||||
|
|
||||||
case Key.DARK_THEME:
|
|
||||||
case Key.SU_REAUTH:
|
|
||||||
case Key.CHECK_UPDATES:
|
|
||||||
case Key.MAGISKHIDE:
|
|
||||||
case Key.COREONLY:
|
|
||||||
case Key.SHOW_SYSTEM_APP:
|
|
||||||
return PREF_BOOL;
|
|
||||||
|
|
||||||
case Key.CUSTOM_CHANNEL:
|
|
||||||
case Key.LOCALE:
|
|
||||||
case Key.ETAG_KEY:
|
|
||||||
return PREF_STR;
|
|
||||||
|
|
||||||
case Key.ROOT_ACCESS:
|
|
||||||
case Key.SU_MNT_NS:
|
|
||||||
case Key.SU_MULTIUSER_MODE:
|
|
||||||
return DB_INT;
|
|
||||||
|
|
||||||
case Key.SU_FINGERPRINT:
|
|
||||||
return DB_BOOL;
|
|
||||||
|
|
||||||
case Key.SU_MANAGER:
|
|
||||||
return DB_STR;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <T> T get(String key) {
|
|
||||||
App app = App.self;
|
|
||||||
switch (getConfigType(key)) {
|
|
||||||
case PREF_INT:
|
|
||||||
return (T) (Integer) app.getPrefs().getInt(key, getDef(key));
|
|
||||||
case PREF_STR_INT:
|
|
||||||
return (T) (Integer) Utils.getPrefsInt(app.getPrefs(), key, getDef(key));
|
|
||||||
case PREF_BOOL:
|
|
||||||
return (T) (Boolean) app.getPrefs().getBoolean(key, getDef(key));
|
|
||||||
case PREF_STR:
|
|
||||||
return (T) app.getPrefs().getString(key, getDef(key));
|
|
||||||
case DB_INT:
|
|
||||||
return (T) (Integer) app.getDB().getSettings(key, getDef(key));
|
|
||||||
case DB_BOOL:
|
|
||||||
return (T) (Boolean) (app.getDB().getSettings(key, getDef(key) ? 1 : 0) != 0);
|
|
||||||
case DB_STR:
|
|
||||||
return (T) app.getDB().getStrings(key, getDef(key));
|
|
||||||
}
|
|
||||||
/* Will never get here (IllegalArgumentException in getConfigType) */
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void set(String key, Object val) {
|
|
||||||
App app = App.self;
|
|
||||||
switch (getConfigType(key)) {
|
|
||||||
case PREF_INT:
|
|
||||||
app.getPrefs().edit().putInt(key, (int) val).apply();
|
|
||||||
break;
|
|
||||||
case PREF_STR_INT:
|
|
||||||
app.getPrefs().edit().putString(key, String.valueOf(val)).apply();
|
|
||||||
break;
|
|
||||||
case PREF_BOOL:
|
|
||||||
app.getPrefs().edit().putBoolean(key, (boolean) val).apply();
|
|
||||||
break;
|
|
||||||
case PREF_STR:
|
|
||||||
app.getPrefs().edit().putString(key, (String) val).apply();
|
|
||||||
break;
|
|
||||||
case DB_INT:
|
|
||||||
app.getDB().setSettings(key, (int) val);
|
|
||||||
break;
|
|
||||||
case DB_BOOL:
|
|
||||||
app.getDB().setSettings(key, (boolean) val ? 1 : 0);
|
|
||||||
break;
|
|
||||||
case DB_STR:
|
|
||||||
app.getDB().setStrings(key, (String) val);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void remove(String key) {
|
|
||||||
App app = App.self;
|
|
||||||
switch (getConfigType(key)) {
|
|
||||||
case PREF_INT:
|
|
||||||
case PREF_STR_INT:
|
|
||||||
case PREF_BOOL:
|
|
||||||
case PREF_STR:
|
|
||||||
app.getPrefs().edit().remove(key).apply();
|
|
||||||
break;
|
|
||||||
case DB_BOOL:
|
|
||||||
case DB_INT:
|
|
||||||
app.getDB().rmSettings(key);
|
|
||||||
break;
|
|
||||||
case DB_STR:
|
|
||||||
app.getDB().setStrings(key, null);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ArrayMap<String, Object> defs = new ArrayMap<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
/* Set default configurations */
|
|
||||||
|
|
||||||
// prefs int
|
|
||||||
defs.put(Key.REPO_ORDER, Value.ORDER_DATE);
|
|
||||||
|
|
||||||
// prefs string int
|
|
||||||
defs.put(Key.SU_REQUEST_TIMEOUT, 10);
|
|
||||||
defs.put(Key.SU_AUTO_RESPONSE, Value.SU_PROMPT);
|
|
||||||
defs.put(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST);
|
|
||||||
defs.put(Key.UPDATE_CHANNEL, Utils.isCanary() ?
|
|
||||||
Value.CANARY_DEBUG_CHANNEL : Value.DEFAULT_CHANNEL);
|
|
||||||
|
|
||||||
// prefs bool
|
|
||||||
defs.put(Key.CHECK_UPDATES, true);
|
|
||||||
defs.put(Key.DARK_THEME, true);
|
|
||||||
//defs.put(Key.SU_REAUTH, false);
|
|
||||||
//defs.put(Key.SHOW_SYSTEM_APP, false);
|
|
||||||
|
|
||||||
// prefs string
|
|
||||||
defs.put(Key.CUSTOM_CHANNEL, "");
|
|
||||||
defs.put(Key.LOCALE, "");
|
|
||||||
//defs.put(Key.ETAG_KEY, null);
|
|
||||||
|
|
||||||
// db int
|
|
||||||
defs.put(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB);
|
|
||||||
defs.put(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER);
|
|
||||||
defs.put(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY);
|
|
||||||
|
|
||||||
// db bool
|
|
||||||
//defs.put(Key.SU_FINGERPRINT, false);
|
|
||||||
|
|
||||||
// db strings
|
|
||||||
//defs.put(Key.SU_MANAGER, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> T getDef(String key) {
|
|
||||||
Object val = defs.get(key);
|
|
||||||
switch (getConfigType(key)) {
|
|
||||||
case PREF_INT:
|
|
||||||
case DB_INT:
|
|
||||||
case PREF_STR_INT:
|
|
||||||
return val != null ? (T) val : (T) (Integer) 0;
|
|
||||||
case DB_BOOL:
|
|
||||||
case PREF_BOOL:
|
|
||||||
return val != null ? (T) val : (T) (Boolean) false;
|
|
||||||
case DB_STR:
|
|
||||||
case PREF_STR:
|
|
||||||
return (T) val;
|
|
||||||
}
|
|
||||||
/* Will never get here (IllegalArgumentException in getConfigType) */
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void setDefs(SharedPreferences pref, SharedPreferences.Editor editor) {
|
|
||||||
App app = App.self;
|
|
||||||
for (String key : defs.keySet()) {
|
|
||||||
int type = getConfigType(key);
|
|
||||||
switch (type) {
|
|
||||||
case DB_INT:
|
|
||||||
editor.putString(key, String.valueOf(
|
|
||||||
app.getDB().getSettings(key, (Integer) defs.get(key))));
|
|
||||||
continue;
|
|
||||||
case DB_STR:
|
|
||||||
editor.putString(key, app.getDB().getStrings(key, (String) defs.get(key)));
|
|
||||||
continue;
|
|
||||||
case DB_BOOL:
|
|
||||||
int bs = app.getDB().getSettings(key, -1);
|
|
||||||
editor.putBoolean(key, bs < 0 ? (Boolean) defs.get(key) : bs != 0);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (pref.contains(key))
|
|
||||||
continue;
|
|
||||||
switch (type) {
|
|
||||||
case PREF_INT:
|
|
||||||
editor.putInt(key, (Integer) defs.get(key));
|
|
||||||
break;
|
|
||||||
case PREF_STR_INT:
|
|
||||||
editor.putString(key, String.valueOf(defs.get(key)));
|
|
||||||
break;
|
|
||||||
case PREF_STR:
|
|
||||||
editor.putString(key, (String) defs.get(key));
|
|
||||||
break;
|
|
||||||
case PREF_BOOL:
|
|
||||||
editor.putBoolean(key, (Boolean) defs.get(key));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
208
app/src/main/java/com/topjohnwu/magisk/Config.kt
Normal file
208
app/src/main/java/com/topjohnwu/magisk/Config.kt
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
package com.topjohnwu.magisk
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Environment
|
||||||
|
import android.util.Xml
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.topjohnwu.magisk.data.database.SettingsDao
|
||||||
|
import com.topjohnwu.magisk.data.database.StringDao
|
||||||
|
import com.topjohnwu.magisk.data.repository.DBConfig
|
||||||
|
import com.topjohnwu.magisk.di.Protected
|
||||||
|
import com.topjohnwu.magisk.extensions.get
|
||||||
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
|
import com.topjohnwu.magisk.extensions.packageName
|
||||||
|
import com.topjohnwu.magisk.model.preference.PreferenceModel
|
||||||
|
import com.topjohnwu.magisk.utils.FingerprintHelper
|
||||||
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
|
import com.topjohnwu.superuser.io.SuFileInputStream
|
||||||
|
import org.xmlpull.v1.XmlPullParser
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object Config : PreferenceModel, DBConfig {
|
||||||
|
|
||||||
|
override val stringDao: StringDao by inject()
|
||||||
|
override val settingsDao: SettingsDao by inject()
|
||||||
|
override val context: Context by inject(Protected)
|
||||||
|
|
||||||
|
object Key {
|
||||||
|
// db configs
|
||||||
|
const val ROOT_ACCESS = "root_access"
|
||||||
|
const val SU_MULTIUSER_MODE = "multiuser_mode"
|
||||||
|
const val SU_MNT_NS = "mnt_ns"
|
||||||
|
const val SU_MANAGER = "requester"
|
||||||
|
const val SU_FINGERPRINT = "su_fingerprint"
|
||||||
|
|
||||||
|
// prefs
|
||||||
|
const val SU_REQUEST_TIMEOUT = "su_request_timeout"
|
||||||
|
const val SU_AUTO_RESPONSE = "su_auto_response"
|
||||||
|
const val SU_NOTIFICATION = "su_notification"
|
||||||
|
const val SU_REAUTH = "su_reauth"
|
||||||
|
const val CHECK_UPDATES = "check_update"
|
||||||
|
const val UPDATE_CHANNEL = "update_channel"
|
||||||
|
const val CUSTOM_CHANNEL = "custom_channel"
|
||||||
|
const val LOCALE = "locale"
|
||||||
|
const val DARK_THEME = "dark_theme"
|
||||||
|
const val REPO_ORDER = "repo_order"
|
||||||
|
const val SHOW_SYSTEM_APP = "show_system"
|
||||||
|
const val DOWNLOAD_PATH = "download_path"
|
||||||
|
|
||||||
|
// system state
|
||||||
|
const val MAGISKHIDE = "magiskhide"
|
||||||
|
const val COREONLY = "disable"
|
||||||
|
}
|
||||||
|
|
||||||
|
object Value {
|
||||||
|
// Update channels
|
||||||
|
const val DEFAULT_CHANNEL = -1
|
||||||
|
const val STABLE_CHANNEL = 0
|
||||||
|
const val BETA_CHANNEL = 1
|
||||||
|
const val CUSTOM_CHANNEL = 2
|
||||||
|
const val CANARY_CHANNEL = 3
|
||||||
|
const val CANARY_DEBUG_CHANNEL = 4
|
||||||
|
|
||||||
|
// root access mode
|
||||||
|
const val ROOT_ACCESS_DISABLED = 0
|
||||||
|
const val ROOT_ACCESS_APPS_ONLY = 1
|
||||||
|
const val ROOT_ACCESS_ADB_ONLY = 2
|
||||||
|
const val ROOT_ACCESS_APPS_AND_ADB = 3
|
||||||
|
|
||||||
|
// su multiuser
|
||||||
|
const val MULTIUSER_MODE_OWNER_ONLY = 0
|
||||||
|
const val MULTIUSER_MODE_OWNER_MANAGED = 1
|
||||||
|
const val MULTIUSER_MODE_USER = 2
|
||||||
|
|
||||||
|
// su mnt ns
|
||||||
|
const val NAMESPACE_MODE_GLOBAL = 0
|
||||||
|
const val NAMESPACE_MODE_REQUESTER = 1
|
||||||
|
const val NAMESPACE_MODE_ISOLATE = 2
|
||||||
|
|
||||||
|
// su notification
|
||||||
|
const val NO_NOTIFICATION = 0
|
||||||
|
const val NOTIFICATION_TOAST = 1
|
||||||
|
|
||||||
|
// su auto response
|
||||||
|
const val SU_PROMPT = 0
|
||||||
|
const val SU_AUTO_DENY = 1
|
||||||
|
const val SU_AUTO_ALLOW = 2
|
||||||
|
|
||||||
|
// su timeout
|
||||||
|
val TIMEOUT_LIST = intArrayOf(0, -1, 10, 20, 30, 60)
|
||||||
|
|
||||||
|
// repo order
|
||||||
|
const val ORDER_NAME = 0
|
||||||
|
const val ORDER_DATE = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private val defaultChannel =
|
||||||
|
if (Utils.isCanary) Value.CANARY_DEBUG_CHANNEL
|
||||||
|
else Value.DEFAULT_CHANNEL
|
||||||
|
|
||||||
|
var downloadPath by preference(Key.DOWNLOAD_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
|
||||||
|
|
||||||
|
var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
|
||||||
|
var suAutoReponse by preferenceStrInt(Key.SU_AUTO_RESPONSE, Value.SU_PROMPT)
|
||||||
|
var suNotification by preferenceStrInt(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST)
|
||||||
|
var updateChannel by preferenceStrInt(Key.UPDATE_CHANNEL, defaultChannel)
|
||||||
|
|
||||||
|
var darkTheme by preference(Key.DARK_THEME, true)
|
||||||
|
var suReAuth by preference(Key.SU_REAUTH, false)
|
||||||
|
var checkUpdate by preference(Key.CHECK_UPDATES, true)
|
||||||
|
var magiskHide by preference(Key.MAGISKHIDE, true)
|
||||||
|
var coreOnly by preference(Key.COREONLY, false)
|
||||||
|
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
|
||||||
|
|
||||||
|
var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "")
|
||||||
|
var locale by preference(Key.LOCALE, "")
|
||||||
|
|
||||||
|
var rootMode by dbSettings(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB)
|
||||||
|
var suMntNamespaceMode by dbSettings(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER)
|
||||||
|
var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY)
|
||||||
|
var suFingerprint by dbSettings(Key.SU_FINGERPRINT, false)
|
||||||
|
var suManager by dbStrings(Key.SU_MANAGER, "", true)
|
||||||
|
|
||||||
|
// Always return a path in external storage where we can write
|
||||||
|
val downloadDirectory get() =
|
||||||
|
Utils.ensureDownloadPath(downloadPath) ?: get<Context>().getExternalFilesDir(null)!!
|
||||||
|
|
||||||
|
fun initialize() = prefs.edit {
|
||||||
|
parsePrefs(this)
|
||||||
|
|
||||||
|
if (!prefs.contains(Key.UPDATE_CHANNEL))
|
||||||
|
putString(Key.UPDATE_CHANNEL, defaultChannel.toString())
|
||||||
|
|
||||||
|
// Get actual state
|
||||||
|
putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
|
||||||
|
|
||||||
|
// Write database configs
|
||||||
|
putString(Key.ROOT_ACCESS, rootMode.toString())
|
||||||
|
putString(Key.SU_MNT_NS, suMntNamespaceMode.toString())
|
||||||
|
putString(Key.SU_MULTIUSER_MODE, suMultiuserMode.toString())
|
||||||
|
putBoolean(Key.SU_FINGERPRINT, FingerprintHelper.useFingerprint())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parsePrefs(editor: SharedPreferences.Editor) = editor.apply {
|
||||||
|
val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS)
|
||||||
|
if (config.exists()) runCatching {
|
||||||
|
val input = SuFileInputStream(config).buffered()
|
||||||
|
val parser = Xml.newPullParser()
|
||||||
|
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||||
|
parser.setInput(input, "UTF-8")
|
||||||
|
parser.nextTag()
|
||||||
|
parser.require(XmlPullParser.START_TAG, null, "map")
|
||||||
|
while (parser.next() != XmlPullParser.END_TAG) {
|
||||||
|
if (parser.eventType != XmlPullParser.START_TAG)
|
||||||
|
continue
|
||||||
|
val key: String = parser.getAttributeValue(null, "name")
|
||||||
|
fun value() = parser.getAttributeValue(null, "value")!!
|
||||||
|
when (parser.name) {
|
||||||
|
"string" -> {
|
||||||
|
parser.require(XmlPullParser.START_TAG, null, "string")
|
||||||
|
putString(key, parser.nextText())
|
||||||
|
parser.require(XmlPullParser.END_TAG, null, "string")
|
||||||
|
}
|
||||||
|
"boolean" -> {
|
||||||
|
parser.require(XmlPullParser.START_TAG, null, "boolean")
|
||||||
|
putBoolean(key, value().toBoolean())
|
||||||
|
parser.nextTag()
|
||||||
|
parser.require(XmlPullParser.END_TAG, null, "boolean")
|
||||||
|
}
|
||||||
|
"int" -> {
|
||||||
|
parser.require(XmlPullParser.START_TAG, null, "int")
|
||||||
|
putInt(key, value().toInt())
|
||||||
|
parser.nextTag()
|
||||||
|
parser.require(XmlPullParser.END_TAG, null, "int")
|
||||||
|
}
|
||||||
|
"long" -> {
|
||||||
|
parser.require(XmlPullParser.START_TAG, null, "long")
|
||||||
|
putLong(key, value().toLong())
|
||||||
|
parser.nextTag()
|
||||||
|
parser.require(XmlPullParser.END_TAG, null, "long")
|
||||||
|
}
|
||||||
|
"float" -> {
|
||||||
|
parser.require(XmlPullParser.START_TAG, null, "int")
|
||||||
|
putFloat(key, value().toFloat())
|
||||||
|
parser.nextTag()
|
||||||
|
parser.require(XmlPullParser.END_TAG, null, "int")
|
||||||
|
}
|
||||||
|
else -> parser.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun export() {
|
||||||
|
// Flush prefs to disk
|
||||||
|
prefs.edit().commit()
|
||||||
|
val xml = File(
|
||||||
|
"${get<Context>(Protected).filesDir.parent}/shared_prefs",
|
||||||
|
"${packageName}_preferences.xml"
|
||||||
|
)
|
||||||
|
Shell.su("cat $xml > /data/adb/${Const.MANAGER_CONFIGS}").exec()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,102 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.Process;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class Const {
|
|
||||||
|
|
||||||
public static final String DEBUG_TAG = "MagiskManager";
|
|
||||||
|
|
||||||
// APK content
|
|
||||||
public static final String ANDROID_MANIFEST = "AndroidManifest.xml";
|
|
||||||
|
|
||||||
public static final String SU_KEYSTORE_KEY = "su_key";
|
|
||||||
|
|
||||||
// Paths
|
|
||||||
public static final String MAGISK_PATH = "/sbin/.magisk/img";
|
|
||||||
public static final File EXTERNAL_PATH;
|
|
||||||
public static File MAGISK_DISABLE_FILE;
|
|
||||||
|
|
||||||
static {
|
|
||||||
MAGISK_DISABLE_FILE = new File("xxx");
|
|
||||||
EXTERNAL_PATH = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
|
||||||
EXTERNAL_PATH.mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final String TMP_FOLDER_PATH = "/dev/tmp";
|
|
||||||
public static final String MAGISK_LOG = "/cache/magisk.log";
|
|
||||||
public static final String MANAGER_CONFIGS = ".tmp.magisk.config";
|
|
||||||
|
|
||||||
// Versions
|
|
||||||
public static final int UPDATE_SERVICE_VER = 1;
|
|
||||||
public static final int SNET_EXT_VER = 12;
|
|
||||||
|
|
||||||
public static final int USER_ID = Process.myUid() / 100000;
|
|
||||||
|
|
||||||
// Generic
|
|
||||||
public static final String MAGISK_INSTALL_LOG_FILENAME = "magisk_install_log_%s.log";
|
|
||||||
|
|
||||||
public static final class MAGISK_VER {
|
|
||||||
public static final int MIN_SUPPORT = 18000;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ID {
|
|
||||||
public static final int FETCH_ZIP = 2;
|
|
||||||
public static final int SELECT_BOOT = 3;
|
|
||||||
|
|
||||||
// notifications
|
|
||||||
public static final int MAGISK_UPDATE_NOTIFICATION_ID = 4;
|
|
||||||
public static final int APK_UPDATE_NOTIFICATION_ID = 5;
|
|
||||||
public static final int DTBO_NOTIFICATION_ID = 7;
|
|
||||||
public static final int HIDE_MANAGER_NOTIFICATION_ID = 8;
|
|
||||||
public static final String UPDATE_NOTIFICATION_CHANNEL = "update";
|
|
||||||
public static final String PROGRESS_NOTIFICATION_CHANNEL = "progress";
|
|
||||||
public static final String CHECK_MAGISK_UPDATE_WORKER_ID = "magisk_update";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Url {
|
|
||||||
private static String getRaw(String where, String name) {
|
|
||||||
return String.format("https://raw.githubusercontent.com/topjohnwu/magisk_files/%s/%s", where, name);
|
|
||||||
}
|
|
||||||
public static final String STABLE_URL = getRaw("master", "stable.json");
|
|
||||||
public static final String BETA_URL = getRaw("master", "beta.json");
|
|
||||||
public static final String CANARY_URL = getRaw("master", "canary_builds/release.json");
|
|
||||||
public static final String CANARY_DEBUG_URL = getRaw("master", "canary_builds/canary.json");
|
|
||||||
public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed&page=%d";
|
|
||||||
public static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s";
|
|
||||||
public static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
|
|
||||||
public static final String MODULE_INSTALLER = "https://raw.githubusercontent.com/topjohnwu/Magisk/master/scripts/module_installer.sh";
|
|
||||||
public static final String PAYPAL_URL = "https://www.paypal.me/topjohnwu";
|
|
||||||
public static final String PATREON_URL = "https://www.patreon.com/topjohnwu";
|
|
||||||
public static final String TWITTER_URL = "https://twitter.com/topjohnwu";
|
|
||||||
public static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382";
|
|
||||||
public static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk";
|
|
||||||
public static final String SNET_URL = getRaw("b66b1a914978e5f4c4bbfd74a59f4ad371bac107", "snet.apk");
|
|
||||||
public static final String BOOTCTL_URL = getRaw("9c5dfc1b8245c0b5b524901ef0ff0f8335757b77", "bootctl");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Key {
|
|
||||||
// others
|
|
||||||
public static final String LINK_KEY = "Link";
|
|
||||||
public static final String IF_NONE_MATCH = "If-None-Match";
|
|
||||||
// intents
|
|
||||||
public static final String OPEN_SECTION = "section";
|
|
||||||
public static final String INTENT_SET_NAME = "filename";
|
|
||||||
public static final String INTENT_SET_LINK = "link";
|
|
||||||
public static final String FLASH_ACTION = "action";
|
|
||||||
public static final String BROADCAST_MANAGER_UPDATE = "manager_update";
|
|
||||||
public static final String BROADCAST_REBOOT = "reboot";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Value {
|
|
||||||
public static final String FLASH_ZIP = "flash";
|
|
||||||
public static final String PATCH_FILE = "patch";
|
|
||||||
public static final String FLASH_MAGISK = "magisk";
|
|
||||||
public static final String FLASH_INACTIVE_SLOT = "slot";
|
|
||||||
public static final String UNINSTALL = "uninstall";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
78
app/src/main/java/com/topjohnwu/magisk/Const.kt
Normal file
78
app/src/main/java/com/topjohnwu/magisk/Const.kt
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package com.topjohnwu.magisk
|
||||||
|
|
||||||
|
import android.os.Process
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object Const {
|
||||||
|
|
||||||
|
// Paths
|
||||||
|
const val MAGISK_PATH = "/sbin/.magisk/img"
|
||||||
|
var MAGISK_DISABLE_FILE = File("xxx")
|
||||||
|
const val TMP_FOLDER_PATH = "/dev/tmp"
|
||||||
|
const val MAGISK_LOG = "/cache/magisk.log"
|
||||||
|
|
||||||
|
// Versions
|
||||||
|
const val SNET_EXT_VER = 13
|
||||||
|
const val SNET_REVISION = "5adbc435ce93ded953c30ebe587edfd50b5503bc"
|
||||||
|
const val BOOTCTL_REVISION = "9c5dfc1b8245c0b5b524901ef0ff0f8335757b77"
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
const val ANDROID_MANIFEST = "AndroidManifest.xml"
|
||||||
|
const val MAGISK_INSTALL_LOG_FILENAME = "magisk_install_log_%s.log"
|
||||||
|
const val MANAGER_CONFIGS = ".tmp.magisk.config"
|
||||||
|
val USER_ID = Process.myUid() / 100000
|
||||||
|
|
||||||
|
object MagiskVersion {
|
||||||
|
const val MIN_SUPPORT = 18000
|
||||||
|
}
|
||||||
|
|
||||||
|
object ID {
|
||||||
|
const val FETCH_ZIP = 2
|
||||||
|
const val SELECT_BOOT = 3
|
||||||
|
|
||||||
|
// notifications
|
||||||
|
const val MAGISK_UPDATE_NOTIFICATION_ID = 4
|
||||||
|
const val APK_UPDATE_NOTIFICATION_ID = 5
|
||||||
|
const val DTBO_NOTIFICATION_ID = 7
|
||||||
|
const val HIDE_MANAGER_NOTIFICATION_ID = 8
|
||||||
|
const val UPDATE_NOTIFICATION_CHANNEL = "update"
|
||||||
|
const val PROGRESS_NOTIFICATION_CHANNEL = "progress"
|
||||||
|
const val CHECK_MAGISK_UPDATE_WORKER_ID = "magisk_update"
|
||||||
|
}
|
||||||
|
|
||||||
|
object Url {
|
||||||
|
const val ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip"
|
||||||
|
const val PAYPAL_URL = "https://www.paypal.me/topjohnwu"
|
||||||
|
const val PATREON_URL = "https://www.patreon.com/topjohnwu"
|
||||||
|
const val TWITTER_URL = "https://twitter.com/topjohnwu"
|
||||||
|
const val XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382"
|
||||||
|
const val SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk"
|
||||||
|
|
||||||
|
const val GITHUB_RAW_URL = "https://raw.githubusercontent.com/"
|
||||||
|
const val GITHUB_API_URL = "https://api.github.com/users/Magisk-Modules-Repo/"
|
||||||
|
}
|
||||||
|
|
||||||
|
object Key {
|
||||||
|
// others
|
||||||
|
const val LINK_KEY = "Link"
|
||||||
|
const val IF_NONE_MATCH = "If-None-Match"
|
||||||
|
const val ETAG_KEY = "ETag"
|
||||||
|
// intents
|
||||||
|
const val OPEN_SECTION = "section"
|
||||||
|
const val INTENT_SET_APP = "app_json"
|
||||||
|
const val FLASH_ACTION = "action"
|
||||||
|
const val FLASH_DATA = "additional_data"
|
||||||
|
const val DISMISS_ID = "dismiss_id"
|
||||||
|
const val BROADCAST_MANAGER_UPDATE = "manager_update"
|
||||||
|
const val BROADCAST_REBOOT = "reboot"
|
||||||
|
}
|
||||||
|
|
||||||
|
object Value {
|
||||||
|
const val FLASH_ZIP = "flash"
|
||||||
|
const val PATCH_FILE = "patch"
|
||||||
|
const val FLASH_MAGISK = "magisk"
|
||||||
|
const val FLASH_INACTIVE_SLOT = "slot"
|
||||||
|
const val UNINSTALL = "uninstall"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
app/src/main/java/com/topjohnwu/magisk/Info.kt
Normal file
26
app/src/main/java/com/topjohnwu/magisk/Info.kt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package com.topjohnwu.magisk
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.model.entity.UpdateInfo
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
|
|
||||||
|
object Info {
|
||||||
|
|
||||||
|
var magiskVersionCode = -1
|
||||||
|
|
||||||
|
var magiskVersionString = ""
|
||||||
|
|
||||||
|
var remote = UpdateInfo()
|
||||||
|
|
||||||
|
var keepVerity = false
|
||||||
|
var keepEnc = false
|
||||||
|
var recovery = false
|
||||||
|
|
||||||
|
fun loadMagiskInfo() {
|
||||||
|
runCatching {
|
||||||
|
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":".toRegex())[0]
|
||||||
|
magiskVersionCode = ShellUtils.fastCmd("magisk -V").toInt()
|
||||||
|
Config.magiskHide = Shell.su("magiskhide --status").exec().isSuccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,33 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.data.database.base.*
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||||
|
import com.topjohnwu.magisk.model.entity.toLog
|
||||||
|
import com.topjohnwu.magisk.model.entity.toMap
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class LogDao : BaseDao() {
|
||||||
|
|
||||||
|
override val table = DatabaseDefinition.Table.LOG
|
||||||
|
|
||||||
|
fun deleteOutdated(
|
||||||
|
suTimeout: Long = TimeUnit.DAYS.toMillis(14)
|
||||||
|
) = query<Delete> {
|
||||||
|
condition {
|
||||||
|
lessThan("time", suTimeout.toString())
|
||||||
|
}
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun deleteAll() = query<Delete> {}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetchAll() = query<Select> {
|
||||||
|
orderBy("time", Order.DESC)
|
||||||
|
}.flattenAsFlowable { it }
|
||||||
|
.map { it.toLog() }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
fun put(log: MagiskLog) = query<Insert> {
|
||||||
|
values(log.toMap())
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
}
|
@@ -1,190 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.data.database;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Config;
|
|
||||||
import com.topjohnwu.magisk.Const;
|
|
||||||
import com.topjohnwu.magisk.model.entity.Policy;
|
|
||||||
import com.topjohnwu.magisk.model.entity.SuLogEntry;
|
|
||||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
import com.topjohnwu.superuser.Shell;
|
|
||||||
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class MagiskDB {
|
|
||||||
|
|
||||||
private static final String POLICY_TABLE = "policies";
|
|
||||||
private static final String LOG_TABLE = "logs";
|
|
||||||
private static final String SETTINGS_TABLE = "settings";
|
|
||||||
private static final String STRINGS_TABLE = "strings";
|
|
||||||
|
|
||||||
private PackageManager pm;
|
|
||||||
|
|
||||||
public MagiskDB(Context context) {
|
|
||||||
pm = context.getPackageManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deletePolicy(Policy policy) {
|
|
||||||
deletePolicy(policy.uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> rawSQL(String fmt, Object... args) {
|
|
||||||
return Shell.su("magisk --sqlite '" + Utils.fmt(fmt, args) + "'").exec().getOut();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ContentValues> SQL(String fmt, Object... args) {
|
|
||||||
List<ContentValues> list = new ArrayList<>();
|
|
||||||
for (String raw : rawSQL(fmt, args)) {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
String[] cols = raw.split("\\|");
|
|
||||||
for (String col : cols) {
|
|
||||||
String[] pair = col.split("=", 2);
|
|
||||||
if (pair.length != 2)
|
|
||||||
continue;
|
|
||||||
values.put(pair[0], pair[1]);
|
|
||||||
}
|
|
||||||
list.add(values);
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String toSQL(ContentValues values) {
|
|
||||||
StringBuilder keys = new StringBuilder(), vals = new StringBuilder();
|
|
||||||
keys.append('(');
|
|
||||||
vals.append("VALUES(");
|
|
||||||
boolean first = true;
|
|
||||||
for (Map.Entry<String, Object> entry : values.valueSet()) {
|
|
||||||
if (!first) {
|
|
||||||
keys.append(',');
|
|
||||||
vals.append(',');
|
|
||||||
} else {
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
keys.append(entry.getKey());
|
|
||||||
vals.append('"');
|
|
||||||
vals.append(entry.getValue());
|
|
||||||
vals.append('"');
|
|
||||||
}
|
|
||||||
keys.append(')');
|
|
||||||
vals.append(')');
|
|
||||||
keys.append(vals);
|
|
||||||
return keys.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearOutdated() {
|
|
||||||
rawSQL(
|
|
||||||
"DELETE FROM %s WHERE until > 0 AND until < %d;" +
|
|
||||||
"DELETE FROM %s WHERE time < %d",
|
|
||||||
POLICY_TABLE, System.currentTimeMillis() / 1000,
|
|
||||||
LOG_TABLE, System.currentTimeMillis() - Config.suLogTimeout * 86400000
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deletePolicy(String pkg) {
|
|
||||||
rawSQL("DELETE FROM %s WHERE package_name=\"%s\"", POLICY_TABLE, pkg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deletePolicy(int uid) {
|
|
||||||
rawSQL("DELETE FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Policy getPolicy(int uid) {
|
|
||||||
List<ContentValues> res =
|
|
||||||
SQL("SELECT * FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
|
||||||
if (!res.isEmpty()) {
|
|
||||||
try {
|
|
||||||
return new Policy(res.get(0), pm);
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
deletePolicy(uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updatePolicy(Policy policy) {
|
|
||||||
rawSQL("REPLACE INTO %s %s", POLICY_TABLE, toSQL(policy.getContentValues()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Policy> getPolicyList() {
|
|
||||||
List<Policy> list = new ArrayList<>();
|
|
||||||
for (ContentValues values : SQL("SELECT * FROM %s WHERE uid/100000=%d", POLICY_TABLE, Const.USER_ID)) {
|
|
||||||
try {
|
|
||||||
list.add(new Policy(values, pm));
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
deletePolicy(values.getAsInteger("uid"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Collections.sort(list);
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<List<SuLogEntry>> getLogs() {
|
|
||||||
List<List<SuLogEntry>> ret = new ArrayList<>();
|
|
||||||
List<SuLogEntry> list = null;
|
|
||||||
String dateString = null, newString;
|
|
||||||
for (ContentValues values : SQL("SELECT * FROM %s ORDER BY time DESC", LOG_TABLE)) {
|
|
||||||
Date date = new Date(values.getAsLong("time"));
|
|
||||||
newString = DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date);
|
|
||||||
if (!TextUtils.equals(dateString, newString)) {
|
|
||||||
dateString = newString;
|
|
||||||
list = new ArrayList<>();
|
|
||||||
ret.add(list);
|
|
||||||
}
|
|
||||||
list.add(new SuLogEntry(values));
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addLog(SuLogEntry log) {
|
|
||||||
rawSQL("INSERT INTO %s %s", LOG_TABLE, toSQL(log.getContentValues()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearLogs() {
|
|
||||||
rawSQL("DELETE FROM %s", LOG_TABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void rmSettings(String key) {
|
|
||||||
rawSQL("DELETE FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSettings(String key, int value) {
|
|
||||||
ContentValues data = new ContentValues();
|
|
||||||
data.put("key", key);
|
|
||||||
data.put("value", value);
|
|
||||||
rawSQL("REPLACE INTO %s %s", SETTINGS_TABLE, toSQL(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSettings(String key, int defaultValue) {
|
|
||||||
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
|
|
||||||
if (res.isEmpty())
|
|
||||||
return defaultValue;
|
|
||||||
return res.get(0).getAsInteger("value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrings(String key, String value) {
|
|
||||||
if (value == null) {
|
|
||||||
rawSQL("DELETE FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ContentValues data = new ContentValues();
|
|
||||||
data.put("key", key);
|
|
||||||
data.put("value", value);
|
|
||||||
rawSQL("REPLACE INTO %s %s", STRINGS_TABLE, toSQL(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStrings(String key, String defaultValue) {
|
|
||||||
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
|
||||||
if (res.isEmpty())
|
|
||||||
return defaultValue;
|
|
||||||
return res.get(0).getAsString("value");
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,75 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import com.topjohnwu.magisk.Const
|
||||||
|
import com.topjohnwu.magisk.data.database.base.*
|
||||||
|
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 java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyDao(
|
||||||
|
private val context: Context
|
||||||
|
) : BaseDao() {
|
||||||
|
|
||||||
|
override val table: String = DatabaseDefinition.Table.POLICY
|
||||||
|
|
||||||
|
fun deleteOutdated(
|
||||||
|
nowSeconds: Long = TimeUnit.MILLISECONDS.toSeconds(now)
|
||||||
|
) = query<Delete> {
|
||||||
|
condition {
|
||||||
|
greaterThan("until", "0")
|
||||||
|
and {
|
||||||
|
lessThan("until", nowSeconds.toString())
|
||||||
|
}
|
||||||
|
or {
|
||||||
|
lessThan("until", "0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun delete(packageName: String) = query<Delete> {
|
||||||
|
condition {
|
||||||
|
equals("package_name", packageName)
|
||||||
|
}
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun delete(uid: Int) = query<Delete> {
|
||||||
|
condition {
|
||||||
|
equals("uid", uid)
|
||||||
|
}
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetch(uid: Int) = query<Select> {
|
||||||
|
condition {
|
||||||
|
equals("uid", uid)
|
||||||
|
}
|
||||||
|
}.map { it.first().toPolicySafe() }
|
||||||
|
|
||||||
|
fun update(policy: MagiskPolicy) = query<Replace> {
|
||||||
|
values(policy.toMap())
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetchAll() = query<Select> {
|
||||||
|
condition {
|
||||||
|
equals("uid/100000", Const.USER_ID)
|
||||||
|
}
|
||||||
|
}.map { it.mapNotNull { it.toPolicySafe() } }
|
||||||
|
|
||||||
|
|
||||||
|
private fun Map<String, String>.toPolicySafe(): MagiskPolicy? {
|
||||||
|
return runCatching { toPolicy(context.packageManager) }.getOrElse {
|
||||||
|
Timber.e(it)
|
||||||
|
if (it is PackageManager.NameNotFoundException) {
|
||||||
|
val uid = getOrElse("uid") { null } ?: return null
|
||||||
|
delete(uid).subscribe()
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,72 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class RepoDao {
|
||||||
|
|
||||||
|
val repoIDList get() = getRepoID().map { it.id }
|
||||||
|
|
||||||
|
val repos: List<Repo> get() = when (Config.repoOrder) {
|
||||||
|
Config.Value.ORDER_NAME -> getReposNameOrder()
|
||||||
|
else -> getReposDateOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
var etagKey: String
|
||||||
|
set(etag) = addEtagRaw(RepoEtag(0, etag))
|
||||||
|
get() = etagRaw()?.key.orEmpty()
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
clearRepos()
|
||||||
|
clearEtag()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query("SELECT * FROM repos ORDER BY last_update DESC")
|
||||||
|
protected abstract fun getReposDateOrder(): List<Repo>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM repos ORDER BY name COLLATE NOCASE")
|
||||||
|
protected abstract fun getReposNameOrder(): List<Repo>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
abstract fun addRepo(repo: Repo)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM repos WHERE id = :id")
|
||||||
|
abstract fun getRepo(id: String): Repo?
|
||||||
|
|
||||||
|
@Query("SELECT id FROM repos")
|
||||||
|
protected abstract fun getRepoID(): List<RepoID>
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
abstract fun removeRepo(repo: Repo)
|
||||||
|
|
||||||
|
@Query("DELETE FROM repos WHERE id = :id")
|
||||||
|
abstract fun removeRepo(id: String)
|
||||||
|
|
||||||
|
@Query("DELETE FROM repos WHERE id IN (:idList)")
|
||||||
|
abstract fun removeRepos(idList: Collection<String>)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM etag")
|
||||||
|
protected abstract fun etagRaw(): RepoEtag?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
protected abstract fun addEtagRaw(etag: RepoEtag)
|
||||||
|
|
||||||
|
@Query("DELETE FROM repos")
|
||||||
|
protected abstract fun clearRepos()
|
||||||
|
|
||||||
|
@Query("DELETE FROM etag")
|
||||||
|
protected abstract fun clearEtag()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RepoID(
|
||||||
|
@PrimaryKey val id: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Entity(tableName = "etag")
|
||||||
|
data class RepoEtag(
|
||||||
|
@PrimaryKey val id: Int,
|
||||||
|
val key: String
|
||||||
|
)
|
||||||
|
|
@@ -0,0 +1,11 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||||
|
|
||||||
|
@Database(version = 6, entities = [Repo::class, RepoEtag::class])
|
||||||
|
abstract class RepoDatabase : RoomDatabase() {
|
||||||
|
|
||||||
|
abstract fun repoDao() : RepoDao
|
||||||
|
}
|
@@ -1,107 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.data.database;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Config;
|
|
||||||
import com.topjohnwu.magisk.model.entity.Repo;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
|
||||||
|
|
||||||
private static final int DATABASE_VER = 5;
|
|
||||||
private static final String TABLE_NAME = "repos";
|
|
||||||
|
|
||||||
private SQLiteDatabase mDb;
|
|
||||||
|
|
||||||
public RepoDatabaseHelper(Context context) {
|
|
||||||
super(context, "repo.db", null, DATABASE_VER);
|
|
||||||
mDb = getWritableDatabase();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(SQLiteDatabase db) {
|
|
||||||
onUpgrade(db, 0, DATABASE_VER);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
||||||
if (oldVersion != newVersion) {
|
|
||||||
// Nuke old DB and create new table
|
|
||||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
|
||||||
db.execSQL(
|
|
||||||
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
|
|
||||||
"(id TEXT, name TEXT, version TEXT, versionCode INT, " +
|
|
||||||
"author TEXT, description TEXT, last_update INT, PRIMARY KEY(id))");
|
|
||||||
Config.remove(Config.Key.ETAG_KEY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
||||||
onUpgrade(db, 0, DATABASE_VER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearRepo() {
|
|
||||||
mDb.delete(TABLE_NAME, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void removeRepo(String id) {
|
|
||||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeRepo(Repo repo) {
|
|
||||||
removeRepo(repo.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeRepo(Iterable<String> list) {
|
|
||||||
for (String id : list) {
|
|
||||||
if (id == null) continue;
|
|
||||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addRepo(Repo repo) {
|
|
||||||
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Repo getRepo(String id) {
|
|
||||||
try (Cursor c = mDb.query(TABLE_NAME, null, "id=?", new String[] { id }, null, null, null)) {
|
|
||||||
if (c.moveToNext()) {
|
|
||||||
return new Repo(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cursor getRawCursor() {
|
|
||||||
return mDb.query(TABLE_NAME, null, null, null, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cursor getRepoCursor() {
|
|
||||||
String orderBy = null;
|
|
||||||
switch ((int) Config.get(Config.Key.REPO_ORDER)) {
|
|
||||||
case Config.Value.ORDER_NAME:
|
|
||||||
orderBy = "name COLLATE NOCASE";
|
|
||||||
break;
|
|
||||||
case Config.Value.ORDER_DATE:
|
|
||||||
orderBy = "last_update DESC";
|
|
||||||
}
|
|
||||||
return mDb.query(TABLE_NAME, null, null, null, null, null, orderBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getRepoIDSet() {
|
|
||||||
HashSet<String> set = new HashSet<>(300);
|
|
||||||
try (Cursor c = mDb.query(TABLE_NAME, null, null, null, null, null, null)) {
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
set.add(c.getString(c.getColumnIndex("id")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,22 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.data.database.base.*
|
||||||
|
|
||||||
|
class SettingsDao : BaseDao() {
|
||||||
|
|
||||||
|
override val table = DatabaseDefinition.Table.SETTINGS
|
||||||
|
|
||||||
|
fun delete(key: String) = query<Delete> {
|
||||||
|
condition { equals("key", key) }
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun put(key: String, value: Int) = query<Replace> {
|
||||||
|
values("key" to key, "value" to value)
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetch(key: String, default: Int = -1) = query<Select> {
|
||||||
|
fields("value")
|
||||||
|
condition { equals("key", key) }
|
||||||
|
}.map { it.firstOrNull()?.values?.firstOrNull()?.toIntOrNull() ?: default }
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,22 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.data.database.base.*
|
||||||
|
|
||||||
|
class StringDao : BaseDao() {
|
||||||
|
|
||||||
|
override val table = DatabaseDefinition.Table.STRINGS
|
||||||
|
|
||||||
|
fun delete(key: String) = query<Delete> {
|
||||||
|
condition { equals("key", key) }
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun put(key: String, value: String) = query<Replace> {
|
||||||
|
values("key" to key, "value" to value)
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetch(key: String, default: String = "") = query<Select> {
|
||||||
|
fields("value")
|
||||||
|
condition { equals("key", key) }
|
||||||
|
}.map { it.firstOrNull()?.values?.firstOrNull() ?: default }
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database.base
|
||||||
|
|
||||||
|
abstract class BaseDao {
|
||||||
|
|
||||||
|
abstract val table: String
|
||||||
|
|
||||||
|
inline fun <reified Builder : MagiskQueryBuilder> query(builder: Builder.() -> Unit) =
|
||||||
|
Builder::class.java.newInstance()
|
||||||
|
.apply { table = this@BaseDao.table }
|
||||||
|
.apply(builder)
|
||||||
|
.toString()
|
||||||
|
.let { MagiskQuery(it) }
|
||||||
|
.query()
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database.base
|
||||||
|
|
||||||
|
import androidx.annotation.AnyThread
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import io.reactivex.Single
|
||||||
|
|
||||||
|
object DatabaseDefinition {
|
||||||
|
|
||||||
|
object Table {
|
||||||
|
const val POLICY = "policies"
|
||||||
|
const val LOG = "logs"
|
||||||
|
const val SETTINGS = "settings"
|
||||||
|
const val STRINGS = "strings"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun MagiskQuery.query() = query.su()
|
||||||
|
|
||||||
|
fun String.suRaw() = Single.fromCallable { Shell.su(this).exec().out }
|
||||||
|
fun String.su() = suRaw().map { it.toMap() }
|
||||||
|
|
||||||
|
fun List<String>.toMap() = map { it.split(Regex("\\|")) }
|
||||||
|
.map { it.toMapInternal() }
|
||||||
|
|
||||||
|
private fun List<String>.toMapInternal() = map { it.split("=", limit = 2) }
|
||||||
|
.filter { it.size == 2 }
|
||||||
|
.map { Pair(it[0], it[1]) }
|
||||||
|
.toMap()
|
@@ -0,0 +1,5 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database.base
|
||||||
|
|
||||||
|
inline class MagiskQuery(private val _query: String) {
|
||||||
|
val query get() = "magisk --sqlite '$_query'"
|
||||||
|
}
|
@@ -0,0 +1,155 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database.base
|
||||||
|
|
||||||
|
import androidx.annotation.StringDef
|
||||||
|
import com.topjohnwu.magisk.data.database.base.Order.Companion.ASC
|
||||||
|
import com.topjohnwu.magisk.data.database.base.Order.Companion.DESC
|
||||||
|
|
||||||
|
interface MagiskQueryBuilder {
|
||||||
|
|
||||||
|
val requestType: String
|
||||||
|
var table: String
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
inline operator fun <reified Builder : MagiskQueryBuilder> invoke(builder: Builder.() -> Unit): MagiskQuery =
|
||||||
|
Builder::class.java.newInstance()
|
||||||
|
.apply(builder)
|
||||||
|
.toString()
|
||||||
|
.let {
|
||||||
|
MagiskQuery(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Delete : MagiskQueryBuilder {
|
||||||
|
override val requestType: String = "DELETE FROM"
|
||||||
|
override var table = ""
|
||||||
|
|
||||||
|
private var condition = ""
|
||||||
|
|
||||||
|
fun condition(builder: Condition.() -> Unit) {
|
||||||
|
condition = Condition().apply(builder).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return listOf(requestType, table, condition).joinToString(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Select : MagiskQueryBuilder {
|
||||||
|
override val requestType: String get() = "SELECT $fields FROM"
|
||||||
|
override lateinit var table: String
|
||||||
|
|
||||||
|
private var fields = "*"
|
||||||
|
private var condition = ""
|
||||||
|
private var orderField = ""
|
||||||
|
|
||||||
|
fun fields(vararg newFields: String) {
|
||||||
|
if (newFields.isEmpty()) {
|
||||||
|
fields = "*"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fields = newFields.joinToString(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun condition(builder: Condition.() -> Unit) {
|
||||||
|
condition = Condition().apply(builder).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun orderBy(field: String, @OrderStrict order: String) {
|
||||||
|
orderField = "ORDER BY $field $order"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return listOf(requestType, table, condition, orderField).joinToString(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Replace : Insert() {
|
||||||
|
override val requestType: String = "REPLACE INTO"
|
||||||
|
}
|
||||||
|
|
||||||
|
open class Insert : MagiskQueryBuilder {
|
||||||
|
override val requestType: String = "INSERT INTO"
|
||||||
|
override lateinit var table: String
|
||||||
|
|
||||||
|
private val keys get() = _values.keys.joinToString(",")
|
||||||
|
private val values get() = _values.values.joinToString(",") {
|
||||||
|
when (it) {
|
||||||
|
is Boolean -> if (it) "1" else "0"
|
||||||
|
is Number -> it.toString()
|
||||||
|
else -> "\"$it\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var _values: Map<String, Any> = mapOf()
|
||||||
|
|
||||||
|
fun values(vararg pairs: Pair<String, Any>) {
|
||||||
|
_values = pairs.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun values(values: Map<String, Any>) {
|
||||||
|
_values = values
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return listOf(requestType, table, "($keys) VALUES($values)").joinToString(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Condition {
|
||||||
|
|
||||||
|
private val conditionWord = "WHERE %s"
|
||||||
|
private var condition: String = ""
|
||||||
|
|
||||||
|
fun equals(field: String, value: Any) {
|
||||||
|
condition = when (value) {
|
||||||
|
is String -> "$field=\"$value\""
|
||||||
|
else -> "$field=$value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun greaterThan(field: String, value: String) {
|
||||||
|
condition = "$field > $value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lessThan(field: String, value: String) {
|
||||||
|
condition = "$field < $value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun greaterOrEqualTo(field: String, value: String) {
|
||||||
|
condition = "$field >= $value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lessOrEqualTo(field: String, value: String) {
|
||||||
|
condition = "$field <= $value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun and(builder: Condition.() -> Unit) {
|
||||||
|
condition = "($condition AND ${Condition().apply(builder).condition})"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun or(builder: Condition.() -> Unit) {
|
||||||
|
condition = "($condition OR ${Condition().apply(builder).condition})"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return conditionWord.format(condition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Order {
|
||||||
|
|
||||||
|
@set:OrderStrict
|
||||||
|
var order = DESC
|
||||||
|
var field = ""
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ASC = "ASC"
|
||||||
|
const val DESC = "DESC"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@StringDef(ASC, DESC)
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
annotation class OrderStrict
|
@@ -0,0 +1,81 @@
|
|||||||
|
package com.topjohnwu.magisk.data.network
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.Const
|
||||||
|
import com.topjohnwu.magisk.model.entity.UpdateInfo
|
||||||
|
import com.topjohnwu.magisk.tasks.GithubRepoInfo
|
||||||
|
import io.reactivex.Flowable
|
||||||
|
import io.reactivex.Single
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import retrofit2.adapter.rxjava2.Result
|
||||||
|
import retrofit2.http.*
|
||||||
|
|
||||||
|
interface GithubRawServices {
|
||||||
|
|
||||||
|
//region topjohnwu/magisk_files
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/master/stable.json")
|
||||||
|
fun fetchStableUpdate(): Single<UpdateInfo>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/master/beta.json")
|
||||||
|
fun fetchBetaUpdate(): Single<UpdateInfo>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/master/canary_builds/release.json")
|
||||||
|
fun fetchCanaryUpdate(): Single<UpdateInfo>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/master/canary_builds/canary.json")
|
||||||
|
fun fetchCanaryDebugUpdate(): Single<UpdateInfo>
|
||||||
|
|
||||||
|
@GET
|
||||||
|
fun fetchCustomUpdate(@Url url: String): Single<UpdateInfo>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/{$REVISION}/snet.jar")
|
||||||
|
@Streaming
|
||||||
|
fun fetchSafetynet(@Path(REVISION) revision: String = Const.SNET_REVISION): Single<ResponseBody>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/{$REVISION}/bootctl")
|
||||||
|
@Streaming
|
||||||
|
fun fetchBootctl(@Path(REVISION) revision: String = Const.BOOTCTL_REVISION): Single<ResponseBody>
|
||||||
|
|
||||||
|
@GET("$MAGISK_MASTER/scripts/module_installer.sh")
|
||||||
|
@Streaming
|
||||||
|
fun fetchInstaller(): Single<ResponseBody>
|
||||||
|
|
||||||
|
@GET("$MAGISK_MODULES/{$MODULE}/master/{$FILE}")
|
||||||
|
fun fetchModuleInfo(@Path(MODULE) id: String, @Path(FILE) file: String): Single<String>
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method shall be used exclusively for fetching files from urls from previous requests.
|
||||||
|
* Him, who uses it in a wrong way, shall die in an eternal flame.
|
||||||
|
* */
|
||||||
|
@GET
|
||||||
|
@Streaming
|
||||||
|
fun fetchFile(@Url url: String): Single<ResponseBody>
|
||||||
|
|
||||||
|
@GET
|
||||||
|
fun fetchString(@Url url: String): Single<String>
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val REVISION = "revision"
|
||||||
|
private const val MODULE = "module"
|
||||||
|
private const val FILE = "file"
|
||||||
|
|
||||||
|
|
||||||
|
private const val MAGISK_FILES = "topjohnwu/magisk_files"
|
||||||
|
private const val MAGISK_MASTER = "topjohnwu/Magisk/master"
|
||||||
|
private const val MAGISK_MODULES = "Magisk-Modules-Repo"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GithubApiServices {
|
||||||
|
|
||||||
|
@GET("repos")
|
||||||
|
fun fetchRepos(@Query("page") page: Int,
|
||||||
|
@Header(Const.Key.IF_NONE_MATCH) etag: String,
|
||||||
|
@Query("sort") sort: String = "pushed",
|
||||||
|
@Query("per_page") count: Int = 100): Flowable<Result<List<GithubRepoInfo>>>
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,106 @@
|
|||||||
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.data.database.SettingsDao
|
||||||
|
import com.topjohnwu.magisk.data.database.StringDao
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
interface DBConfig {
|
||||||
|
val settingsDao: SettingsDao
|
||||||
|
val stringDao: StringDao
|
||||||
|
|
||||||
|
fun dbSettings(
|
||||||
|
name: String,
|
||||||
|
default: Int
|
||||||
|
) = DBSettingsValue(name, default)
|
||||||
|
|
||||||
|
fun dbSettings(
|
||||||
|
name: String,
|
||||||
|
default: Boolean
|
||||||
|
) = DBBoolSettings(name, default)
|
||||||
|
|
||||||
|
fun dbStrings(
|
||||||
|
name: String,
|
||||||
|
default: String,
|
||||||
|
sync: Boolean = false
|
||||||
|
) = DBStringsValue(name, default, sync)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class DBSettingsValue(
|
||||||
|
private val name: String,
|
||||||
|
private val default: Int
|
||||||
|
) : ReadWriteProperty<DBConfig, Int> {
|
||||||
|
|
||||||
|
private var value: Int? = null
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Int {
|
||||||
|
if (value == null)
|
||||||
|
value = thisRef.settingsDao.fetch(name, default).blockingGet()
|
||||||
|
return value!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Int) {
|
||||||
|
synchronized(this) {
|
||||||
|
this.value = value
|
||||||
|
}
|
||||||
|
thisRef.settingsDao.put(name, value)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DBBoolSettings(
|
||||||
|
name: String,
|
||||||
|
default: Boolean
|
||||||
|
) : ReadWriteProperty<DBConfig, Boolean> {
|
||||||
|
|
||||||
|
val base = DBSettingsValue(name, if (default) 1 else 0)
|
||||||
|
|
||||||
|
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Boolean
|
||||||
|
= base.getValue(thisRef, property) != 0
|
||||||
|
|
||||||
|
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Boolean) =
|
||||||
|
base.setValue(thisRef, property, if (value) 1 else 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
class DBStringsValue(
|
||||||
|
private val name: String,
|
||||||
|
private val default: String,
|
||||||
|
private val sync: Boolean
|
||||||
|
) : ReadWriteProperty<DBConfig, String> {
|
||||||
|
|
||||||
|
private var value: String? = null
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun getValue(thisRef: DBConfig, property: KProperty<*>): String {
|
||||||
|
if (value == null)
|
||||||
|
value = thisRef.stringDao.fetch(name, default).blockingGet()
|
||||||
|
return value!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: String) {
|
||||||
|
synchronized(this) {
|
||||||
|
this.value = value
|
||||||
|
}
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
if (sync) {
|
||||||
|
thisRef.stringDao.delete(name).blockingAwait()
|
||||||
|
} else {
|
||||||
|
thisRef.stringDao.delete(name)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sync) {
|
||||||
|
thisRef.stringDao.put(name, value).blockingAwait()
|
||||||
|
} else {
|
||||||
|
thisRef.stringDao.put(name, value)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,40 @@
|
|||||||
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.Const
|
||||||
|
import com.topjohnwu.magisk.data.database.LogDao
|
||||||
|
import com.topjohnwu.magisk.data.database.base.suRaw
|
||||||
|
import com.topjohnwu.magisk.extensions.toSingle
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||||
|
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
|
class LogRepository(
|
||||||
|
private val logDao: LogDao
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun fetchLogs() = logDao.fetchAll()
|
||||||
|
.map { it.sortByDescending { it.date.time }; it }
|
||||||
|
.map { it.wrap() }
|
||||||
|
|
||||||
|
fun fetchMagiskLogs() = "tail -n 5000 ${Const.MAGISK_LOG}".suRaw()
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
|
||||||
|
fun clearLogs() = logDao.deleteAll()
|
||||||
|
fun clearOutdated() = logDao.deleteOutdated()
|
||||||
|
|
||||||
|
fun clearMagiskLogs() = Shell.su("echo -n > " + Const.MAGISK_LOG)
|
||||||
|
.toSingle()
|
||||||
|
.map { it.exec() }
|
||||||
|
|
||||||
|
fun put(log: MagiskLog) = logDao.put(log)
|
||||||
|
|
||||||
|
private fun List<MagiskLog>.wrap(): List<WrappedMagiskLog> {
|
||||||
|
val day = TimeUnit.DAYS.toMillis(1)
|
||||||
|
return groupBy { it.date.time / day }
|
||||||
|
.map { WrappedMagiskLog(it.key * day, it.value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,77 @@
|
|||||||
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.Info
|
||||||
|
import com.topjohnwu.magisk.data.database.base.su
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
|
import com.topjohnwu.magisk.extensions.getLabel
|
||||||
|
import com.topjohnwu.magisk.extensions.packageName
|
||||||
|
import com.topjohnwu.magisk.extensions.toSingle
|
||||||
|
import com.topjohnwu.magisk.model.entity.HideAppInfo
|
||||||
|
import com.topjohnwu.magisk.model.entity.HideTarget
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import io.reactivex.Single
|
||||||
|
|
||||||
|
class MagiskRepository(
|
||||||
|
private val apiRaw: GithubRawServices,
|
||||||
|
private val packageManager: PackageManager
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun fetchSafetynet() = apiRaw.fetchSafetynet()
|
||||||
|
|
||||||
|
fun fetchUpdate() = when (Config.updateChannel) {
|
||||||
|
Config.Value.DEFAULT_CHANNEL, Config.Value.STABLE_CHANNEL -> apiRaw.fetchStableUpdate()
|
||||||
|
Config.Value.BETA_CHANNEL -> apiRaw.fetchBetaUpdate()
|
||||||
|
Config.Value.CANARY_CHANNEL -> apiRaw.fetchCanaryUpdate()
|
||||||
|
Config.Value.CANARY_DEBUG_CHANNEL -> apiRaw.fetchCanaryDebugUpdate()
|
||||||
|
Config.Value.CUSTOM_CHANNEL -> apiRaw.fetchCustomUpdate(Config.customChannelUrl)
|
||||||
|
else -> throw IllegalArgumentException()
|
||||||
|
}.flatMap {
|
||||||
|
// If remote version is lower than current installed, try switching to beta
|
||||||
|
if (it.magisk.versionCode < Info.magiskVersionCode
|
||||||
|
&& Config.updateChannel == Config.Value.DEFAULT_CHANNEL) {
|
||||||
|
Config.updateChannel = Config.Value.BETA_CHANNEL
|
||||||
|
apiRaw.fetchBetaUpdate()
|
||||||
|
} else {
|
||||||
|
Single.just(it)
|
||||||
|
}
|
||||||
|
}.doOnSuccess { Info.remote = it }
|
||||||
|
|
||||||
|
fun fetchApps() =
|
||||||
|
Single.fromCallable { packageManager.getInstalledApplications(0) }
|
||||||
|
.flattenAsFlowable { it }
|
||||||
|
.filter { it.enabled && !blacklist.contains(it.packageName) }
|
||||||
|
.map {
|
||||||
|
val label = it.getLabel(packageManager)
|
||||||
|
val icon = it.loadIcon(packageManager)
|
||||||
|
HideAppInfo(it, label, icon)
|
||||||
|
}
|
||||||
|
.filter { it.processes.isNotEmpty() }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
fun fetchHideTargets() = Shell.su("magiskhide --ls").toSingle()
|
||||||
|
.map { it.exec().out }
|
||||||
|
.flattenAsFlowable { it }
|
||||||
|
.map { HideTarget(it) }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
fun toggleHide(isEnabled: Boolean, packageName: String, process: String) =
|
||||||
|
"magiskhide --%s %s %s".format(isEnabled.state, packageName, process).su().ignoreElement()
|
||||||
|
|
||||||
|
private val Boolean.state get() = if (this) "add" else "rm"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val blacklist by lazy { listOf(
|
||||||
|
packageName,
|
||||||
|
"android",
|
||||||
|
"com.android.chrome",
|
||||||
|
"com.chrome.beta",
|
||||||
|
"com.chrome.dev",
|
||||||
|
"com.chrome.canary",
|
||||||
|
"com.android.webview",
|
||||||
|
"com.google.android.webview"
|
||||||
|
) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
|
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||||
|
|
||||||
|
class StringRepository(
|
||||||
|
private val api: GithubRawServices
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun getString(url: String) = api.fetchString(url)
|
||||||
|
|
||||||
|
fun getMetadata(repo: Repo) = api.fetchModuleInfo(repo.id, "module.prop")
|
||||||
|
|
||||||
|
fun getReadme(repo: Repo) = api.fetchModuleInfo(repo.id, "README.md")
|
||||||
|
}
|
@@ -1,19 +1,61 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.skoumal.teanity.rxbus.RxBus
|
import com.skoumal.teanity.rxbus.RxBus
|
||||||
import com.topjohnwu.magisk.App
|
import org.koin.core.qualifier.named
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val SUTimeout = named("su_timeout")
|
||||||
|
val Protected = named("protected")
|
||||||
|
|
||||||
val applicationModule = module {
|
val applicationModule = module {
|
||||||
single { RxBus() }
|
single { RxBus() }
|
||||||
factory { get<Context>().resources }
|
factory { get<Context>().resources }
|
||||||
factory { get<Context>() as App }
|
|
||||||
factory { get<Context>().packageManager }
|
factory { get<Context>().packageManager }
|
||||||
single(SUTimeout) {
|
factory(Protected) { createDEContext(get()) }
|
||||||
get<App>().protectedContext.getSharedPreferences("su_timeout", 0)
|
single(SUTimeout) { get<Context>(Protected).getSharedPreferences("su_timeout", 0) }
|
||||||
}
|
single { PreferenceManager.getDefaultSharedPreferences(get<Context>(Protected)) }
|
||||||
single { PreferenceManager.getDefaultSharedPreferences(get<App>().protectedContext) }
|
single { ActivityTracker() }
|
||||||
|
factory { get<ActivityTracker>().foreground ?: NullActivity }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createDEContext(context: Context): Context {
|
||||||
|
return if (Build.VERSION.SDK_INT >= 24)
|
||||||
|
context.createDeviceProtectedStorageContext()
|
||||||
|
else context
|
||||||
|
}
|
||||||
|
|
||||||
|
class ActivityTracker : Application.ActivityLifecycleCallbacks {
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
var foreground: Activity? = null
|
||||||
|
|
||||||
|
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
|
||||||
|
|
||||||
|
override fun onActivityStarted(activity: Activity) {}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun onActivityResumed(activity: Activity) {
|
||||||
|
foreground = activity
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun onActivityPaused(activity: Activity) {
|
||||||
|
foreground = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityStopped(activity: Activity) {}
|
||||||
|
|
||||||
|
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
|
||||||
|
|
||||||
|
override fun onActivityDestroyed(activity: Activity) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("Registered")
|
||||||
|
object NullActivity : Activity()
|
||||||
|
@@ -1,12 +1,23 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
import com.topjohnwu.magisk.App
|
import android.content.Context
|
||||||
import com.topjohnwu.magisk.data.database.MagiskDB
|
import androidx.room.Room
|
||||||
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper
|
import com.topjohnwu.magisk.data.database.*
|
||||||
|
import com.topjohnwu.magisk.tasks.RepoUpdater
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|
||||||
val databaseModule = module {
|
val databaseModule = module {
|
||||||
single { MagiskDB(get<App>().protectedContext) }
|
single { LogDao() }
|
||||||
single { RepoDatabaseHelper(get()) }
|
single { PolicyDao(get()) }
|
||||||
|
single { SettingsDao() }
|
||||||
|
single { StringDao() }
|
||||||
|
single { createRepoDatabase(get()) }
|
||||||
|
single { get<RepoDatabase>().repoDao() }
|
||||||
|
single { RepoUpdater(get(), get()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createRepoDatabase(context: Context) =
|
||||||
|
Room.databaseBuilder(context, RepoDatabase::class.java, "repo.db")
|
||||||
|
.fallbackToDestructiveMigration()
|
||||||
|
.build()
|
||||||
|
@@ -1,10 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.di
|
|
||||||
|
|
||||||
import org.koin.dsl.module
|
|
||||||
|
|
||||||
|
|
||||||
val miscModule = module {
|
|
||||||
|
|
||||||
// define miscs here
|
|
||||||
|
|
||||||
}
|
|
@@ -5,6 +5,5 @@ val koinModules = listOf(
|
|||||||
networkingModule,
|
networkingModule,
|
||||||
databaseModule,
|
databaseModule,
|
||||||
repositoryModule,
|
repositoryModule,
|
||||||
viewModelModules,
|
viewModelModules
|
||||||
miscModule
|
|
||||||
)
|
)
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.di
|
|
||||||
|
|
||||||
import org.koin.core.qualifier.named
|
|
||||||
|
|
||||||
val SUTimeout = named("su_timeout")
|
|
@@ -1,6 +1,65 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.Const
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubApiServices
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||||
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
|
import retrofit2.converter.scalars.ScalarsConverterFactory
|
||||||
|
import se.ansman.kotshi.KotshiJsonAdapterFactory
|
||||||
|
|
||||||
|
val networkingModule = module {
|
||||||
|
single { createOkHttpClient() }
|
||||||
|
single { createMoshiConverterFactory() }
|
||||||
|
single { createRetrofit(get(), get()) }
|
||||||
|
single { createApiService<GithubRawServices>(get(), Const.Url.GITHUB_RAW_URL) }
|
||||||
|
single { createApiService<GithubApiServices>(get(), Const.Url.GITHUB_API_URL) }
|
||||||
|
}
|
||||||
|
|
||||||
val networkingModule = module {}
|
fun createOkHttpClient(): OkHttpClient {
|
||||||
|
val builder = OkHttpClient.Builder()
|
||||||
|
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
|
||||||
|
level = HttpLoggingInterceptor.Level.HEADERS
|
||||||
|
}
|
||||||
|
builder.addInterceptor(httpLoggingInterceptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createMoshiConverterFactory(): MoshiConverterFactory {
|
||||||
|
val moshi = Moshi.Builder()
|
||||||
|
.add(KotshiJsonAdapterFactory)
|
||||||
|
.build()
|
||||||
|
return MoshiConverterFactory.create(moshi)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createRetrofit(
|
||||||
|
okHttpClient: OkHttpClient,
|
||||||
|
converterFactory: MoshiConverterFactory
|
||||||
|
): Retrofit.Builder {
|
||||||
|
return Retrofit.Builder()
|
||||||
|
.addConverterFactory(ScalarsConverterFactory.create())
|
||||||
|
.addConverterFactory(converterFactory)
|
||||||
|
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||||
|
.client(okHttpClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
@KotshiJsonAdapterFactory
|
||||||
|
abstract class JsonAdapterFactory : JsonAdapter.Factory
|
||||||
|
|
||||||
|
inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseUrl: String): T {
|
||||||
|
return retrofitBuilder
|
||||||
|
.baseUrl(baseUrl)
|
||||||
|
.build()
|
||||||
|
.create(T::class.java)
|
||||||
|
}
|
@@ -1,6 +1,13 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||||
|
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||||
|
import com.topjohnwu.magisk.data.repository.StringRepository
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|
||||||
val repositoryModule = module {}
|
val repositoryModule = module {
|
||||||
|
single { MagiskRepository(get(), get()) }
|
||||||
|
single { LogRepository(get()) }
|
||||||
|
single { StringRepository(get()) }
|
||||||
|
}
|
||||||
|
@@ -18,8 +18,10 @@ val viewModelModules = module {
|
|||||||
viewModel { HomeViewModel(get()) }
|
viewModel { HomeViewModel(get()) }
|
||||||
viewModel { SuperuserViewModel(get(), get(), get(), get()) }
|
viewModel { SuperuserViewModel(get(), get(), get(), get()) }
|
||||||
viewModel { HideViewModel(get(), get()) }
|
viewModel { HideViewModel(get(), get()) }
|
||||||
viewModel { ModuleViewModel(get(), get()) }
|
viewModel { ModuleViewModel(get(), get(), get()) }
|
||||||
viewModel { LogViewModel(get(), get()) }
|
viewModel { LogViewModel(get(), get()) }
|
||||||
viewModel { (action: String, uri: Uri?) -> FlashViewModel(action, uri, get()) }
|
viewModel { (action: String, file: Uri, additional: Uri) ->
|
||||||
|
FlashViewModel(action, file, additional, get())
|
||||||
|
}
|
||||||
viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) }
|
viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) }
|
||||||
}
|
}
|
||||||
|
121
app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt
Normal file
121
app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.ComponentInfo
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.pm.PackageManager.*
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
import com.topjohnwu.magisk.utils.FileProvider
|
||||||
|
import com.topjohnwu.magisk.utils.currentLocale
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
|
||||||
|
val packageName: String get() = get<Context>().packageName
|
||||||
|
|
||||||
|
val PackageInfo.processes
|
||||||
|
get() = activities?.processNames.orEmpty() +
|
||||||
|
services?.processNames.orEmpty() +
|
||||||
|
receivers?.processNames.orEmpty() +
|
||||||
|
providers?.processNames.orEmpty()
|
||||||
|
|
||||||
|
val Array<out ComponentInfo>.processNames get() = mapNotNull { it.processName }
|
||||||
|
|
||||||
|
val ApplicationInfo.packageInfo: PackageInfo?
|
||||||
|
get() {
|
||||||
|
val pm: PackageManager by inject()
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val request = GET_ACTIVITIES or
|
||||||
|
GET_SERVICES or
|
||||||
|
GET_RECEIVERS or
|
||||||
|
GET_PROVIDERS
|
||||||
|
pm.getPackageInfo(packageName, request)
|
||||||
|
} catch (e1: Exception) {
|
||||||
|
try {
|
||||||
|
pm.activities(packageName).apply {
|
||||||
|
services = pm.services(packageName)
|
||||||
|
receivers = pm.receivers(packageName)
|
||||||
|
providers = pm.providers(packageName)
|
||||||
|
}
|
||||||
|
} catch (e2: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val Uri.fileName: String
|
||||||
|
get() {
|
||||||
|
var name: String? = null
|
||||||
|
get<Context>().contentResolver.query(this, null, null, null, null)?.use { c ->
|
||||||
|
val nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||||
|
if (nameIndex != -1) {
|
||||||
|
c.moveToFirst()
|
||||||
|
name = c.getString(nameIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name == null && path != null) {
|
||||||
|
val idx = path!!.lastIndexOf('/')
|
||||||
|
name = path!!.substring(idx + 1)
|
||||||
|
}
|
||||||
|
return name.orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PackageManager.activities(packageName: String) =
|
||||||
|
getPackageInfo(packageName, GET_ACTIVITIES)
|
||||||
|
|
||||||
|
fun PackageManager.services(packageName: String) =
|
||||||
|
getPackageInfo(packageName, GET_SERVICES).services
|
||||||
|
|
||||||
|
fun PackageManager.receivers(packageName: String) =
|
||||||
|
getPackageInfo(packageName, GET_RECEIVERS).receivers
|
||||||
|
|
||||||
|
fun PackageManager.providers(packageName: String) =
|
||||||
|
getPackageInfo(packageName, GET_PROVIDERS).providers
|
||||||
|
|
||||||
|
fun Context.rawResource(id: Int) = resources.openRawResource(id)
|
||||||
|
|
||||||
|
fun Context.readUri(uri: Uri) =
|
||||||
|
contentResolver.openInputStream(uri) ?: throw FileNotFoundException()
|
||||||
|
|
||||||
|
fun Intent.startActivity(context: Context) = context.startActivity(this)
|
||||||
|
|
||||||
|
fun File.provide(context: Context = get()): Uri {
|
||||||
|
return FileProvider.getUriForFile(context, context.packageName + ".provider", this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun File.mv(destination: File) {
|
||||||
|
inputStream().writeTo(destination)
|
||||||
|
deleteRecursively()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.toFile() = File(this)
|
||||||
|
|
||||||
|
fun Intent.chooser(title: String = "Pick an app") = Intent.createChooser(this, title)
|
||||||
|
|
||||||
|
fun Context.cachedFile(name: String) = File(cacheDir, name)
|
||||||
|
|
||||||
|
fun <Result> Cursor.toList(transformer: (Cursor) -> Result): List<Result> {
|
||||||
|
val out = mutableListOf<Result>()
|
||||||
|
while (moveToNext()) out.add(transformer(this))
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ApplicationInfo.getLabel(pm: PackageManager): String {
|
||||||
|
runCatching {
|
||||||
|
if (labelRes > 0) {
|
||||||
|
val res = pm.getResourcesForApplication(this)
|
||||||
|
val config = Configuration()
|
||||||
|
config.setLocale(currentLocale)
|
||||||
|
res.updateConfiguration(config, res.displayMetrics)
|
||||||
|
return res.getString(labelRes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadLabel(pm).toString()
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
import com.skoumal.teanity.util.KObservableField
|
||||||
|
|
103
app/src/main/java/com/topjohnwu/magisk/extensions/XJava.kt
Normal file
103
app/src/main/java/com/topjohnwu/magisk/extensions/XJava.kt
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.net.toFile
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.util.*
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
import kotlin.NoSuchElementException
|
||||||
|
|
||||||
|
fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
|
||||||
|
var entry: ZipEntry? = nextEntry
|
||||||
|
while (entry != null) {
|
||||||
|
callback(entry)
|
||||||
|
entry = nextEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Uri.writeTo(file: File) = toFile().copyTo(file)
|
||||||
|
|
||||||
|
fun InputStream.writeTo(file: File) =
|
||||||
|
withStreams(this, file.outputStream()) { reader, writer -> reader.copyTo(writer) }
|
||||||
|
|
||||||
|
inline fun <In : InputStream, Out : OutputStream> withStreams(
|
||||||
|
inStream: In,
|
||||||
|
outStream: Out,
|
||||||
|
withBoth: (In, Out) -> Unit
|
||||||
|
) {
|
||||||
|
inStream.use { reader ->
|
||||||
|
outStream.use { writer ->
|
||||||
|
withBoth(reader, writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T, R> List<T>.firstMap(mapper: (T) -> R?): R {
|
||||||
|
for (item: T in this) {
|
||||||
|
return mapper(item) ?: continue
|
||||||
|
}
|
||||||
|
throw NoSuchElementException("Collection contains no element matching the predicate.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.langTagToLocale(): Locale {
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
return Locale.forLanguageTag(this)
|
||||||
|
} else {
|
||||||
|
val tok = split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
if (tok.isEmpty()) {
|
||||||
|
return Locale("")
|
||||||
|
}
|
||||||
|
val language = when (tok[0]) {
|
||||||
|
"und" -> "" // Undefined
|
||||||
|
"fil" -> "tl" // Filipino
|
||||||
|
else -> tok[0]
|
||||||
|
}
|
||||||
|
if (language.length != 2 && language.length != 3)
|
||||||
|
return Locale("")
|
||||||
|
if (tok.size == 1)
|
||||||
|
return Locale(language)
|
||||||
|
val country = tok[1]
|
||||||
|
|
||||||
|
return if (country.length != 2 && country.length != 3) Locale(language)
|
||||||
|
else Locale(language, country)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Locale.toLangTag(): String {
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
return toLanguageTag()
|
||||||
|
} else {
|
||||||
|
var language = language
|
||||||
|
var country = country
|
||||||
|
var variant = variant
|
||||||
|
when {
|
||||||
|
language.isEmpty() || !language.matches("\\p{Alpha}{2,8}".toRegex()) ->
|
||||||
|
language = "und" // Follow the Locale#toLanguageTag() implementation
|
||||||
|
language == "iw" -> language = "he" // correct deprecated "Hebrew"
|
||||||
|
language == "in" -> language = "id" // correct deprecated "Indonesian"
|
||||||
|
language == "ji" -> language = "yi" // correct deprecated "Yiddish"
|
||||||
|
}
|
||||||
|
// ensure valid country code, if not well formed, it's omitted
|
||||||
|
|
||||||
|
// variant subtags that begin with a letter must be at least 5 characters long
|
||||||
|
// ensure valid country code, if not well formed, it's omitted
|
||||||
|
if (!country.matches("\\p{Alpha}{2}|\\p{Digit}{3}".toRegex())) {
|
||||||
|
country = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// variant subtags that begin with a letter must be at least 5 characters long
|
||||||
|
if (!variant.matches("\\p{Alnum}{5,8}|\\p{Digit}\\p{Alnum}{3}".toRegex())) {
|
||||||
|
variant = ""
|
||||||
|
}
|
||||||
|
val tag = StringBuilder(language)
|
||||||
|
if (country.isNotEmpty())
|
||||||
|
tag.append('-').append(country)
|
||||||
|
if (variant.isNotEmpty())
|
||||||
|
tag.append('-').append(variant)
|
||||||
|
return tag.toString()
|
||||||
|
}
|
||||||
|
}
|
@@ -1,20 +1,17 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
import org.koin.core.context.GlobalContext
|
import org.koin.core.context.GlobalContext
|
||||||
import org.koin.core.parameter.ParametersDefinition
|
import org.koin.core.parameter.ParametersDefinition
|
||||||
import org.koin.core.qualifier.Qualifier
|
import org.koin.core.qualifier.Qualifier
|
||||||
import org.koin.core.scope.Scope
|
|
||||||
|
|
||||||
fun getKoin() = GlobalContext.get().koin
|
fun getKoin() = GlobalContext.get().koin
|
||||||
|
|
||||||
inline fun <reified T : Any> inject(
|
inline fun <reified T> inject(
|
||||||
qualifier: Qualifier? = null,
|
qualifier: Qualifier? = null,
|
||||||
scope: Scope? = null,
|
|
||||||
noinline parameters: ParametersDefinition? = null
|
noinline parameters: ParametersDefinition? = null
|
||||||
) = lazy { get<T>(qualifier, scope, parameters) }
|
) = lazy { get<T>(qualifier, parameters) }
|
||||||
|
|
||||||
inline fun <reified T : Any> get(
|
inline fun <reified T> get(
|
||||||
qualifier: Qualifier? = null,
|
qualifier: Qualifier? = null,
|
||||||
scope: Scope? = null,
|
|
||||||
noinline parameters: ParametersDefinition? = null
|
noinline parameters: ParametersDefinition? = null
|
||||||
): T = getKoin().get(qualifier, scope, parameters)
|
): T = getKoin().get(qualifier, parameters)
|
@@ -1,5 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import androidx.collection.SparseArrayCompat
|
||||||
import androidx.databinding.ObservableList
|
import androidx.databinding.ObservableList
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
import com.skoumal.teanity.extensions.subscribeK
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
import com.skoumal.teanity.util.DiffObservableList
|
||||||
@@ -76,4 +77,8 @@ fun <T1> ObservableList<T1>.copyNewInputInto(
|
|||||||
val addedValues = sender?.slice(positionStart until positionEnd).orEmpty()
|
val addedValues = sender?.slice(positionStart until positionEnd).orEmpty()
|
||||||
target.addAll(addedValues)
|
target.addAll(addedValues)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
operator fun <E> SparseArrayCompat<E>.set(key: Int, value: E) {
|
||||||
|
put(key, value)
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import io.reactivex.functions.BiFunction
|
import io.reactivex.functions.BiFunction
|
14
app/src/main/java/com/topjohnwu/magisk/extensions/XSU.kt
Normal file
14
app/src/main/java/com/topjohnwu/magisk/extensions/XSU.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.Info
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.io.SuFileInputStream
|
||||||
|
import com.topjohnwu.superuser.io.SuFileOutputStream
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun reboot(reason: String = if (Info.recovery) "recovery" else "") {
|
||||||
|
Shell.su("/system/bin/svc power reboot $reason || /system/bin/reboot $reason").submit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun File.suOutputStream() = SuFileOutputStream(this)
|
||||||
|
fun File.suInputStream() = SuFileInputStream(this)
|
27
app/src/main/java/com/topjohnwu/magisk/extensions/XString.kt
Normal file
27
app/src/main/java/com/topjohnwu/magisk/extensions/XString.kt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
|
||||||
|
val specialChars = arrayOf('!', '@', '#', '$', '%', '&', '?')
|
||||||
|
|
||||||
|
fun String.replaceRandomWithSpecial(): String {
|
||||||
|
var random: Char
|
||||||
|
do {
|
||||||
|
random = random()
|
||||||
|
} while (random == '.')
|
||||||
|
return replace(random, specialChars.random())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun StringBuilder.appendIf(condition: Boolean, builder: StringBuilder.() -> Unit) =
|
||||||
|
if (condition) apply(builder) else this
|
||||||
|
|
||||||
|
fun Int.res(vararg args: Any): String {
|
||||||
|
val resources: Resources by inject()
|
||||||
|
return resources.getString(this, *args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.trimEmptyToNull(): String? = if (isBlank()) null else this
|
||||||
|
|
||||||
|
fun String.legalFilename() = replace(" ", "_").replace("'", "").replace("\"", "")
|
||||||
|
.replace("$", "").replace("`", "").replace("*", "").replace("/", "_")
|
||||||
|
.replace("#", "").replace("@", "").replace("\\", "_")
|
20
app/src/main/java/com/topjohnwu/magisk/extensions/XTime.kt
Normal file
20
app/src/main/java/com/topjohnwu/magisk/extensions/XTime.kt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.utils.currentLocale
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.text.ParseException
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
|
val now get() = System.currentTimeMillis()
|
||||||
|
|
||||||
|
fun Long.toTime(format: DateFormat) = format.format(this).orEmpty()
|
||||||
|
fun String.toTime(format: DateFormat) = try {
|
||||||
|
format.parse(this)?.time ?: -1
|
||||||
|
} catch (e: ParseException) {
|
||||||
|
-1L
|
||||||
|
}
|
||||||
|
|
||||||
|
val timeFormatFull by lazy { SimpleDateFormat("yyyy/MM/dd_HH:mm:ss", 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) }
|
14
app/src/main/java/com/topjohnwu/magisk/extensions/XView.kt
Normal file
14
app/src/main/java/com/topjohnwu/magisk/extensions/XView.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewTreeObserver
|
||||||
|
|
||||||
|
fun View.setOnViewReadyListener(callback: () -> Unit) = addOnGlobalLayoutListener(true, callback)
|
||||||
|
|
||||||
|
fun View.addOnGlobalLayoutListener(oneShot: Boolean = false, callback: () -> Unit) =
|
||||||
|
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
|
override fun onGlobalLayout() {
|
||||||
|
if (oneShot) viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
})
|
@@ -0,0 +1,37 @@
|
|||||||
|
package com.topjohnwu.magisk.model.binding
|
||||||
|
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||||
|
import com.topjohnwu.magisk.model.entity.recycler.LenientRvItem
|
||||||
|
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
|
||||||
|
|
||||||
|
class BindingAdapter : BindingRecyclerViewAdapter<ComparableRvItem<*>>() {
|
||||||
|
|
||||||
|
private var recyclerView: RecyclerView? = null
|
||||||
|
|
||||||
|
override fun onBindBinding(
|
||||||
|
binding: ViewDataBinding,
|
||||||
|
variableId: Int,
|
||||||
|
layoutRes: Int,
|
||||||
|
position: Int,
|
||||||
|
item: ComparableRvItem<*>
|
||||||
|
) {
|
||||||
|
super.onBindBinding(binding, variableId, layoutRes, position, item)
|
||||||
|
|
||||||
|
when (item) {
|
||||||
|
is LenientRvItem -> {
|
||||||
|
val recycler = recyclerView ?: return
|
||||||
|
item.onBindingBound(binding)
|
||||||
|
item.onBindingBound(binding, recycler)
|
||||||
|
}
|
||||||
|
else -> item.onBindingBound(binding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
super.onAttachedToRecyclerView(recyclerView)
|
||||||
|
this.recyclerView = recyclerView
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,156 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.model.download;
|
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.IBinder;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.App;
|
|
||||||
import com.topjohnwu.magisk.ClassMap;
|
|
||||||
import com.topjohnwu.magisk.Const;
|
|
||||||
import com.topjohnwu.magisk.model.entity.Repo;
|
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashActivity;
|
|
||||||
import com.topjohnwu.magisk.view.Notifications;
|
|
||||||
import com.topjohnwu.magisk.view.ProgressNotification;
|
|
||||||
import com.topjohnwu.net.Networking;
|
|
||||||
import com.topjohnwu.superuser.Shell;
|
|
||||||
import com.topjohnwu.superuser.ShellUtils;
|
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipInputStream;
|
|
||||||
import java.util.zip.ZipOutputStream;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
public class DownloadModuleService extends Service {
|
|
||||||
|
|
||||||
private List<ProgressNotification> notifications;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
notifications = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
||||||
Shell.EXECUTOR.execute(() -> {
|
|
||||||
Repo repo = intent.getParcelableExtra("repo");
|
|
||||||
boolean install = intent.getBooleanExtra("install", false);
|
|
||||||
dlProcessInstall(repo, install);
|
|
||||||
});
|
|
||||||
return START_REDELIVER_INTENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void onTaskRemoved(Intent rootIntent) {
|
|
||||||
for (ProgressNotification n : notifications) {
|
|
||||||
Notifications.mgr.cancel(n.hashCode());
|
|
||||||
}
|
|
||||||
notifications.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void addNotification(ProgressNotification n) {
|
|
||||||
if (notifications.isEmpty()) {
|
|
||||||
// Start foreground
|
|
||||||
startForeground(n.hashCode(), n.getNotification());
|
|
||||||
}
|
|
||||||
notifications.add(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void removeNotification(ProgressNotification n) {
|
|
||||||
notifications.remove(n);
|
|
||||||
if (notifications.isEmpty()) {
|
|
||||||
// No more tasks, stop service
|
|
||||||
stopForeground(true);
|
|
||||||
stopSelf();
|
|
||||||
} else {
|
|
||||||
// Pick another notification as our foreground notification
|
|
||||||
n = notifications.get(0);
|
|
||||||
startForeground(n.hashCode(), n.getNotification());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dlProcessInstall(Repo repo, boolean install) {
|
|
||||||
File output = new File(Const.EXTERNAL_PATH, repo.getDownloadFilename());
|
|
||||||
ProgressNotification progress = new ProgressNotification(output.getName());
|
|
||||||
addNotification(progress);
|
|
||||||
try {
|
|
||||||
InputStream in = Networking.get(repo.getZipUrl())
|
|
||||||
.setDownloadProgressListener(progress)
|
|
||||||
.execForInputStream().getResult();
|
|
||||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(output));
|
|
||||||
processZip(in, out);
|
|
||||||
Intent intent = new Intent(this, ClassMap.get(FlashActivity.class));
|
|
||||||
intent.setData(Uri.fromFile(output))
|
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
|
||||||
synchronized (getApplication()) {
|
|
||||||
if (install && App.foreground() != null &&
|
|
||||||
!(App.foreground() instanceof FlashActivity)) {
|
|
||||||
/* Only start flashing if there is a foreground activity and the
|
|
||||||
* user is not also flashing another module at the same time */
|
|
||||||
App.foreground().startActivity(intent);
|
|
||||||
} else {
|
|
||||||
/* Or else we preset a notification notifying that we are done */
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(this, progress.hashCode(), intent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
progress.dlDone(pi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
progress.dlFail();
|
|
||||||
}
|
|
||||||
removeNotification(progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processZip(InputStream in, OutputStream out)
|
|
||||||
throws IOException {
|
|
||||||
try (ZipInputStream zin = new ZipInputStream(in);
|
|
||||||
ZipOutputStream zout = new ZipOutputStream(out)) {
|
|
||||||
|
|
||||||
// Inject latest module-installer.sh as update-binary
|
|
||||||
zout.putNextEntry(new ZipEntry("META-INF/"));
|
|
||||||
zout.putNextEntry(new ZipEntry("META-INF/com/"));
|
|
||||||
zout.putNextEntry(new ZipEntry("META-INF/com/google/"));
|
|
||||||
zout.putNextEntry(new ZipEntry("META-INF/com/google/android/"));
|
|
||||||
zout.putNextEntry(new ZipEntry("META-INF/com/google/android/update-binary"));
|
|
||||||
try (InputStream update_bin = Networking.get(Const.Url.MODULE_INSTALLER)
|
|
||||||
.execForInputStream().getResult()) {
|
|
||||||
ShellUtils.pump(update_bin, zout);
|
|
||||||
}
|
|
||||||
zout.putNextEntry(new ZipEntry("META-INF/com/google/android/updater-script"));
|
|
||||||
zout.write("#MAGISK\n".getBytes("UTF-8"));
|
|
||||||
|
|
||||||
int off = -1;
|
|
||||||
ZipEntry entry;
|
|
||||||
while ((entry = zin.getNextEntry()) != null) {
|
|
||||||
if (off < 0)
|
|
||||||
off = entry.getName().indexOf('/') + 1;
|
|
||||||
String path = entry.getName().substring(off);
|
|
||||||
if (path.isEmpty())
|
|
||||||
continue;
|
|
||||||
if (path.startsWith("META-INF"))
|
|
||||||
continue;
|
|
||||||
zout.putNextEntry(new ZipEntry(path));
|
|
||||||
if (!entry.isDirectory())
|
|
||||||
ShellUtils.pump(zin, zout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,142 @@
|
|||||||
|
package com.topjohnwu.magisk.model.download
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import com.topjohnwu.magisk.ClassMap
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.extensions.chooser
|
||||||
|
import com.topjohnwu.magisk.extensions.provide
|
||||||
|
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.DownloadSubject
|
||||||
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
|
||||||
|
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
||||||
|
import com.topjohnwu.magisk.utils.APKInstall
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.random.Random.Default.nextInt
|
||||||
|
|
||||||
|
/* More of a facade for [RemoteFileService], but whatever... */
|
||||||
|
@SuppressLint("Registered")
|
||||||
|
open class DownloadService : RemoteFileService() {
|
||||||
|
|
||||||
|
private val context get() = this
|
||||||
|
private val File.type
|
||||||
|
get() = MimeTypeMap.getSingleton()
|
||||||
|
.getMimeTypeFromExtension(extension)
|
||||||
|
?: "resource/folder"
|
||||||
|
|
||||||
|
override fun onFinished(subject: DownloadSubject, id: Int) = when (subject) {
|
||||||
|
is Magisk -> onFinishedInternal(subject, id)
|
||||||
|
is Module -> onFinishedInternal(subject, id)
|
||||||
|
is Manager -> onFinishedInternal(subject, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onFinishedInternal(
|
||||||
|
subject: Magisk,
|
||||||
|
id: Int
|
||||||
|
) = when (val conf = subject.configuration) {
|
||||||
|
Uninstall -> FlashActivity.uninstall(this, subject.file, id)
|
||||||
|
is Patch -> FlashActivity.patch(this, subject.file, conf.fileUri, id)
|
||||||
|
is Flash -> FlashActivity.flash(this, subject.file, conf is Secondary, id)
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onFinishedInternal(
|
||||||
|
subject: Module,
|
||||||
|
id: Int
|
||||||
|
) = when (subject.configuration) {
|
||||||
|
is Flash -> FlashActivity.install(this, subject.file, id)
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onFinishedInternal(
|
||||||
|
subject: Manager,
|
||||||
|
id: Int
|
||||||
|
) {
|
||||||
|
remove(id)
|
||||||
|
when (subject.configuration) {
|
||||||
|
is APK.Upgrade -> APKInstall.install(this, subject.file)
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
override fun NotificationCompat.Builder.addActions(subject: DownloadSubject)
|
||||||
|
= when (subject) {
|
||||||
|
is Magisk -> addActionsInternal(subject)
|
||||||
|
is Module -> addActionsInternal(subject)
|
||||||
|
is Manager -> addActionsInternal(subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NotificationCompat.Builder.addActionsInternal(subject: Magisk)
|
||||||
|
= when (val conf = subject.configuration) {
|
||||||
|
Download -> addAction(0, R.string.download_open_parent, fileIntent(subject.file.parentFile!!))
|
||||||
|
.addAction(0, R.string.download_open_self, fileIntent(subject.file))
|
||||||
|
Uninstall -> setContentIntent(FlashActivity.uninstallIntent(context, subject.file))
|
||||||
|
is Flash -> setContentIntent(FlashActivity.flashIntent(context, subject.file, conf is Secondary))
|
||||||
|
is Patch -> setContentIntent(FlashActivity.patchIntent(context, subject.file, conf.fileUri))
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NotificationCompat.Builder.addActionsInternal(subject: Module)
|
||||||
|
= when (subject.configuration) {
|
||||||
|
Download -> addAction(0, R.string.download_open_parent, fileIntent(subject.file.parentFile!!))
|
||||||
|
.addAction(0, R.string.download_open_self, fileIntent(subject.file))
|
||||||
|
is Flash -> setContentIntent(FlashActivity.installIntent(context, subject.file))
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NotificationCompat.Builder.addActionsInternal(subject: Manager)
|
||||||
|
= when (subject.configuration) {
|
||||||
|
APK.Upgrade -> setContentIntent(APKInstall.installIntent(context, subject.file))
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("ReplaceSingleLineLet")
|
||||||
|
private fun NotificationCompat.Builder.setContentIntent(intent: Intent) =
|
||||||
|
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
||||||
|
.let { setContentIntent(it) }
|
||||||
|
|
||||||
|
@Suppress("ReplaceSingleLineLet")
|
||||||
|
private fun NotificationCompat.Builder.addAction(icon: Int, title: Int, intent: Intent) =
|
||||||
|
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
||||||
|
.let { addAction(icon, getString(title), it) }
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
private fun fileIntent(file: File): Intent {
|
||||||
|
return Intent(Intent.ACTION_VIEW)
|
||||||
|
.setDataAndType(file.provide(this), file.type)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
.chooser()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
lateinit var subject: DownloadSubject
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
inline operator fun invoke(context: Context, argBuilder: Builder.() -> Unit) {
|
||||||
|
val app = context.applicationContext
|
||||||
|
val builder = Builder().apply(argBuilder)
|
||||||
|
val intent = Intent(app, ClassMap[DownloadService::class.java])
|
||||||
|
.putExtra(ARG_URL, builder.subject)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
app.startForegroundService(intent)
|
||||||
|
} else {
|
||||||
|
app.startService(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,65 @@
|
|||||||
|
package com.topjohnwu.magisk.model.download
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.ClassMap
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
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.DownloadSubject
|
||||||
|
import com.topjohnwu.magisk.ui.SplashActivity
|
||||||
|
import com.topjohnwu.magisk.utils.DynamicClassLoader
|
||||||
|
import com.topjohnwu.magisk.utils.PatchAPK
|
||||||
|
import com.topjohnwu.magisk.utils.RootUtils
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
private fun RemoteFileService.patchPackage(apk: File, id: Int) {
|
||||||
|
if (packageName != BuildConfig.APPLICATION_ID) {
|
||||||
|
update(id) { notification ->
|
||||||
|
notification.setProgress(0, 0, true)
|
||||||
|
.setProgress(0, 0, true)
|
||||||
|
.setContentTitle(getString(R.string.hide_manager_title))
|
||||||
|
.setContentText("")
|
||||||
|
}
|
||||||
|
val patched = File(apk.parent, "patched.apk")
|
||||||
|
try {
|
||||||
|
// Try using the new APK to patch itself
|
||||||
|
val loader = DynamicClassLoader(apk)
|
||||||
|
loader.loadClass("a.a")
|
||||||
|
.getMethod("patchAPK", String::class.java, String::class.java, String::class.java)
|
||||||
|
.invoke(null, apk.path, patched.path, packageName)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
// Fallback to use the current implementation
|
||||||
|
PatchAPK.patch(apk.path, patched.path, packageName)
|
||||||
|
}
|
||||||
|
apk.delete()
|
||||||
|
patched.renameTo(apk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun RemoteFileService.restore(apk: File, id: Int) {
|
||||||
|
update(id) { notification ->
|
||||||
|
notification.setProgress(0, 0, true)
|
||||||
|
.setProgress(0, 0, true)
|
||||||
|
.setContentTitle(getString(R.string.restore_img_msg))
|
||||||
|
.setContentText("")
|
||||||
|
}
|
||||||
|
Config.export()
|
||||||
|
// Make it world readable
|
||||||
|
apk.setReadable(true, false)
|
||||||
|
if (Shell.su("pm install $apk").exec().isSuccess) {
|
||||||
|
val component = ComponentName(BuildConfig.APPLICATION_ID,
|
||||||
|
ClassMap.get<Class<*>>(SplashActivity::class.java).name)
|
||||||
|
RootUtils.rmAndLaunch(packageName, component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RemoteFileService.handleAPK(subject: DownloadSubject.Manager)
|
||||||
|
= when (subject.configuration) {
|
||||||
|
is Upgrade -> patchPackage(subject.file, subject.hashCode())
|
||||||
|
is Restore -> restore(subject.file, subject.hashCode())
|
||||||
|
}
|
@@ -0,0 +1,44 @@
|
|||||||
|
package com.topjohnwu.magisk.model.download
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.extensions.withStreams
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
|
fun InputStream.toModule(file: File, installer: InputStream) {
|
||||||
|
|
||||||
|
val input = ZipInputStream(buffered())
|
||||||
|
val output = ZipOutputStream(file.outputStream().buffered())
|
||||||
|
|
||||||
|
withStreams(input, output) { zin, zout ->
|
||||||
|
zout.putNextEntry(ZipEntry("META-INF/"))
|
||||||
|
zout.putNextEntry(ZipEntry("META-INF/com/"))
|
||||||
|
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
|
||||||
|
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
|
||||||
|
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
|
||||||
|
installer.copyTo(zout)
|
||||||
|
|
||||||
|
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
|
||||||
|
zout.write("#MAGISK\n".toByteArray(charset("UTF-8")))
|
||||||
|
|
||||||
|
var off = -1
|
||||||
|
var entry: ZipEntry? = zin.nextEntry
|
||||||
|
while (entry != null) {
|
||||||
|
if (off < 0) {
|
||||||
|
off = entry.name.indexOf('/') + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
val path = entry.name.substring(off)
|
||||||
|
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
|
||||||
|
zout.putNextEntry(ZipEntry(path))
|
||||||
|
if (!entry.isDirectory) {
|
||||||
|
zin.copyTo(zout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = zin.nextEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,86 @@
|
|||||||
|
package com.topjohnwu.magisk.model.download
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.IBinder
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.random.Random.Default.nextInt
|
||||||
|
|
||||||
|
abstract class NotificationService : Service() {
|
||||||
|
|
||||||
|
abstract val defaultNotification: NotificationCompat.Builder
|
||||||
|
|
||||||
|
private val manager by lazy { NotificationManagerCompat.from(this) }
|
||||||
|
private val hasNotifications get() = notifications.isNotEmpty()
|
||||||
|
|
||||||
|
private val notifications =
|
||||||
|
Collections.synchronizedMap(mutableMapOf<Int, NotificationCompat.Builder>())
|
||||||
|
|
||||||
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||||
|
super.onTaskRemoved(rootIntent)
|
||||||
|
notifications.forEach { cancel(it.key) }
|
||||||
|
notifications.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --
|
||||||
|
|
||||||
|
fun update(
|
||||||
|
id: Int,
|
||||||
|
body: (NotificationCompat.Builder) -> Unit = {}
|
||||||
|
) {
|
||||||
|
val notification = notifications.getOrPut(id) { defaultNotification }
|
||||||
|
|
||||||
|
notify(id, notification.also(body).build())
|
||||||
|
|
||||||
|
if (notifications.size == 1) {
|
||||||
|
updateForeground()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun finishNotify(
|
||||||
|
id: Int,
|
||||||
|
editBody: (NotificationCompat.Builder) -> NotificationCompat.Builder? = { null }
|
||||||
|
) : Int {
|
||||||
|
val currentNotification = remove(id)?.run(editBody)
|
||||||
|
|
||||||
|
var newId = -1
|
||||||
|
currentNotification?.let {
|
||||||
|
newId = nextInt(Int.MAX_VALUE)
|
||||||
|
notify(newId, it.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasNotifications) {
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
return newId
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
private fun notify(id: Int, notification: Notification) {
|
||||||
|
manager.notify(id, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancel(id: Int) {
|
||||||
|
manager.cancel(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun remove(id: Int) = notifications.remove(id).also {
|
||||||
|
cancel(id)
|
||||||
|
updateForeground()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateForeground() {
|
||||||
|
if (hasNotifications)
|
||||||
|
startForeground(notifications.keys.first(), notifications.values.first().build())
|
||||||
|
else
|
||||||
|
stopForeground(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --
|
||||||
|
|
||||||
|
override fun onBind(p0: Intent?): IBinder? = null
|
||||||
|
}
|
@@ -0,0 +1,118 @@
|
|||||||
|
package com.topjohnwu.magisk.model.download
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import com.skoumal.teanity.extensions.subscribeK
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
|
import com.topjohnwu.magisk.di.NullActivity
|
||||||
|
import com.topjohnwu.magisk.extensions.get
|
||||||
|
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.utils.ProgressInputStream
|
||||||
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
|
import io.reactivex.Completable
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
abstract class RemoteFileService : NotificationService() {
|
||||||
|
|
||||||
|
private val service: GithubRawServices by inject()
|
||||||
|
|
||||||
|
override val defaultNotification: NotificationCompat.Builder
|
||||||
|
get() = Notifications.progress(this, "")
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
intent?.getParcelableExtra<DownloadSubject>(ARG_URL)?.let { start(it) }
|
||||||
|
return START_REDELIVER_INTENT
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
private fun start(subject: DownloadSubject) = checkExisting(subject)
|
||||||
|
.onErrorResumeNext { download(subject) }
|
||||||
|
.doOnSubscribe { update(subject.hashCode()) { it.setContentTitle(subject.title) } }
|
||||||
|
.subscribeK(onError = {
|
||||||
|
Timber.e(it)
|
||||||
|
finishNotify(subject.hashCode()) { notification ->
|
||||||
|
notification.setContentText(getString(R.string.download_file_error))
|
||||||
|
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||||
|
.setOngoing(false)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
val newId = finishNotify(subject)
|
||||||
|
if (get<Activity>() !is NullActivity) {
|
||||||
|
onFinished(subject, newId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkExisting(subject: DownloadSubject) = Completable.fromAction {
|
||||||
|
check(subject is Magisk) { "Download cache is disabled" }
|
||||||
|
|
||||||
|
subject.file.also {
|
||||||
|
check(it.exists() && ShellUtils.checkSum("MD5", it, subject.magisk.md5)) {
|
||||||
|
"The given file does not match checksum"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun download(subject: DownloadSubject) = service.fetchFile(subject.url)
|
||||||
|
.map { it.toStream(subject.hashCode()) }
|
||||||
|
.flatMapCompletable { stream ->
|
||||||
|
when (subject) {
|
||||||
|
is Module -> service.fetchInstaller()
|
||||||
|
.doOnSuccess { stream.toModule(subject.file, it.byteStream()) }
|
||||||
|
.ignoreElement()
|
||||||
|
else -> Completable.fromAction { stream.writeTo(subject.file) }
|
||||||
|
}
|
||||||
|
}.doOnComplete {
|
||||||
|
if (subject is Manager)
|
||||||
|
handleAPK(subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ResponseBody.toStream(id: Int): InputStream {
|
||||||
|
val maxRaw = contentLength()
|
||||||
|
val max = maxRaw / 1_000_000f
|
||||||
|
|
||||||
|
return ProgressInputStream(byteStream()) {
|
||||||
|
val progress = it / 1_000_000f
|
||||||
|
update(id) { notification ->
|
||||||
|
if (maxRaw > 0) {
|
||||||
|
notification
|
||||||
|
.setProgress(maxRaw.toInt(), it.toInt(), false)
|
||||||
|
.setContentText("%.2f / %.2f MB".format(progress, max))
|
||||||
|
} else {
|
||||||
|
notification.setContentText("%.2f MB / ??".format(progress))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun finishNotify(subject: DownloadSubject) = finishNotify(subject.hashCode()) {
|
||||||
|
it.addActions(subject)
|
||||||
|
.setContentText(getString(R.string.download_complete))
|
||||||
|
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
|
.setProgress(0, 0, false)
|
||||||
|
.setOngoing(false)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
|
||||||
|
@Throws(Throwable::class)
|
||||||
|
protected abstract fun onFinished(subject: DownloadSubject, id: Int)
|
||||||
|
|
||||||
|
protected abstract fun NotificationCompat.Builder.addActions(subject: DownloadSubject)
|
||||||
|
: NotificationCompat.Builder
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ARG_URL = "arg_url"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,144 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.model.entity;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
public abstract class BaseModule implements Comparable<BaseModule>, Parcelable {
|
|
||||||
|
|
||||||
private String mId, mName, mVersion, mAuthor, mDescription;
|
|
||||||
private int mVersionCode = -1;
|
|
||||||
|
|
||||||
protected BaseModule() {
|
|
||||||
mId = mName = mVersion = mAuthor = mDescription = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected BaseModule(Cursor c) {
|
|
||||||
mId = nonNull(c.getString(c.getColumnIndex("id")));
|
|
||||||
mName = nonNull(c.getString(c.getColumnIndex("name")));
|
|
||||||
mVersion = nonNull(c.getString(c.getColumnIndex("version")));
|
|
||||||
mVersionCode = c.getInt(c.getColumnIndex("versionCode"));
|
|
||||||
mAuthor = nonNull(c.getString(c.getColumnIndex("author")));
|
|
||||||
mDescription = nonNull(c.getString(c.getColumnIndex("description")));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected BaseModule(Parcel p) {
|
|
||||||
mId = p.readString();
|
|
||||||
mName = p.readString();
|
|
||||||
mVersion = p.readString();
|
|
||||||
mAuthor = p.readString();
|
|
||||||
mDescription = p.readString();
|
|
||||||
mVersionCode = p.readInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(@NonNull BaseModule module) {
|
|
||||||
return this.getName().toLowerCase().compareTo(module.getName().toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeString(mId);
|
|
||||||
dest.writeString(mName);
|
|
||||||
dest.writeString(mVersion);
|
|
||||||
dest.writeString(mAuthor);
|
|
||||||
dest.writeString(mDescription);
|
|
||||||
dest.writeInt(mVersionCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String nonNull(String s) {
|
|
||||||
return s == null ? "" : s;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContentValues getContentValues() {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put("id", mId);
|
|
||||||
values.put("name", mName);
|
|
||||||
values.put("version", mVersion);
|
|
||||||
values.put("versionCode", mVersionCode);
|
|
||||||
values.put("author", mAuthor);
|
|
||||||
values.put("description", mDescription);
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void parseProps(List<String> props) { parseProps(props.toArray(new String[0])); }
|
|
||||||
|
|
||||||
protected void parseProps(String[] props) throws NumberFormatException {
|
|
||||||
for (String line : props) {
|
|
||||||
String[] prop = line.split("=", 2);
|
|
||||||
if (prop.length != 2)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
String key = prop[0].trim();
|
|
||||||
String value = prop[1].trim();
|
|
||||||
if (key.isEmpty() || key.charAt(0) == '#')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case "id":
|
|
||||||
mId = value;
|
|
||||||
break;
|
|
||||||
case "name":
|
|
||||||
mName = value;
|
|
||||||
break;
|
|
||||||
case "version":
|
|
||||||
mVersion = value;
|
|
||||||
break;
|
|
||||||
case "versionCode":
|
|
||||||
mVersionCode = Integer.parseInt(value);
|
|
||||||
break;
|
|
||||||
case "author":
|
|
||||||
mAuthor = value;
|
|
||||||
break;
|
|
||||||
case "description":
|
|
||||||
mDescription = value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return mName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
mName = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVersion() {
|
|
||||||
return mVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAuthor() {
|
|
||||||
return mAuthor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return mId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(String id) {
|
|
||||||
mId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return mDescription;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getVersionCode() {
|
|
||||||
return mVersionCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -2,8 +2,8 @@ package com.topjohnwu.magisk.model.entity
|
|||||||
|
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import com.topjohnwu.magisk.utils.packageInfo
|
import com.topjohnwu.magisk.extensions.packageInfo
|
||||||
import com.topjohnwu.magisk.utils.processes
|
import com.topjohnwu.magisk.extensions.processes
|
||||||
|
|
||||||
class HideAppInfo(
|
class HideAppInfo(
|
||||||
val info: ApplicationInfo,
|
val info: ApplicationInfo,
|
||||||
|
@@ -0,0 +1,57 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.extensions.timeFormatTime
|
||||||
|
import com.topjohnwu.magisk.extensions.toTime
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.ALLOW
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
data class MagiskLog(
|
||||||
|
val fromUid: Int,
|
||||||
|
val toUid: Int,
|
||||||
|
val fromPid: Int,
|
||||||
|
val packageName: String,
|
||||||
|
val appName: String,
|
||||||
|
val command: String,
|
||||||
|
val action: Boolean,
|
||||||
|
val date: Date
|
||||||
|
) {
|
||||||
|
val timeString = date.time.toTime(timeFormatTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class WrappedMagiskLog(
|
||||||
|
val time: Long,
|
||||||
|
val items: List<MagiskLog>
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Map<String, String>.toLog(): MagiskLog {
|
||||||
|
return MagiskLog(
|
||||||
|
fromUid = get("from_uid")?.toIntOrNull() ?: -1,
|
||||||
|
toUid = get("to_uid")?.toIntOrNull() ?: -1,
|
||||||
|
fromPid = get("from_pid")?.toIntOrNull() ?: -1,
|
||||||
|
packageName = get("package_name").orEmpty(),
|
||||||
|
appName = get("app_name").orEmpty(),
|
||||||
|
command = get("command").orEmpty(),
|
||||||
|
action = get("action")?.toIntOrNull() != 0,
|
||||||
|
date = get("time")?.toLongOrNull()?.toDate() ?: Date()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Long.toDate() = Date(this)
|
||||||
|
|
||||||
|
fun MagiskLog.toMap() = mapOf(
|
||||||
|
"from_uid" to fromUid,
|
||||||
|
"to_uid" to toUid,
|
||||||
|
"from_pid" to fromPid,
|
||||||
|
"package_name" to packageName,
|
||||||
|
"app_name" to appName,
|
||||||
|
"command" to command,
|
||||||
|
"action" to action,
|
||||||
|
"time" to date.time
|
||||||
|
)
|
||||||
|
|
||||||
|
fun MagiskPolicy.toLog(
|
||||||
|
toUid: Int,
|
||||||
|
fromPid: Int,
|
||||||
|
command: String,
|
||||||
|
date: Date
|
||||||
|
) = MagiskLog(uid, toUid, fromPid, packageName, appName, command, policy == ALLOW, date)
|
@@ -0,0 +1,69 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import com.topjohnwu.magisk.extensions.getLabel
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.INTERACTIVE
|
||||||
|
|
||||||
|
|
||||||
|
data class MagiskPolicy(
|
||||||
|
val uid: Int,
|
||||||
|
val packageName: String,
|
||||||
|
val appName: String,
|
||||||
|
val policy: Int = INTERACTIVE,
|
||||||
|
val until: Long = -1L,
|
||||||
|
val logging: Boolean = true,
|
||||||
|
val notification: Boolean = true,
|
||||||
|
val applicationInfo: ApplicationInfo
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val INTERACTIVE = 0
|
||||||
|
const val DENY = 1
|
||||||
|
const val ALLOW = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MagiskPolicy.toMap() = mapOf(
|
||||||
|
"uid" to uid,
|
||||||
|
"package_name" to packageName,
|
||||||
|
"policy" to policy,
|
||||||
|
"until" to until,
|
||||||
|
"logging" to logging,
|
||||||
|
"notification" to notification
|
||||||
|
)
|
||||||
|
|
||||||
|
@Throws(PackageManager.NameNotFoundException::class)
|
||||||
|
fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
|
||||||
|
val uid = get("uid")?.toIntOrNull() ?: -1
|
||||||
|
val packageName = get("package_name").orEmpty()
|
||||||
|
val info = pm.getApplicationInfo(packageName, 0)
|
||||||
|
|
||||||
|
if (info.uid != uid)
|
||||||
|
throw PackageManager.NameNotFoundException()
|
||||||
|
|
||||||
|
return MagiskPolicy(
|
||||||
|
uid = uid,
|
||||||
|
packageName = packageName,
|
||||||
|
policy = get("policy")?.toIntOrNull() ?: INTERACTIVE,
|
||||||
|
until = get("until")?.toLongOrNull() ?: -1L,
|
||||||
|
logging = get("logging")?.toIntOrNull() != 0,
|
||||||
|
notification = get("notification")?.toIntOrNull() != 0,
|
||||||
|
applicationInfo = info,
|
||||||
|
appName = info.getLabel(pm)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(PackageManager.NameNotFoundException::class)
|
||||||
|
fun Int.toPolicy(pm: PackageManager): MagiskPolicy {
|
||||||
|
val pkg = pm.getPackagesForUid(this)?.firstOrNull()
|
||||||
|
?: throw PackageManager.NameNotFoundException()
|
||||||
|
val info = pm.getApplicationInfo(pkg, 0)
|
||||||
|
return MagiskPolicy(
|
||||||
|
uid = this,
|
||||||
|
packageName = pkg,
|
||||||
|
applicationInfo = info,
|
||||||
|
appName = info.loadLabel(pm).toString()
|
||||||
|
)
|
||||||
|
}
|
@@ -1,79 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.model.entity;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
import com.topjohnwu.superuser.Shell;
|
|
||||||
import com.topjohnwu.superuser.io.SuFile;
|
|
||||||
|
|
||||||
public class Module extends BaseModule {
|
|
||||||
|
|
||||||
private SuFile mRemoveFile, mDisableFile, mUpdateFile;
|
|
||||||
private boolean mEnable, mRemove, mUpdated;
|
|
||||||
|
|
||||||
public Module(String path) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
parseProps(Shell.su("dos2unix < " + path + "/module.prop").exec().getOut());
|
|
||||||
} catch (NumberFormatException ignored) {}
|
|
||||||
|
|
||||||
mRemoveFile = new SuFile(path, "remove");
|
|
||||||
mDisableFile = new SuFile(path, "disable");
|
|
||||||
mUpdateFile = new SuFile(path, "update");
|
|
||||||
|
|
||||||
if (getId().isEmpty()) {
|
|
||||||
int sep = path.lastIndexOf('/');
|
|
||||||
setId(path.substring(sep + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getName().isEmpty()) {
|
|
||||||
setName(getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
mEnable = !mDisableFile.exists();
|
|
||||||
mRemove = mRemoveFile.exists();
|
|
||||||
mUpdated = mUpdateFile.exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Parcelable.Creator<Module> CREATOR = new Creator<Module>() {
|
|
||||||
/* It won't be used at any place */
|
|
||||||
@Override
|
|
||||||
public Module createFromParcel(Parcel source) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Module[] newArray(int size) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public void createDisableFile() {
|
|
||||||
mEnable = !mDisableFile.createNewFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeDisableFile() {
|
|
||||||
mEnable = mDisableFile.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return mEnable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void createRemoveFile() {
|
|
||||||
mRemove = mRemoveFile.createNewFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteRemoveFile() {
|
|
||||||
mRemove = !mRemoveFile.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean willBeRemoved() {
|
|
||||||
return mRemove;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUpdated() {
|
|
||||||
return mUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,61 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.model.entity;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
|
|
||||||
public class Policy implements Comparable<Policy>{
|
|
||||||
public static final int INTERACTIVE = 0;
|
|
||||||
public static final int DENY = 1;
|
|
||||||
public static final int ALLOW = 2;
|
|
||||||
|
|
||||||
public int uid, policy = INTERACTIVE;
|
|
||||||
public long until;
|
|
||||||
public boolean logging = true, notification = true;
|
|
||||||
public String packageName, appName;
|
|
||||||
public ApplicationInfo info;
|
|
||||||
|
|
||||||
public Policy(int uid, PackageManager pm) throws PackageManager.NameNotFoundException {
|
|
||||||
String[] pkgs = pm.getPackagesForUid(uid);
|
|
||||||
if (pkgs == null || pkgs.length == 0)
|
|
||||||
throw new PackageManager.NameNotFoundException();
|
|
||||||
this.uid = uid;
|
|
||||||
packageName = pkgs[0];
|
|
||||||
info = pm.getApplicationInfo(packageName, 0);
|
|
||||||
appName = Utils.getAppLabel(info, pm);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Policy(ContentValues values, PackageManager pm) throws PackageManager.NameNotFoundException {
|
|
||||||
uid = values.getAsInteger("uid");
|
|
||||||
packageName = values.getAsString("package_name");
|
|
||||||
policy = values.getAsInteger("policy");
|
|
||||||
until = values.getAsInteger("until");
|
|
||||||
logging = values.getAsInteger("logging") != 0;
|
|
||||||
notification = values.getAsInteger("notification") != 0;
|
|
||||||
info = pm.getApplicationInfo(packageName, 0);
|
|
||||||
if (info.uid != uid)
|
|
||||||
throw new PackageManager.NameNotFoundException();
|
|
||||||
appName = info.loadLabel(pm).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContentValues getContentValues() {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put("uid", uid);
|
|
||||||
values.put("package_name", packageName);
|
|
||||||
values.put("policy", policy);
|
|
||||||
values.put("until", until);
|
|
||||||
values.put("logging", logging ? 1 : 0);
|
|
||||||
values.put("notification", notification ? 1 : 0);
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(@NonNull Policy policy) {
|
|
||||||
return appName.toLowerCase().compareTo(policy.appName.toLowerCase());
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,109 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.model.entity;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Const;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class Repo extends BaseModule {
|
|
||||||
|
|
||||||
private Date mLastUpdate;
|
|
||||||
|
|
||||||
public Repo(String id) {
|
|
||||||
setId(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Repo(Cursor c) {
|
|
||||||
super(c);
|
|
||||||
mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update")));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Repo(Parcel p) {
|
|
||||||
super(p);
|
|
||||||
mLastUpdate = new Date(p.readLong());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Parcelable.Creator<Repo> CREATOR = new Parcelable.Creator<Repo>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Repo createFromParcel(Parcel source) {
|
|
||||||
return new Repo(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Repo[] newArray(int size) {
|
|
||||||
return new Repo[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
super.writeToParcel(dest, flags);
|
|
||||||
dest.writeLong(mLastUpdate.getTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update() throws IllegalRepoException {
|
|
||||||
String props[] = Utils.dlString(getPropUrl()).split("\\n");
|
|
||||||
try {
|
|
||||||
parseProps(props);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw new IllegalRepoException("Repo [" + getId() + "] parse error: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getVersionCode() < 0) {
|
|
||||||
throw new IllegalRepoException("Repo [" + getId() + "] does not contain versionCode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update(Date lastUpdate) throws IllegalRepoException {
|
|
||||||
mLastUpdate = lastUpdate;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ContentValues getContentValues() {
|
|
||||||
ContentValues values = super.getContentValues();
|
|
||||||
values.put("last_update", mLastUpdate.getTime());
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getZipUrl() {
|
|
||||||
return String.format(Const.Url.ZIP_URL, getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPropUrl() {
|
|
||||||
return getFileUrl("module.prop");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDetailUrl() {
|
|
||||||
return getFileUrl("README.md");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFileUrl(String file) {
|
|
||||||
return String.format(Const.Url.FILE_URL, getId(), file);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastUpdateString() {
|
|
||||||
return DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(mLastUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getLastUpdate() {
|
|
||||||
return mLastUpdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDownloadFilename() {
|
|
||||||
return Utils.getLegalFilename(getName() + "-" + getVersion() + ".zip");
|
|
||||||
}
|
|
||||||
|
|
||||||
public class IllegalRepoException extends Exception {
|
|
||||||
IllegalRepoException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,56 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.model.entity;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
|
||||||
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class SuLogEntry {
|
|
||||||
|
|
||||||
public int fromUid, toUid, fromPid;
|
|
||||||
public String packageName, appName, command;
|
|
||||||
public boolean action;
|
|
||||||
public Date date;
|
|
||||||
|
|
||||||
public SuLogEntry(Policy policy) {
|
|
||||||
fromUid = policy.uid;
|
|
||||||
packageName = policy.packageName;
|
|
||||||
appName = policy.appName;
|
|
||||||
action = policy.policy == Policy.ALLOW;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SuLogEntry(ContentValues values) {
|
|
||||||
fromUid = values.getAsInteger("from_uid");
|
|
||||||
packageName = values.getAsString("package_name");
|
|
||||||
appName = values.getAsString("app_name");
|
|
||||||
fromPid = values.getAsInteger("from_pid");
|
|
||||||
command = values.getAsString("command");
|
|
||||||
toUid = values.getAsInteger("to_uid");
|
|
||||||
action = values.getAsInteger("action") != 0;
|
|
||||||
date = new Date(values.getAsLong("time"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContentValues getContentValues() {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put("from_uid", fromUid);
|
|
||||||
values.put("package_name", packageName);
|
|
||||||
values.put("app_name", appName);
|
|
||||||
values.put("from_pid", fromPid);
|
|
||||||
values.put("command", command);
|
|
||||||
values.put("to_uid", toUid);
|
|
||||||
values.put("action", action ? 1 : 0);
|
|
||||||
values.put("time", date.getTime());
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDateString() {
|
|
||||||
return DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTimeString() {
|
|
||||||
return new SimpleDateFormat("h:mm a", LocaleManager.locale).format(date);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,35 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import se.ansman.kotshi.JsonSerializable
|
||||||
|
|
||||||
|
@JsonSerializable
|
||||||
|
data class UpdateInfo(
|
||||||
|
val app: ManagerJson = ManagerJson(),
|
||||||
|
val uninstaller: UninstallerJson = UninstallerJson(),
|
||||||
|
val magisk: MagiskJson = MagiskJson()
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonSerializable
|
||||||
|
data class UninstallerJson(
|
||||||
|
val link: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonSerializable
|
||||||
|
data class MagiskJson(
|
||||||
|
val version: String = "",
|
||||||
|
val versionCode: Int = -1,
|
||||||
|
val link: String = "",
|
||||||
|
val note: String = "",
|
||||||
|
val md5: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@JsonSerializable
|
||||||
|
data class ManagerJson(
|
||||||
|
val version: String = "",
|
||||||
|
val versionCode: Int = -1,
|
||||||
|
val link: String = "",
|
||||||
|
val note: String = ""
|
||||||
|
) : Parcelable
|
@@ -0,0 +1,3 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
data class Version(val version: String, val versionCode: Int)
|
@@ -0,0 +1,37 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity.internal
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
sealed class Configuration : Parcelable {
|
||||||
|
|
||||||
|
sealed class Flash : Configuration() {
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
object Primary : Flash()
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
object Secondary : Flash()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class APK : Configuration() {
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
object Upgrade : APK()
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
object Restore : APK()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
object Download : Configuration()
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
object Uninstall : Configuration()
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Patch(val fileUri: Uri) : Configuration()
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,106 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity.internal
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.Info
|
||||||
|
import com.topjohnwu.magisk.extensions.cachedFile
|
||||||
|
import com.topjohnwu.magisk.extensions.get
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskJson
|
||||||
|
import com.topjohnwu.magisk.model.entity.ManagerJson
|
||||||
|
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||||
|
import kotlinx.android.parcel.IgnoredOnParcel
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
sealed class DownloadSubject : Parcelable {
|
||||||
|
|
||||||
|
abstract val url: String
|
||||||
|
abstract val file: File
|
||||||
|
open val title: String get() = file.name
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Module(
|
||||||
|
val module: Repo,
|
||||||
|
val configuration: Configuration
|
||||||
|
) : DownloadSubject() {
|
||||||
|
override val url: String get() = module.zipUrl
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
override val file by lazy {
|
||||||
|
File(Config.downloadDirectory, module.downloadFilename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Manager(
|
||||||
|
val configuration: Configuration.APK
|
||||||
|
) : DownloadSubject() {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val manager: ManagerJson = Info.remote.app
|
||||||
|
|
||||||
|
override val title: String
|
||||||
|
get() = "MagiskManager-v${manager.version}(${manager.versionCode})"
|
||||||
|
|
||||||
|
override val url: String
|
||||||
|
get() = manager.link
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
override val file by lazy {
|
||||||
|
get<Context>().cachedFile("manager.apk")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Magisk : DownloadSubject() {
|
||||||
|
|
||||||
|
abstract val configuration: Configuration
|
||||||
|
val magisk: MagiskJson = Info.remote.magisk
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
protected data class Flash(
|
||||||
|
override val configuration: Configuration
|
||||||
|
) : Magisk() {
|
||||||
|
override val url: String get() = magisk.link
|
||||||
|
override val title: String get() = "Magisk-v${magisk.version}(${magisk.versionCode})"
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
override val file by lazy {
|
||||||
|
get<Context>().cachedFile("magisk.zip")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
protected class Uninstall : Magisk() {
|
||||||
|
override val configuration: Configuration get() = Configuration.Uninstall
|
||||||
|
override val url: String get() = Info.remote.uninstaller.link
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
override val file by lazy {
|
||||||
|
get<Context>().cachedFile("uninstall.zip")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
protected class Download : Magisk() {
|
||||||
|
override val configuration: Configuration get() = Configuration.Download
|
||||||
|
override val url: String get() = magisk.link
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
override val file by lazy {
|
||||||
|
File(Config.downloadDirectory, "Magisk-v${magisk.version}(${magisk.versionCode}).zip")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
operator fun invoke(configuration: Configuration) = when (configuration) {
|
||||||
|
Configuration.Download -> Download()
|
||||||
|
Configuration.Uninstall -> Uninstall()
|
||||||
|
else -> Flash(configuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,41 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity.module
|
||||||
|
|
||||||
|
abstract class BaseModule : Comparable<BaseModule> {
|
||||||
|
abstract var id: String
|
||||||
|
protected set
|
||||||
|
abstract var name: String
|
||||||
|
protected set
|
||||||
|
abstract var author: String
|
||||||
|
protected set
|
||||||
|
abstract var version: String
|
||||||
|
protected set
|
||||||
|
abstract var versionCode: Int
|
||||||
|
protected set
|
||||||
|
abstract var description: String
|
||||||
|
protected set
|
||||||
|
|
||||||
|
@Throws(NumberFormatException::class)
|
||||||
|
protected fun parseProps(props: List<String>) {
|
||||||
|
for (line in props) {
|
||||||
|
val prop = line.split("=".toRegex(), 2).map { it.trim() }
|
||||||
|
if (prop.size != 2)
|
||||||
|
continue
|
||||||
|
|
||||||
|
val key = prop[0]
|
||||||
|
val value = prop[1]
|
||||||
|
if (key.isEmpty() || key[0] == '#')
|
||||||
|
continue
|
||||||
|
|
||||||
|
when (key) {
|
||||||
|
"id" -> id = value
|
||||||
|
"name" -> name = value
|
||||||
|
"version" -> version = value
|
||||||
|
"versionCode" -> versionCode = value.toInt()
|
||||||
|
"author" -> author = value
|
||||||
|
"description" -> description = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun compareTo(other: BaseModule) = name.compareTo(other.name, true)
|
||||||
|
}
|
@@ -0,0 +1,71 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity.module
|
||||||
|
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import com.topjohnwu.magisk.Const
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
|
|
||||||
|
class Module(path: String) : BaseModule() {
|
||||||
|
override var id: String = ""
|
||||||
|
override var name: String = ""
|
||||||
|
override var author: String = ""
|
||||||
|
override var version: String = ""
|
||||||
|
override var versionCode: Int = -1
|
||||||
|
override var description: String = ""
|
||||||
|
|
||||||
|
private val removeFile: SuFile = SuFile(path, "remove")
|
||||||
|
private val disableFile: SuFile = SuFile(path, "disable")
|
||||||
|
private val updateFile: SuFile = SuFile(path, "update")
|
||||||
|
|
||||||
|
val updated: Boolean = updateFile.exists()
|
||||||
|
|
||||||
|
var enable: Boolean = !disableFile.exists()
|
||||||
|
set(enable) {
|
||||||
|
field = if (enable) {
|
||||||
|
disableFile.delete()
|
||||||
|
} else {
|
||||||
|
!disableFile.createNewFile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var remove: Boolean = removeFile.exists()
|
||||||
|
set(remove) {
|
||||||
|
field = if (remove) {
|
||||||
|
removeFile.createNewFile()
|
||||||
|
} else {
|
||||||
|
!removeFile.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
runCatching {
|
||||||
|
parseProps(Shell.su("dos2unix < $path/module.prop").exec().out)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.isEmpty()) {
|
||||||
|
val sep = path.lastIndexOf('/')
|
||||||
|
id = path.substring(sep + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
name = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun loadModules(): List<Module> {
|
||||||
|
val moduleList = mutableListOf<Module>()
|
||||||
|
val path = SuFile(Const.MAGISK_PATH)
|
||||||
|
val modules =
|
||||||
|
path.listFiles { _, name -> name != "lost+found" && name != ".core" }.orEmpty()
|
||||||
|
for (file in modules) {
|
||||||
|
if (file.isFile) continue
|
||||||
|
val module = Module(Const.MAGISK_PATH + "/" + file.name)
|
||||||
|
moduleList.add(module)
|
||||||
|
}
|
||||||
|
return moduleList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,71 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity.module
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.topjohnwu.magisk.Const
|
||||||
|
import com.topjohnwu.magisk.data.repository.StringRepository
|
||||||
|
import com.topjohnwu.magisk.extensions.get
|
||||||
|
import com.topjohnwu.magisk.extensions.legalFilename
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Entity(tableName = "repos")
|
||||||
|
@Parcelize
|
||||||
|
data class Repo(
|
||||||
|
@PrimaryKey override var id: String,
|
||||||
|
override var name: String,
|
||||||
|
override var author: String,
|
||||||
|
override var version: String,
|
||||||
|
override var versionCode: Int,
|
||||||
|
override var description: String,
|
||||||
|
var last_update: Long
|
||||||
|
) : BaseModule(), Parcelable {
|
||||||
|
|
||||||
|
private val stringRepo: StringRepository get() = get()
|
||||||
|
|
||||||
|
val lastUpdate get() = Date(last_update)
|
||||||
|
|
||||||
|
val lastUpdateString: String get() = dateFormat.format(lastUpdate)
|
||||||
|
|
||||||
|
val downloadFilename: String get() = "$name-$version($versionCode).zip".legalFilename()
|
||||||
|
|
||||||
|
val readme get() = stringRepo.getReadme(this)
|
||||||
|
|
||||||
|
val zipUrl: String get() = Const.Url.ZIP_URL.format(id)
|
||||||
|
|
||||||
|
constructor(id: String) : this(id, "", "", "", -1, "", 0)
|
||||||
|
|
||||||
|
@Throws(IllegalRepoException::class)
|
||||||
|
fun update() {
|
||||||
|
val props = runCatching {
|
||||||
|
stringRepo.getMetadata(this).blockingGet()
|
||||||
|
.orEmpty().split("\\n".toRegex()).dropLastWhile { it.isEmpty() }
|
||||||
|
}.getOrElse {
|
||||||
|
throw IllegalRepoException("Repo [$id] module.prop download error: " + it.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
props.runCatching {
|
||||||
|
parseProps(this)
|
||||||
|
}.onFailure {
|
||||||
|
throw IllegalRepoException("Repo [$id] parse error: " + it.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (versionCode < 0) {
|
||||||
|
throw IllegalRepoException("Repo [$id] does not contain versionCode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IllegalRepoException::class)
|
||||||
|
fun update(lastUpdate: Date) {
|
||||||
|
last_update = lastUpdate.time
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
|
class IllegalRepoException(message: String) : Exception(message)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM)!!
|
||||||
|
}
|
||||||
|
}
|
@@ -1,11 +1,26 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.recycler
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
|
||||||
class ConsoleRvItem(val item: String) : ComparableRvItem<ConsoleRvItem>() {
|
class ConsoleRvItem(val item: String) : LenientRvItem<ConsoleRvItem>() {
|
||||||
override val layoutRes: Int = R.layout.item_console
|
override val layoutRes: Int = R.layout.item_console
|
||||||
|
|
||||||
|
override fun onBindingBound(binding: ViewDataBinding, recyclerView: RecyclerView) {
|
||||||
|
val view = binding.root as TextView
|
||||||
|
view.measure(0, 0)
|
||||||
|
val desiredWidth = view.measuredWidth
|
||||||
|
|
||||||
|
view.updateLayoutParams { width = desiredWidth }
|
||||||
|
|
||||||
|
if (recyclerView.width < desiredWidth) {
|
||||||
|
recyclerView.requestLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun contentSameAs(other: ConsoleRvItem) = itemSameAs(other)
|
override fun contentSameAs(other: ConsoleRvItem) = itemSameAs(other)
|
||||||
override fun itemSameAs(other: ConsoleRvItem) = item == other.item
|
override fun itemSameAs(other: ConsoleRvItem) = item == other.item
|
||||||
}
|
}
|
@@ -6,12 +6,12 @@ import com.skoumal.teanity.rxbus.RxBus
|
|||||||
import com.skoumal.teanity.util.DiffObservableList
|
import com.skoumal.teanity.util.DiffObservableList
|
||||||
import com.skoumal.teanity.util.KObservableField
|
import com.skoumal.teanity.util.KObservableField
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
|
import com.topjohnwu.magisk.extensions.toggle
|
||||||
import com.topjohnwu.magisk.model.entity.HideAppInfo
|
import com.topjohnwu.magisk.model.entity.HideAppInfo
|
||||||
import com.topjohnwu.magisk.model.entity.HideTarget
|
import com.topjohnwu.magisk.model.entity.HideTarget
|
||||||
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
||||||
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
||||||
import com.topjohnwu.magisk.utils.inject
|
|
||||||
import com.topjohnwu.magisk.utils.toggle
|
|
||||||
|
|
||||||
class HideRvItem(val item: HideAppInfo, targets: List<HideTarget>) :
|
class HideRvItem(val item: HideAppInfo, targets: List<HideTarget>) :
|
||||||
ComparableRvItem<HideRvItem>() {
|
ComparableRvItem<HideRvItem>() {
|
||||||
|
@@ -0,0 +1,16 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This item addresses issues where enclosing recycler has to be invalidated or generally
|
||||||
|
* manipulated with. This shouldn't be however necessary for 99.9% of use-cases. Refrain from using
|
||||||
|
* this item as it provides virtually no additional functionality. Stick with ComparableRvItem.
|
||||||
|
* */
|
||||||
|
abstract class LenientRvItem<in T> : ComparableRvItem<T>() {
|
||||||
|
|
||||||
|
open fun onBindingBound(binding: ViewDataBinding, recyclerView: RecyclerView) {}
|
||||||
|
|
||||||
|
}
|
@@ -1,12 +1,14 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.recycler
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
import androidx.databinding.ObservableList
|
|
||||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||||
import com.skoumal.teanity.util.DiffObservableList
|
import com.skoumal.teanity.util.DiffObservableList
|
||||||
import com.skoumal.teanity.util.KObservableField
|
import com.skoumal.teanity.util.KObservableField
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.model.entity.SuLogEntry
|
import com.topjohnwu.magisk.extensions.timeFormatMedium
|
||||||
import com.topjohnwu.magisk.utils.toggle
|
import com.topjohnwu.magisk.extensions.toTime
|
||||||
|
import com.topjohnwu.magisk.extensions.toggle
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||||
|
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
|
||||||
|
|
||||||
class LogRvItem : ComparableRvItem<LogRvItem>() {
|
class LogRvItem : ComparableRvItem<LogRvItem>() {
|
||||||
override val layoutRes: Int = R.layout.item_page_log
|
override val layoutRes: Int = R.layout.item_page_log
|
||||||
@@ -25,12 +27,12 @@ class LogRvItem : ComparableRvItem<LogRvItem>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LogItemRvItem(
|
class LogItemRvItem(
|
||||||
val items: ObservableList<ComparableRvItem<*>>
|
item: WrappedMagiskLog
|
||||||
) : ComparableRvItem<LogItemRvItem>() {
|
) : ComparableRvItem<LogItemRvItem>() {
|
||||||
override val layoutRes: Int = R.layout.item_superuser_log
|
override val layoutRes: Int = R.layout.item_superuser_log
|
||||||
|
|
||||||
val date = items.filterIsInstance<LogItemEntryRvItem>().firstOrNull()
|
val date = item.time.toTime(timeFormatMedium)
|
||||||
?.item?.dateString.orEmpty()
|
val items: List<ComparableRvItem<*>> = item.items.map { LogItemEntryRvItem(it) }
|
||||||
val isExpanded = KObservableField(false)
|
val isExpanded = KObservableField(false)
|
||||||
|
|
||||||
fun toggle() = isExpanded.toggle()
|
fun toggle() = isExpanded.toggle()
|
||||||
@@ -41,7 +43,7 @@ class LogItemRvItem(
|
|||||||
override fun itemSameAs(other: LogItemRvItem): Boolean = date == other.date
|
override fun itemSameAs(other: LogItemRvItem): Boolean = date == other.date
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogItemEntryRvItem(val item: SuLogEntry) : ComparableRvItem<LogItemEntryRvItem>() {
|
class LogItemEntryRvItem(val item: MagiskLog) : ComparableRvItem<LogItemEntryRvItem>() {
|
||||||
override val layoutRes: Int = R.layout.item_superuser_log_entry
|
override val layoutRes: Int = R.layout.item_superuser_log_entry
|
||||||
|
|
||||||
val isExpanded = KObservableField(false)
|
val isExpanded = KObservableField(false)
|
||||||
|
@@ -6,61 +6,70 @@ import com.skoumal.teanity.databinding.ComparableRvItem
|
|||||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
||||||
import com.skoumal.teanity.util.KObservableField
|
import com.skoumal.teanity.util.KObservableField
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.model.entity.Module
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.model.entity.Repo
|
import com.topjohnwu.magisk.extensions.toggle
|
||||||
import com.topjohnwu.magisk.utils.get
|
import com.topjohnwu.magisk.model.entity.module.Module
|
||||||
import com.topjohnwu.magisk.utils.toggle
|
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||||
|
|
||||||
class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
|
class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.item_module
|
override val layoutRes: Int = R.layout.item_module
|
||||||
|
|
||||||
val lastActionNotice = KObservableField("")
|
val lastActionNotice = KObservableField("")
|
||||||
val isChecked = KObservableField(item.isEnabled)
|
val isChecked = KObservableField(item.enable)
|
||||||
val isDeletable = KObservableField(item.willBeRemoved())
|
val isDeletable = KObservableField(item.remove)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
isChecked.addOnPropertyChangedCallback {
|
isChecked.addOnPropertyChangedCallback {
|
||||||
when (it) {
|
when (it) {
|
||||||
true -> item.removeDisableFile().notice(R.string.disable_file_removed)
|
true -> {
|
||||||
false -> item.createDisableFile().notice(R.string.disable_file_created)
|
item.enable = true
|
||||||
|
notice(R.string.disable_file_removed)
|
||||||
|
}
|
||||||
|
false -> {
|
||||||
|
item.enable = false
|
||||||
|
notice(R.string.disable_file_created)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isDeletable.addOnPropertyChangedCallback {
|
isDeletable.addOnPropertyChangedCallback {
|
||||||
when (it) {
|
when (it) {
|
||||||
true -> item.createRemoveFile().notice(R.string.remove_file_created)
|
true -> {
|
||||||
false -> item.deleteRemoveFile().notice(R.string.remove_file_deleted)
|
item.remove = true
|
||||||
|
notice(R.string.remove_file_created)
|
||||||
|
}
|
||||||
|
false -> {
|
||||||
|
item.remove = false
|
||||||
|
notice(R.string.remove_file_deleted)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
when {
|
when {
|
||||||
item.isUpdated -> notice(R.string.update_file_created)
|
item.updated -> notice(R.string.update_file_created)
|
||||||
item.willBeRemoved() -> notice(R.string.remove_file_created)
|
item.remove -> notice(R.string.remove_file_created)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggle() = isChecked.toggle()
|
fun toggle() = isChecked.toggle()
|
||||||
fun toggleDelete() = isDeletable.toggle()
|
fun toggleDelete() = isDeletable.toggle()
|
||||||
|
|
||||||
@Suppress("unused")
|
private fun notice(@StringRes info: Int) {
|
||||||
private fun Any.notice(@StringRes info: Int) {
|
|
||||||
lastActionNotice.value = get<Resources>().getString(info)
|
lastActionNotice.value = get<Resources>().getString(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun contentSameAs(other: ModuleRvItem): Boolean = item.version == other.item.version
|
override fun contentSameAs(other: ModuleRvItem): Boolean = item.version == other.item.version
|
||||||
&& item.versionCode == other.item.versionCode
|
&& item.versionCode == other.item.versionCode
|
||||||
&& item.description == other.item.description
|
&& item.description == other.item.description
|
||||||
|
&& item.name == other.item.name
|
||||||
|
|
||||||
override fun itemSameAs(other: ModuleRvItem): Boolean = item.name == other.item.name
|
override fun itemSameAs(other: ModuleRvItem): Boolean = item.id == other.item.id
|
||||||
}
|
}
|
||||||
|
|
||||||
class RepoRvItem(val item: Repo) : ComparableRvItem<RepoRvItem>() {
|
class RepoRvItem(val item: Repo) : ComparableRvItem<RepoRvItem>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.item_repo
|
override val layoutRes: Int = R.layout.item_repo
|
||||||
|
|
||||||
override fun contentSameAs(other: RepoRvItem): Boolean = item.version == other.item.version
|
override fun contentSameAs(other: RepoRvItem): Boolean = item == other.item
|
||||||
&& item.lastUpdate == other.item.lastUpdate
|
|
||||||
&& item.versionCode == other.item.versionCode
|
|
||||||
&& item.description == other.item.description
|
|
||||||
|
|
||||||
override fun itemSameAs(other: RepoRvItem): Boolean = item.detailUrl == other.item.detailUrl
|
override fun itemSameAs(other: RepoRvItem): Boolean = item.id == other.item.id
|
||||||
}
|
}
|
@@ -6,18 +6,18 @@ import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
|||||||
import com.skoumal.teanity.rxbus.RxBus
|
import com.skoumal.teanity.rxbus.RxBus
|
||||||
import com.skoumal.teanity.util.KObservableField
|
import com.skoumal.teanity.util.KObservableField
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.model.entity.Policy
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
|
import com.topjohnwu.magisk.extensions.toggle
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
|
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
|
||||||
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
|
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
|
||||||
import com.topjohnwu.magisk.utils.inject
|
|
||||||
import com.topjohnwu.magisk.utils.toggle
|
|
||||||
|
|
||||||
class PolicyRvItem(val item: Policy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() {
|
class PolicyRvItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.item_policy
|
override val layoutRes: Int = R.layout.item_policy
|
||||||
|
|
||||||
val isExpanded = KObservableField(false)
|
val isExpanded = KObservableField(false)
|
||||||
val isEnabled = KObservableField(item.policy == Policy.ALLOW)
|
val isEnabled = KObservableField(item.policy == MagiskPolicy.ALLOW)
|
||||||
val shouldNotify = KObservableField(item.notification)
|
val shouldNotify = KObservableField(item.notification)
|
||||||
val shouldLog = KObservableField(item.logging)
|
val shouldLog = KObservableField(item.logging)
|
||||||
|
|
||||||
@@ -25,6 +25,13 @@ class PolicyRvItem(val item: Policy, val icon: Drawable) : ComparableRvItem<Poli
|
|||||||
|
|
||||||
private val rxBus: RxBus by inject()
|
private val rxBus: RxBus by inject()
|
||||||
|
|
||||||
|
private val currentStateItem
|
||||||
|
get() = item.copy(
|
||||||
|
policy = if (isEnabled.value) MagiskPolicy.ALLOW else MagiskPolicy.DENY,
|
||||||
|
notification = shouldNotify.value,
|
||||||
|
logging = shouldLog.value
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
isEnabled.addOnPropertyChangedCallback {
|
isEnabled.addOnPropertyChangedCallback {
|
||||||
it ?: return@addOnPropertyChangedCallback
|
it ?: return@addOnPropertyChangedCallback
|
||||||
@@ -32,13 +39,11 @@ class PolicyRvItem(val item: Policy, val icon: Drawable) : ComparableRvItem<Poli
|
|||||||
}
|
}
|
||||||
shouldNotify.addOnPropertyChangedCallback {
|
shouldNotify.addOnPropertyChangedCallback {
|
||||||
it ?: return@addOnPropertyChangedCallback
|
it ?: return@addOnPropertyChangedCallback
|
||||||
item.notification = it
|
rxBus.post(PolicyUpdateEvent.Notification(currentStateItem))
|
||||||
rxBus.post(PolicyUpdateEvent.Notification(this@PolicyRvItem))
|
|
||||||
}
|
}
|
||||||
shouldLog.addOnPropertyChangedCallback {
|
shouldLog.addOnPropertyChangedCallback {
|
||||||
it ?: return@addOnPropertyChangedCallback
|
it ?: return@addOnPropertyChangedCallback
|
||||||
item.logging = it
|
rxBus.post(PolicyUpdateEvent.Log(currentStateItem))
|
||||||
rxBus.post(PolicyUpdateEvent.Log(this@PolicyRvItem))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.model.events
|
package com.topjohnwu.magisk.model.events
|
||||||
|
|
||||||
import com.skoumal.teanity.rxbus.RxBus
|
import com.skoumal.teanity.rxbus.RxBus
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
||||||
@@ -8,9 +9,9 @@ import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
|||||||
class HideProcessEvent(val item: HideProcessRvItem) : RxBus.Event
|
class HideProcessEvent(val item: HideProcessRvItem) : RxBus.Event
|
||||||
|
|
||||||
class PolicyEnableEvent(val item: PolicyRvItem, val enable: Boolean) : RxBus.Event
|
class PolicyEnableEvent(val item: PolicyRvItem, val enable: Boolean) : RxBus.Event
|
||||||
sealed class PolicyUpdateEvent(val item: PolicyRvItem) : RxBus.Event {
|
sealed class PolicyUpdateEvent(val item: MagiskPolicy) : RxBus.Event {
|
||||||
class Notification(item: PolicyRvItem) : PolicyUpdateEvent(item)
|
class Notification(item: MagiskPolicy) : PolicyUpdateEvent(item)
|
||||||
class Log(item: PolicyRvItem) : PolicyUpdateEvent(item)
|
class Log(item: MagiskPolicy) : PolicyUpdateEvent(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModuleUpdatedEvent(val item: ModuleRvItem) : RxBus.Event
|
class ModuleUpdatedEvent(val item: ModuleRvItem) : RxBus.Event
|
||||||
|
@@ -2,8 +2,7 @@ package com.topjohnwu.magisk.model.events
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import com.skoumal.teanity.viewevents.ViewEvent
|
import com.skoumal.teanity.viewevents.ViewEvent
|
||||||
import com.topjohnwu.magisk.model.entity.Policy
|
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||||
import com.topjohnwu.magisk.model.entity.Repo
|
|
||||||
import io.reactivex.subjects.PublishSubject
|
import io.reactivex.subjects.PublishSubject
|
||||||
|
|
||||||
|
|
||||||
@@ -36,5 +35,4 @@ class PermissionEvent(
|
|||||||
|
|
||||||
class BackPressEvent : ViewEvent()
|
class BackPressEvent : ViewEvent()
|
||||||
|
|
||||||
class SuDialogEvent(val policy: Policy) : ViewEvent()
|
|
||||||
class DieEvent : ViewEvent()
|
class DieEvent : ViewEvent()
|
@@ -3,9 +3,8 @@ package com.topjohnwu.magisk.model.flash
|
|||||||
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.tasks.FlashZip
|
import com.topjohnwu.magisk.tasks.FlashZip
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
|
||||||
import com.topjohnwu.magisk.utils.inject
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ sealed class Flashing(
|
|||||||
|
|
||||||
override fun onResult(success: Boolean) {
|
override fun onResult(success: Boolean) {
|
||||||
if (success) {
|
if (success) {
|
||||||
Utils.loadModules()
|
//Utils.loadModules()
|
||||||
}
|
}
|
||||||
super.onResult(success)
|
super.onResult(success)
|
||||||
}
|
}
|
||||||
|
@@ -5,10 +5,11 @@ import com.topjohnwu.magisk.tasks.MagiskInstaller
|
|||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
|
||||||
sealed class Patching(
|
sealed class Patching(
|
||||||
|
file: Uri,
|
||||||
private val console: MutableList<String>,
|
private val console: MutableList<String>,
|
||||||
logs: MutableList<String>,
|
logs: MutableList<String>,
|
||||||
private val resultListener: FlashResultListener
|
private val resultListener: FlashResultListener
|
||||||
) : MagiskInstaller(console, logs) {
|
) : MagiskInstaller(file, console, logs) {
|
||||||
|
|
||||||
override fun onResult(success: Boolean) {
|
override fun onResult(success: Boolean) {
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -21,29 +22,32 @@ sealed class Patching(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class File(
|
class File(
|
||||||
|
file: Uri,
|
||||||
private val uri: Uri,
|
private val uri: Uri,
|
||||||
console: MutableList<String>,
|
console: MutableList<String>,
|
||||||
logs: MutableList<String>,
|
logs: MutableList<String>,
|
||||||
resultListener: FlashResultListener
|
resultListener: FlashResultListener
|
||||||
) : Patching(console, logs, resultListener) {
|
) : Patching(file, console, logs, resultListener) {
|
||||||
override fun operations() =
|
override fun operations() =
|
||||||
extractZip() && handleFile(uri) && patchBoot() && storeBoot()
|
extractZip() && handleFile(uri) && patchBoot() && storeBoot()
|
||||||
}
|
}
|
||||||
|
|
||||||
class SecondSlot(
|
class SecondSlot(
|
||||||
|
file: Uri,
|
||||||
console: MutableList<String>,
|
console: MutableList<String>,
|
||||||
logs: MutableList<String>,
|
logs: MutableList<String>,
|
||||||
resultListener: FlashResultListener
|
resultListener: FlashResultListener
|
||||||
) : Patching(console, logs, resultListener) {
|
) : Patching(file, console, logs, resultListener) {
|
||||||
override fun operations() =
|
override fun operations() =
|
||||||
findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA()
|
findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA()
|
||||||
}
|
}
|
||||||
|
|
||||||
class Direct(
|
class Direct(
|
||||||
|
file: Uri,
|
||||||
console: MutableList<String>,
|
console: MutableList<String>,
|
||||||
logs: MutableList<String>,
|
logs: MutableList<String>,
|
||||||
resultListener: FlashResultListener
|
resultListener: FlashResultListener
|
||||||
) : Patching(console, logs, resultListener) {
|
) : Patching(file, console, logs, resultListener) {
|
||||||
override fun operations() =
|
override fun operations() =
|
||||||
findImage() && extractZip() && patchBoot() && flashBoot()
|
findImage() && extractZip() && patchBoot() && flashBoot()
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.topjohnwu.magisk.extensions.trimEmptyToNull
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class BooleanProperty(
|
||||||
|
private val name: String,
|
||||||
|
private val default: Boolean,
|
||||||
|
private val commit: Boolean
|
||||||
|
) : Property(), ReadWriteProperty<PreferenceModel, Boolean> {
|
||||||
|
|
||||||
|
override operator fun getValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>
|
||||||
|
): Boolean {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
return thisRef.prefs.get(prefName, default)
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun setValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>,
|
||||||
|
value: Boolean
|
||||||
|
) {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
thisRef.prefs.edit(commit) { put(prefName, value) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.topjohnwu.magisk.extensions.trimEmptyToNull
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class FloatProperty(
|
||||||
|
private val name: String,
|
||||||
|
private val default: Float,
|
||||||
|
private val commit: Boolean
|
||||||
|
) : Property(), ReadWriteProperty<PreferenceModel, Float> {
|
||||||
|
|
||||||
|
override operator fun getValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>
|
||||||
|
): Float {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
return thisRef.prefs.get(prefName, default)
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun setValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>,
|
||||||
|
value: Float
|
||||||
|
) {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
thisRef.prefs.edit(commit) { put(prefName, value) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.topjohnwu.magisk.extensions.trimEmptyToNull
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class IntProperty(
|
||||||
|
private val name: String,
|
||||||
|
private val default: Int,
|
||||||
|
private val commit: Boolean
|
||||||
|
) : Property(), ReadWriteProperty<PreferenceModel, Int> {
|
||||||
|
|
||||||
|
override operator fun getValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>
|
||||||
|
): Int {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
return thisRef.prefs.get(prefName, default)
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun setValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>,
|
||||||
|
value: Int
|
||||||
|
) {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
thisRef.prefs.edit(commit) { put(prefName, value) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.topjohnwu.magisk.extensions.trimEmptyToNull
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class LongProperty(
|
||||||
|
private val name: String,
|
||||||
|
private val default: Long,
|
||||||
|
private val commit: Boolean
|
||||||
|
) : Property(), ReadWriteProperty<PreferenceModel, Long> {
|
||||||
|
|
||||||
|
override operator fun getValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>
|
||||||
|
): Long {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
return thisRef.prefs.get(prefName, default)
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun setValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>,
|
||||||
|
value: Long
|
||||||
|
) {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
thisRef.prefs.edit(commit) { put(prefName, value) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,69 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
interface PreferenceModel {
|
||||||
|
|
||||||
|
val context: Context
|
||||||
|
|
||||||
|
val fileName: String
|
||||||
|
get() = "${context.packageName}_preferences"
|
||||||
|
val commitPrefs: Boolean
|
||||||
|
get() = false
|
||||||
|
val prefs: SharedPreferences
|
||||||
|
get() = context.getSharedPreferences(fileName, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
fun preferenceStrInt(
|
||||||
|
name: String,
|
||||||
|
default: Int,
|
||||||
|
writeDefault: Boolean = false,
|
||||||
|
commit: Boolean = commitPrefs
|
||||||
|
) = object: ReadWriteProperty<PreferenceModel, Int> {
|
||||||
|
val base = StringProperty(name, default.toString(), commit)
|
||||||
|
override fun getValue(thisRef: PreferenceModel, property: KProperty<*>): Int =
|
||||||
|
base.getValue(thisRef, property).toInt()
|
||||||
|
|
||||||
|
override fun setValue(thisRef: PreferenceModel, property: KProperty<*>, value: Int) =
|
||||||
|
base.setValue(thisRef, property, value.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun preference(
|
||||||
|
name: String,
|
||||||
|
default: Boolean,
|
||||||
|
commit: Boolean = commitPrefs
|
||||||
|
) = BooleanProperty(name, default, commit)
|
||||||
|
|
||||||
|
fun preference(
|
||||||
|
name: String,
|
||||||
|
default: Float,
|
||||||
|
commit: Boolean = commitPrefs
|
||||||
|
) = FloatProperty(name, default, commit)
|
||||||
|
|
||||||
|
fun preference(
|
||||||
|
name: String,
|
||||||
|
default: Int,
|
||||||
|
commit: Boolean = commitPrefs
|
||||||
|
) = IntProperty(name, default, commit)
|
||||||
|
|
||||||
|
fun preference(
|
||||||
|
name: String,
|
||||||
|
default: Long,
|
||||||
|
commit: Boolean = commitPrefs
|
||||||
|
) = LongProperty(name, default, commit)
|
||||||
|
|
||||||
|
fun preference(
|
||||||
|
name: String,
|
||||||
|
default: String,
|
||||||
|
commit: Boolean = commitPrefs
|
||||||
|
) = StringProperty(name, default, commit)
|
||||||
|
|
||||||
|
fun preference(
|
||||||
|
name: String,
|
||||||
|
default: Set<String>,
|
||||||
|
commit: Boolean = commitPrefs
|
||||||
|
) = StringSetProperty(name, default, commit)
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
|
||||||
|
abstract class Property {
|
||||||
|
|
||||||
|
fun SharedPreferences.Editor.put(name: String, value: Boolean) = putBoolean(name, value)
|
||||||
|
fun SharedPreferences.Editor.put(name: String, value: Float) = putFloat(name, value)
|
||||||
|
fun SharedPreferences.Editor.put(name: String, value: Int) = putInt(name, value)
|
||||||
|
fun SharedPreferences.Editor.put(name: String, value: Long) = putLong(name, value)
|
||||||
|
fun SharedPreferences.Editor.put(name: String, value: String) = putString(name, value)
|
||||||
|
fun SharedPreferences.Editor.put(name: String, value: Set<String>) = putStringSet(name, value)
|
||||||
|
|
||||||
|
fun SharedPreferences.get(name: String, value: Boolean) = getBoolean(name, value)
|
||||||
|
fun SharedPreferences.get(name: String, value: Float) = getFloat(name, value)
|
||||||
|
fun SharedPreferences.get(name: String, value: Int) = getInt(name, value)
|
||||||
|
fun SharedPreferences.get(name: String, value: Long) = getLong(name, value)
|
||||||
|
fun SharedPreferences.get(name: String, value: String) = getString(name, value) ?: value
|
||||||
|
fun SharedPreferences.get(name: String, value: Set<String>) = getStringSet(name, value) ?: value
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.topjohnwu.magisk.extensions.trimEmptyToNull
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class StringProperty(
|
||||||
|
private val name: String,
|
||||||
|
private val default: String,
|
||||||
|
private val commit: Boolean
|
||||||
|
) : Property(), ReadWriteProperty<PreferenceModel, String> {
|
||||||
|
|
||||||
|
override operator fun getValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>
|
||||||
|
): String {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
return thisRef.prefs.get(prefName, default)
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun setValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>,
|
||||||
|
value: String
|
||||||
|
) {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
thisRef.prefs.edit(commit) { put(prefName, value) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.model.preference
|
||||||
|
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.topjohnwu.magisk.extensions.trimEmptyToNull
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class StringSetProperty(
|
||||||
|
private val name: String,
|
||||||
|
private val default: Set<String>,
|
||||||
|
private val commit: Boolean
|
||||||
|
) : Property(), ReadWriteProperty<PreferenceModel, Set<String>> {
|
||||||
|
|
||||||
|
override operator fun getValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>
|
||||||
|
): Set<String> {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
return thisRef.prefs.get(prefName, default)
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun setValue(
|
||||||
|
thisRef: PreferenceModel,
|
||||||
|
property: KProperty<*>,
|
||||||
|
value: Set<String>
|
||||||
|
) {
|
||||||
|
val prefName = name.trimEmptyToNull() ?: property.name
|
||||||
|
thisRef.prefs.edit(commit) { put(prefName, value) }
|
||||||
|
}
|
||||||
|
}
|
@@ -6,18 +6,25 @@ import android.content.Intent
|
|||||||
import com.topjohnwu.magisk.ClassMap
|
import com.topjohnwu.magisk.ClassMap
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.data.database.MagiskDB
|
import com.topjohnwu.magisk.Info
|
||||||
|
import com.topjohnwu.magisk.data.database.PolicyDao
|
||||||
|
import com.topjohnwu.magisk.data.database.base.su
|
||||||
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
|
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.DownloadSubject
|
||||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
|
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
|
||||||
import com.topjohnwu.magisk.utils.DownloadApp
|
|
||||||
import com.topjohnwu.magisk.utils.RootUtils
|
|
||||||
import com.topjohnwu.magisk.utils.SuLogger
|
import com.topjohnwu.magisk.utils.SuLogger
|
||||||
import com.topjohnwu.magisk.utils.get
|
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import com.topjohnwu.magisk.view.Shortcuts
|
import com.topjohnwu.magisk.view.Shortcuts
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
|
||||||
open class GeneralReceiver : BroadcastReceiver() {
|
open class GeneralReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
private val policyDB: PolicyDao by inject()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val REQUEST = "request"
|
const val REQUEST = "request"
|
||||||
const val LOG = "log"
|
const val LOG = "log"
|
||||||
@@ -26,28 +33,25 @@ open class GeneralReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getPkg(intent: Intent): String {
|
private fun getPkg(intent: Intent): String {
|
||||||
return intent.data?.encodedSchemeSpecificPart ?: ""
|
return intent.data?.encodedSchemeSpecificPart.orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent?) {
|
override fun onReceive(context: Context, intent: Intent?) {
|
||||||
if (intent == null)
|
intent ?: return
|
||||||
return
|
when (intent.action ?: return) {
|
||||||
val mDB: MagiskDB = get()
|
|
||||||
var action: String? = intent.action ?: return
|
|
||||||
when (action) {
|
|
||||||
Intent.ACTION_REBOOT, Intent.ACTION_BOOT_COMPLETED -> {
|
Intent.ACTION_REBOOT, Intent.ACTION_BOOT_COMPLETED -> {
|
||||||
action = intent.getStringExtra("action")
|
val action = intent.getStringExtra("action")
|
||||||
if (action == null) {
|
if (action == null) {
|
||||||
// Actual boot completed event
|
// Actual boot completed event
|
||||||
Shell.su("mm_patch_dtbo").submit { result ->
|
Shell.su("mm_patch_dtbo").submit {
|
||||||
if (result.isSuccess)
|
if (it.isSuccess)
|
||||||
Notifications.dtboPatched()
|
Notifications.dtboPatched(context)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
when (action) {
|
when (action) {
|
||||||
REQUEST -> {
|
REQUEST -> {
|
||||||
val i = Intent(context, ClassMap.get<Any>(SuRequestActivity::class.java))
|
val i = Intent(context, ClassMap[SuRequestActivity::class.java])
|
||||||
.setAction(action)
|
.setAction(action)
|
||||||
.putExtra("socket", intent.getStringExtra("socket"))
|
.putExtra("socket", intent.getStringExtra("socket"))
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
@@ -61,20 +65,23 @@ open class GeneralReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
Intent.ACTION_PACKAGE_REPLACED ->
|
Intent.ACTION_PACKAGE_REPLACED ->
|
||||||
// This will only work pre-O
|
// This will only work pre-O
|
||||||
if (Config.get<Boolean>(Config.Key.SU_REAUTH)!!) {
|
if (Config.suReAuth)
|
||||||
mDB.deletePolicy(getPkg(intent))
|
policyDB.delete(getPkg(intent)).blockingGet()
|
||||||
}
|
|
||||||
Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
|
Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
|
||||||
val pkg = getPkg(intent)
|
val pkg = getPkg(intent)
|
||||||
mDB.deletePolicy(pkg)
|
policyDB.delete(pkg).blockingGet()
|
||||||
Shell.su("magiskhide --rm $pkg").submit()
|
"magiskhide --rm $pkg".su().blockingGet()
|
||||||
}
|
}
|
||||||
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context)
|
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context)
|
||||||
Const.Key.BROADCAST_MANAGER_UPDATE -> {
|
Const.Key.BROADCAST_MANAGER_UPDATE -> {
|
||||||
Config.managerLink = intent.getStringExtra(Const.Key.INTENT_SET_LINK)
|
intent.getParcelableExtra<ManagerJson>(Const.Key.INTENT_SET_APP)?.let {
|
||||||
DownloadApp.upgrade(intent.getStringExtra(Const.Key.INTENT_SET_NAME))
|
Info.remote = Info.remote.copy(app = it)
|
||||||
|
}
|
||||||
|
DownloadService(context) {
|
||||||
|
subject = DownloadSubject.Manager(Configuration.APK.Upgrade)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Const.Key.BROADCAST_REBOOT -> RootUtils.reboot()
|
Const.Key.BROADCAST_REBOOT -> reboot()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,33 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.model.update;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.work.ListenableWorker;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.App;
|
|
||||||
import com.topjohnwu.magisk.BuildConfig;
|
|
||||||
import com.topjohnwu.magisk.Config;
|
|
||||||
import com.topjohnwu.magisk.model.worker.DelegateWorker;
|
|
||||||
import com.topjohnwu.magisk.tasks.CheckUpdates;
|
|
||||||
import com.topjohnwu.magisk.view.Notifications;
|
|
||||||
import com.topjohnwu.superuser.Shell;
|
|
||||||
|
|
||||||
public class UpdateCheckService extends DelegateWorker {
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public ListenableWorker.Result doWork() {
|
|
||||||
if (App.foreground() == null) {
|
|
||||||
Shell.getShell();
|
|
||||||
CheckUpdates.check(this::onCheckDone);
|
|
||||||
}
|
|
||||||
return ListenableWorker.Result.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onCheckDone() {
|
|
||||||
if (BuildConfig.VERSION_CODE < Config.remoteManagerVersionCode) {
|
|
||||||
Notifications.managerUpdate();
|
|
||||||
} else if (Config.magiskVersionCode < Config.remoteMagiskVersionCode) {
|
|
||||||
Notifications.magiskUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.topjohnwu.magisk.model.update
|
||||||
|
|
||||||
|
import androidx.work.ListenableWorker
|
||||||
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.Info
|
||||||
|
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||||
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
|
import com.topjohnwu.magisk.model.worker.DelegateWorker
|
||||||
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
|
||||||
|
class UpdateCheckService : DelegateWorker() {
|
||||||
|
|
||||||
|
private val magiskRepo: MagiskRepository by inject()
|
||||||
|
|
||||||
|
override fun doWork(): ListenableWorker.Result {
|
||||||
|
// Make sure shell initializer was ran
|
||||||
|
Shell.getShell()
|
||||||
|
return runCatching {
|
||||||
|
magiskRepo.fetchUpdate().blockingGet()
|
||||||
|
if (BuildConfig.VERSION_CODE < Info.remote.app.versionCode)
|
||||||
|
Notifications.managerUpdate(applicationContext)
|
||||||
|
else if (Info.magiskVersionCode < Info.remote.magisk.versionCode)
|
||||||
|
Notifications.magiskUpdate(applicationContext)
|
||||||
|
ListenableWorker.Result.success()
|
||||||
|
}.getOrElse {
|
||||||
|
ListenableWorker.Result.failure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,84 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.model.worker;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Network;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import androidx.annotation.MainThread;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import androidx.work.Data;
|
|
||||||
import androidx.work.ListenableWorker;
|
|
||||||
|
|
||||||
public abstract class DelegateWorker {
|
|
||||||
|
|
||||||
private ListenableWorker worker;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public abstract ListenableWorker.Result doWork();
|
|
||||||
|
|
||||||
public void onStopped() {}
|
|
||||||
|
|
||||||
public void setActualWorker(ListenableWorker w) {
|
|
||||||
worker = w;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public Context getApplicationContext() {
|
|
||||||
return worker.getApplicationContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public UUID getId() {
|
|
||||||
return worker.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public Data getInputData() {
|
|
||||||
return worker.getInputData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public Set<String> getTags() {
|
|
||||||
return worker.getTags();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@RequiresApi(24)
|
|
||||||
public List<Uri> getTriggeredContentUris() {
|
|
||||||
return worker.getTriggeredContentUris();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@RequiresApi(24)
|
|
||||||
public List<String> getTriggeredContentAuthorities() {
|
|
||||||
return worker.getTriggeredContentAuthorities();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@RequiresApi(28)
|
|
||||||
public Network getNetwork() {
|
|
||||||
return worker.getNetwork();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRunAttemptCount() {
|
|
||||||
return worker.getRunAttemptCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@MainThread
|
|
||||||
public ListenableFuture<ListenableWorker.Result> startWork() {
|
|
||||||
return worker.startWork();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isStopped() {
|
|
||||||
return worker.isStopped();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,59 @@
|
|||||||
|
package com.topjohnwu.magisk.model.worker
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Network
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.annotation.MainThread
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.work.Data
|
||||||
|
import androidx.work.ListenableWorker
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
abstract class DelegateWorker {
|
||||||
|
|
||||||
|
private lateinit var worker: ListenableWorker
|
||||||
|
|
||||||
|
val applicationContext: Context
|
||||||
|
get() = worker.applicationContext
|
||||||
|
|
||||||
|
val id: UUID
|
||||||
|
get() = worker.id
|
||||||
|
|
||||||
|
val inputData: Data
|
||||||
|
get() = worker.inputData
|
||||||
|
|
||||||
|
val tags: Set<String>
|
||||||
|
get() = worker.tags
|
||||||
|
|
||||||
|
val triggeredContentUris: List<Uri>
|
||||||
|
@RequiresApi(24)
|
||||||
|
get() = worker.triggeredContentUris
|
||||||
|
|
||||||
|
val triggeredContentAuthorities: List<String>
|
||||||
|
@RequiresApi(24)
|
||||||
|
get() = worker.triggeredContentAuthorities
|
||||||
|
|
||||||
|
val network: Network?
|
||||||
|
@RequiresApi(28)
|
||||||
|
get() = worker.network
|
||||||
|
|
||||||
|
val runAttemptCount: Int
|
||||||
|
get() = worker.runAttemptCount
|
||||||
|
|
||||||
|
val isStopped: Boolean
|
||||||
|
get() = worker.isStopped
|
||||||
|
|
||||||
|
abstract fun doWork(): ListenableWorker.Result
|
||||||
|
|
||||||
|
fun onStopped() {}
|
||||||
|
|
||||||
|
fun attachWorker(w: ListenableWorker) {
|
||||||
|
worker = w
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
fun startWork(): ListenableFuture<ListenableWorker.Result> {
|
||||||
|
return worker.startWork()
|
||||||
|
}
|
||||||
|
}
|
75
app/src/main/java/com/topjohnwu/magisk/model/zip/Zip.kt
Normal file
75
app/src/main/java/com/topjohnwu/magisk/model/zip/Zip.kt
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package com.topjohnwu.magisk.model.zip
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.extensions.forEach
|
||||||
|
import com.topjohnwu.magisk.extensions.withStreams
|
||||||
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
|
import java.io.File
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
|
|
||||||
|
class Zip private constructor(private val values: Builder) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
operator fun invoke(builder: Builder.() -> Unit): Zip {
|
||||||
|
return Zip(Builder().apply(builder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
lateinit var zip: File
|
||||||
|
lateinit var destination: File
|
||||||
|
var excludeDirs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Path(val path: String, val pullFromDir: Boolean = true)
|
||||||
|
|
||||||
|
fun unzip(vararg paths: Pair<String, Boolean>) =
|
||||||
|
unzip(*paths.map { Path(it.first, it.second) }.toTypedArray())
|
||||||
|
|
||||||
|
@Suppress("RedundantLambdaArrow")
|
||||||
|
fun unzip(vararg paths: Path) {
|
||||||
|
ensureRequiredParams()
|
||||||
|
|
||||||
|
values.zip.zipStream().use {
|
||||||
|
it.forEach { e ->
|
||||||
|
val currentPath = paths.firstOrNull { e.name.startsWith(it.path) }
|
||||||
|
val isDirectory = values.excludeDirs && e.isDirectory
|
||||||
|
if (currentPath == null || isDirectory) {
|
||||||
|
// Ignore directories, only create files
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
val name = if (currentPath.pullFromDir) {
|
||||||
|
e.name.substring(e.name.lastIndexOf('/') + 1)
|
||||||
|
} else {
|
||||||
|
e.name
|
||||||
|
}
|
||||||
|
|
||||||
|
val out = File(values.destination, name)
|
||||||
|
.ensureExists()
|
||||||
|
.outputStream()
|
||||||
|
//.suOutputStream()
|
||||||
|
|
||||||
|
withStreams(it, out) { reader, writer ->
|
||||||
|
reader.copyTo(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureRequiredParams() {
|
||||||
|
if (!values.zip.exists()) {
|
||||||
|
throw RuntimeException("Zip file does not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun File.ensureExists() =
|
||||||
|
if ((!parentFile.exists() && !parentFile.mkdirs()) || parentFile is SuFile) {
|
||||||
|
SuFile(parentFile, name).apply { parentFile.mkdirs() }
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun File.zipStream() = ZipInputStream(inputStream())
|
||||||
|
|
||||||
|
}
|
@@ -1,132 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.tasks;
|
|
||||||
|
|
||||||
import android.os.SystemClock;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Config;
|
|
||||||
import com.topjohnwu.magisk.Const;
|
|
||||||
import com.topjohnwu.magisk.utils.Event;
|
|
||||||
import com.topjohnwu.net.Networking;
|
|
||||||
import com.topjohnwu.net.Request;
|
|
||||||
import com.topjohnwu.net.ResponseListener;
|
|
||||||
import com.topjohnwu.superuser.ShellUtils;
|
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class CheckUpdates {
|
|
||||||
|
|
||||||
private static Request getRequest() {
|
|
||||||
String url;
|
|
||||||
switch ((int) Config.get(Config.Key.UPDATE_CHANNEL)) {
|
|
||||||
case Config.Value.BETA_CHANNEL:
|
|
||||||
url = Const.Url.BETA_URL;
|
|
||||||
break;
|
|
||||||
case Config.Value.CUSTOM_CHANNEL:
|
|
||||||
url = Config.get(Config.Key.CUSTOM_CHANNEL);
|
|
||||||
break;
|
|
||||||
case Config.Value.CANARY_CHANNEL:
|
|
||||||
url = Const.Url.CANARY_URL;
|
|
||||||
break;
|
|
||||||
case Config.Value.CANARY_DEBUG_CHANNEL:
|
|
||||||
url = Const.Url.CANARY_DEBUG_URL;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
url = Const.Url.STABLE_URL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return Networking.get(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void check() {
|
|
||||||
check(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void check(Runnable cb) {
|
|
||||||
Request request = getRequest();
|
|
||||||
UpdateListener listener = new UpdateListener(cb);
|
|
||||||
if (ShellUtils.onMainThread()) {
|
|
||||||
request.getAsJSONObject(listener);
|
|
||||||
} else {
|
|
||||||
JSONObject json = request.execForJSONObject().getResult();
|
|
||||||
if (json != null)
|
|
||||||
listener.onResponse(json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class UpdateListener implements ResponseListener<JSONObject> {
|
|
||||||
|
|
||||||
private Runnable cb;
|
|
||||||
private long start;
|
|
||||||
|
|
||||||
UpdateListener(Runnable callback) {
|
|
||||||
cb = callback;
|
|
||||||
start = SystemClock.uptimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getInt(JSONObject json, String name, int defValue) {
|
|
||||||
if (json == null)
|
|
||||||
return defValue;
|
|
||||||
try {
|
|
||||||
return json.getInt(name);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
return defValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getString(JSONObject json, String name, String defValue) {
|
|
||||||
if (json == null)
|
|
||||||
return defValue;
|
|
||||||
try {
|
|
||||||
return json.getString(name);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
return defValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private JSONObject getJson(JSONObject json, String name) {
|
|
||||||
try {
|
|
||||||
return json.getJSONObject(name);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponse(JSONObject json) {
|
|
||||||
JSONObject magisk = getJson(json, "magisk");
|
|
||||||
Config.remoteMagiskVersionCode = getInt(magisk, "versionCode", -1);
|
|
||||||
|
|
||||||
if ((int) Config.get(Config.Key.UPDATE_CHANNEL) == Config.Value.DEFAULT_CHANNEL) {
|
|
||||||
if (Config.magiskVersionCode > Config.remoteMagiskVersionCode) {
|
|
||||||
// If we are newer than current stable channel, switch to beta
|
|
||||||
Config.set(Config.Key.UPDATE_CHANNEL, Config.Value.BETA_CHANNEL);
|
|
||||||
check(cb);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
Config.set(Config.Key.UPDATE_CHANNEL, Config.Value.STABLE_CHANNEL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Config.remoteMagiskVersionString = getString(magisk, "version", null);
|
|
||||||
Config.magiskLink = getString(magisk, "link", null);
|
|
||||||
Config.magiskNoteLink = getString(magisk, "note", null);
|
|
||||||
Config.magiskMD5 = getString(magisk, "md5", null);
|
|
||||||
|
|
||||||
JSONObject manager = getJson(json, "app");
|
|
||||||
Config.remoteManagerVersionString = getString(manager, "version", null);
|
|
||||||
Config.remoteManagerVersionCode = getInt(manager, "versionCode", -1);
|
|
||||||
Config.managerLink = getString(manager, "link", null);
|
|
||||||
Config.managerNoteLink = getString(manager, "note", null);
|
|
||||||
|
|
||||||
JSONObject uninstaller = getJson(json, "uninstaller");
|
|
||||||
Config.uninstallerLink = getString(uninstaller, "link", null);
|
|
||||||
|
|
||||||
UiThreadHandler.handler.postAtTime(() -> Event.trigger(Event.UPDATE_CHECK_DONE),
|
|
||||||
start + 1000 /* Add artificial delay to let UI behave correctly */);
|
|
||||||
|
|
||||||
if (cb != null)
|
|
||||||
cb.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,12 +1,12 @@
|
|||||||
package com.topjohnwu.magisk.tasks
|
package com.topjohnwu.magisk.tasks
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
import com.skoumal.teanity.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.App
|
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.utils.fileName
|
import com.topjohnwu.magisk.extensions.fileName
|
||||||
import com.topjohnwu.magisk.utils.inject
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
import com.topjohnwu.magisk.utils.readUri
|
import com.topjohnwu.magisk.extensions.readUri
|
||||||
import com.topjohnwu.magisk.utils.unzip
|
import com.topjohnwu.magisk.utils.unzip
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
@@ -20,8 +20,11 @@ abstract class FlashZip(
|
|||||||
private val logs: MutableList<String>
|
private val logs: MutableList<String>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val app: App by inject()
|
private val context: Context by inject()
|
||||||
private val tmpFile: File = File(app.cacheDir, "install.zip")
|
private val installFolder = File(context.cacheDir, "flash").apply {
|
||||||
|
if (!exists()) mkdirs()
|
||||||
|
}
|
||||||
|
private val tmpFile: File = File(installFolder, "install.zip")
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
private fun unzipAndCheck(): Boolean {
|
private fun unzipAndCheck(): Boolean {
|
||||||
@@ -40,7 +43,7 @@ abstract class FlashZip(
|
|||||||
console.add("- Copying zip to temp directory")
|
console.add("- Copying zip to temp directory")
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
app.readUri(mUri).use { input ->
|
context.readUri(mUri).use { input ->
|
||||||
tmpFile.outputStream().use { out -> input.copyTo(out) }
|
tmpFile.outputStream().use { out -> input.copyTo(out) }
|
||||||
}
|
}
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user