mirror of
https://github.com/topjohnwu/Magisk
synced 2025-11-14 08:47:34 +01:00
Compare commits
268 Commits
manager-v7
...
manager-v7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b41cd8564 | ||
|
|
7db523071d | ||
|
|
974ee58b9c | ||
|
|
1e88f2c382 | ||
|
|
0bdcfcaaf5 | ||
|
|
5f9c78d04f | ||
|
|
afa178fdec | ||
|
|
3a0e3c98f7 | ||
|
|
fafa92d44b | ||
|
|
242e64d72f | ||
|
|
2262af728e | ||
|
|
bce777d7c6 | ||
|
|
15bd2da824 | ||
|
|
bd438ca288 | ||
|
|
e0d02a61a9 | ||
|
|
b3328a0ec2 | ||
|
|
3c2041933f | ||
|
|
e88b1cc443 | ||
|
|
71b05b18a0 | ||
|
|
b07b528e2a | ||
|
|
1aeb6315ff | ||
|
|
1b4a3d2d9f | ||
|
|
3049a81c3b | ||
|
|
2db1e5cb74 | ||
|
|
78c64d39ec | ||
|
|
46ba726232 | ||
|
|
eb26e62889 | ||
|
|
7f667fed18 | ||
|
|
b2cb2b8b75 | ||
|
|
d19f65ce4a | ||
|
|
025b060506 | ||
|
|
7fa2625a03 | ||
|
|
33d62d7f21 | ||
|
|
b336655a79 | ||
|
|
3beffd84d6 | ||
|
|
02761f5f35 | ||
|
|
3b9f7885e0 | ||
|
|
7668e45890 | ||
|
|
695c8bc5d0 | ||
|
|
06c42d05c3 | ||
|
|
404104208f | ||
|
|
b4d0ad9713 | ||
|
|
4f4f54a059 | ||
|
|
12fda29280 | ||
|
|
af060b3132 | ||
|
|
8c500709e4 | ||
|
|
490e6a6f23 | ||
|
|
08177c3dd8 | ||
|
|
d22b9c26b6 | ||
|
|
4bb8ad19cf | ||
|
|
3e275b7dba | ||
|
|
11b7076a43 | ||
|
|
291c718ba2 | ||
|
|
fcd6071c57 | ||
|
|
476b61c4c9 | ||
|
|
8cc5f096a2 | ||
|
|
474d65207e | ||
|
|
03428329ef | ||
|
|
8d21988656 | ||
|
|
72edbfc455 | ||
|
|
276535dad6 | ||
|
|
e373e59661 | ||
|
|
34bb18448c | ||
|
|
01253f050a | ||
|
|
5bee1c56a9 | ||
|
|
474cc7d56d | ||
|
|
bffdedddb4 | ||
|
|
fd72f658c0 | ||
|
|
d3b5cf82d8 | ||
|
|
d26d804cc2 | ||
|
|
4f9a25ee89 | ||
|
|
bb9ce0e897 | ||
|
|
d6fb9868bf | ||
|
|
9aff1a57d3 | ||
|
|
7681fde4d0 | ||
|
|
d3b7b41927 | ||
|
|
da159e4655 | ||
|
|
7f6a6016d6 | ||
|
|
44ed0a3279 | ||
|
|
9964e1bb8e | ||
|
|
8b8f725499 | ||
|
|
bab856bce2 | ||
|
|
3d285b91c6 | ||
|
|
1dc531930d | ||
|
|
3d3345acac | ||
|
|
b29f0ca4d1 | ||
|
|
576efbdc1b | ||
|
|
a7f0510a3e | ||
|
|
2ef088cb60 | ||
|
|
7c320b6fc4 | ||
|
|
5a4c82b860 | ||
|
|
9b297b752e | ||
|
|
1d6ba58ccd | ||
|
|
1542447822 | ||
|
|
a6f0aff659 | ||
|
|
171ddab32b | ||
|
|
2aee0b0be0 | ||
|
|
817cdf7113 | ||
|
|
1a38f25bd9 | ||
|
|
ad40e53349 | ||
|
|
a2ddf362d8 | ||
|
|
65eca31635 | ||
|
|
8b0b4a2c39 | ||
|
|
c0216c0653 | ||
|
|
61de63a518 | ||
|
|
d952cc2327 | ||
|
|
46447f7cfd | ||
|
|
a6e62e07a2 | ||
|
|
b1d25e0503 | ||
|
|
25c557248c | ||
|
|
472cde29b8 | ||
|
|
73525d19e9 | ||
|
|
26618f8d73 | ||
|
|
6f7c13b814 | ||
|
|
e7d668502c | ||
|
|
6fd357962f | ||
|
|
0c9feedb37 | ||
|
|
14ba002cbc | ||
|
|
7da97489cc | ||
|
|
a9f11b28c8 | ||
|
|
b31d986c8d | ||
|
|
2dad751889 | ||
|
|
c85b1c56af | ||
|
|
6dd34aec47 | ||
|
|
4cd154675f | ||
|
|
d8d72f92b3 | ||
|
|
a30f5b175f | ||
|
|
8277896ca1 | ||
|
|
493068c073 | ||
|
|
f4299fbea8 | ||
|
|
10ce11d671 | ||
|
|
0f34457a10 | ||
|
|
34c65e13bc | ||
|
|
17a77e2577 | ||
|
|
0f219e5ae6 | ||
|
|
353c3c7d81 | ||
|
|
0a89edf3b0 | ||
|
|
e7155837d7 | ||
|
|
31e003bda5 | ||
|
|
490e4d3180 | ||
|
|
dc9f69bab0 | ||
|
|
fdf04f77f2 | ||
|
|
5e87483f34 | ||
|
|
f7aa451591 | ||
|
|
321d11c2c6 | ||
|
|
ee447bc4ce | ||
|
|
31153e4366 | ||
|
|
7693024c29 | ||
|
|
9628700a2f | ||
|
|
38576173cb | ||
|
|
19a769c12e | ||
|
|
3c1db7d2f7 | ||
|
|
626507093a | ||
|
|
588b3d14a3 | ||
|
|
815efa7791 | ||
|
|
97a691ce2f | ||
|
|
9d948f2c2b | ||
|
|
0b87108174 | ||
|
|
7fc7809cfc | ||
|
|
c30be20e49 | ||
|
|
25c64db0a1 | ||
|
|
676e9c6593 | ||
|
|
d459859361 | ||
|
|
2be0cef446 | ||
|
|
294db93fde | ||
|
|
7f971f7173 | ||
|
|
5c7b59524d | ||
|
|
5133e5910e | ||
|
|
1512c350df | ||
|
|
a5fc7891a6 | ||
|
|
3eb9633231 | ||
|
|
ac67b48247 | ||
|
|
81b65ea646 | ||
|
|
45c1f6bc27 | ||
|
|
0d31e5c8b1 | ||
|
|
6378abf454 | ||
|
|
f8fcaadb5b | ||
|
|
0b5fd3ee76 | ||
|
|
d010cb7e42 | ||
|
|
71136d7347 | ||
|
|
a18c552ddf | ||
|
|
9656878ef3 | ||
|
|
7ded7de39a | ||
|
|
0f74e89b44 | ||
|
|
953c40b083 | ||
|
|
271b0287d8 | ||
|
|
96a8a2a8b8 | ||
|
|
75306f658f | ||
|
|
325d9a0b86 | ||
|
|
a02493fbaa | ||
|
|
9c27d691dd | ||
|
|
935bd01f59 | ||
|
|
eeb5d669f6 | ||
|
|
78daa2eb62 | ||
|
|
40eda05a30 | ||
|
|
9f9de8c43b | ||
|
|
a910c8ccd8 | ||
|
|
43bda2d4a4 | ||
|
|
c7033dd757 | ||
|
|
5673a9bace | ||
|
|
34ff764515 | ||
|
|
1b3a009da7 | ||
|
|
a49002bb2c | ||
|
|
7342fc2307 | ||
|
|
9867a3bd60 | ||
|
|
5ffb9eaa5b | ||
|
|
b05b688267 | ||
|
|
f3d7f85063 | ||
|
|
de969a9dab | ||
|
|
59fd38bbf8 | ||
|
|
06dc6df270 | ||
|
|
ff8460b361 | ||
|
|
674d272eaa | ||
|
|
c3e00c279d | ||
|
|
175d920c94 | ||
|
|
04920883ea | ||
|
|
5e44b0b9d5 | ||
|
|
23c1a1dab8 | ||
|
|
f5d054b93c | ||
|
|
d25ae5e0a9 | ||
|
|
c42a51dcbb | ||
|
|
da3fd92b31 | ||
|
|
4a45ba3c14 | ||
|
|
dbc8bed234 | ||
|
|
f8b4190a11 | ||
|
|
479972e3ae | ||
|
|
3ea28b0afb | ||
|
|
2b3cc28966 | ||
|
|
751642b39a | ||
|
|
d6c2c821a4 | ||
|
|
dfc65b95f7 | ||
|
|
b45d922463 | ||
|
|
f87ee3fcf9 | ||
|
|
e0927cd763 | ||
|
|
21099eabfa | ||
|
|
abbd2e6b72 | ||
|
|
5b7ddbbb01 | ||
|
|
6352fbb3b2 | ||
|
|
d3f49334e2 | ||
|
|
c4356171b3 | ||
|
|
5c5625911d | ||
|
|
6a10cc9c55 | ||
|
|
6b317f918e | ||
|
|
08b528dc4f | ||
|
|
fc886a5a47 | ||
|
|
0cb90e2e55 | ||
|
|
64113a69b4 | ||
|
|
544bb7459c | ||
|
|
578a50b464 | ||
|
|
3d4081d0af | ||
|
|
b763b81f56 | ||
|
|
947dae4900 | ||
|
|
debd1d7d54 | ||
|
|
cba0d04000 | ||
|
|
695e7e6da0 | ||
|
|
4cd4bfa1d7 | ||
|
|
16b400964b | ||
|
|
cf2d02c0dd | ||
|
|
0fcd0de0d1 | ||
|
|
748a35774f | ||
|
|
a52a3e38ed | ||
|
|
ee0cef06a6 | ||
|
|
0e5a113a0c | ||
|
|
a1ccd44013 | ||
|
|
4d91e50d6d | ||
|
|
120668c7bc | ||
|
|
d81ccde569 | ||
|
|
e8581b4adb |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,8 +2,8 @@ out
|
|||||||
*.zip
|
*.zip
|
||||||
*.jks
|
*.jks
|
||||||
*.apk
|
*.apk
|
||||||
config.prop
|
/config.prop
|
||||||
update.sh
|
/update.sh
|
||||||
|
|
||||||
# Built binaries
|
# Built binaries
|
||||||
native/out
|
native/out
|
||||||
|
|||||||
@@ -30,13 +30,12 @@ Furthermore, Magisk provides a **Systemless Interface** to alter the system (or
|
|||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
Default string resources for Magisk Manager are scattered throughout
|
Default string resources for Magisk Manager and its stub APK are located here:
|
||||||
|
|
||||||
- `app/src/main/res/values/strings.xml`
|
- `app/src/main/res/values/strings.xml`
|
||||||
- `stub/src/main/res/values/strings.xml`
|
- `stub/src/main/res/values/strings.xml`
|
||||||
- `shared/src/main/res/values/strings.xml`
|
|
||||||
|
|
||||||
Translate each and place them in the respective locations (`<module>/src/main/res/values-<lang>/strings.xml`).
|
Translate each and place them in the respective locations (`[module]/src/main/res/values-[lang]/strings.xml`).
|
||||||
|
|
||||||
## Signature Verification
|
## Signature Verification
|
||||||
|
|
||||||
|
|||||||
@@ -35,13 +35,12 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
exclude '/META-INF/*.version'
|
exclude '/META-INF/**'
|
||||||
exclude '/META-INF/*.kotlin_module'
|
|
||||||
exclude '/META-INF/rxkotlin.properties'
|
|
||||||
exclude '/androidsupportmultidexversion.txt'
|
exclude '/androidsupportmultidexversion.txt'
|
||||||
exclude '/org/bouncycastle/**'
|
exclude '/org/bouncycastle/**'
|
||||||
exclude '/kotlin/**'
|
exclude '/kotlin/**'
|
||||||
exclude '/kotlinx/**'
|
exclude '/kotlinx/**'
|
||||||
|
exclude '/okhttp3/**'
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
@@ -60,38 +59,50 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'com.github.topjohnwu:jtar:1.0.0'
|
implementation 'com.github.topjohnwu:jtar:1.0.0'
|
||||||
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.ncapdevi:frag-nav:3.2.0'
|
implementation 'com.ncapdevi:frag-nav:3.2.0'
|
||||||
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.6'
|
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.6'
|
||||||
|
|
||||||
def vMarkwon = '3.1.0'
|
implementation 'io.reactivex.rxjava2:rxjava:2.2.13'
|
||||||
implementation "ru.noties.markwon:core:${vMarkwon}"
|
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
|
||||||
implementation "ru.noties.markwon:html:${vMarkwon}"
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||||
implementation "ru.noties.markwon:image-svg:${vMarkwon}"
|
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:${vKotlin}"
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${vKotlin}"
|
||||||
|
|
||||||
|
def vBAdapt = '3.1.1'
|
||||||
|
def bindingAdapter = 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter'
|
||||||
|
implementation "${bindingAdapter}:${vBAdapt}"
|
||||||
|
implementation "${bindingAdapter}-recyclerview:${vBAdapt}"
|
||||||
|
|
||||||
|
def vMarkwon = '4.2.0'
|
||||||
|
implementation "io.noties.markwon:core:${vMarkwon}"
|
||||||
|
implementation "io.noties.markwon:html:${vMarkwon}"
|
||||||
|
implementation "io.noties.markwon:image:${vMarkwon}"
|
||||||
|
implementation 'com.caverock:androidsvg:1.4'
|
||||||
|
|
||||||
def vLibsu = '2.5.1'
|
def vLibsu = '2.5.1'
|
||||||
implementation "com.github.topjohnwu.libsu:core:${vLibsu}"
|
implementation "com.github.topjohnwu.libsu:core:${vLibsu}"
|
||||||
implementation "com.github.topjohnwu.libsu:io:${vLibsu}"
|
implementation "com.github.topjohnwu.libsu:io:${vLibsu}"
|
||||||
|
|
||||||
def vKoin = "2.0.1"
|
def vKoin = '2.0.1'
|
||||||
implementation "org.koin:koin-core:${vKoin}"
|
implementation "org.koin:koin-core:${vKoin}"
|
||||||
implementation "org.koin:koin-android:${vKoin}"
|
implementation "org.koin:koin-android:${vKoin}"
|
||||||
implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
|
implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
|
||||||
|
|
||||||
def vRetrofit = '2.6.1'
|
def vRetrofit = '2.6.2'
|
||||||
implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
|
implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
|
||||||
implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
|
implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
|
||||||
implementation "com.squareup.retrofit2:converter-scalars:${vRetrofit}"
|
implementation "com.squareup.retrofit2:converter-scalars:${vRetrofit}"
|
||||||
implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
|
implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
|
||||||
|
|
||||||
def vOkHttp = '3.12.2'
|
def vOkHttp = '3.12.6'
|
||||||
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
|
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
|
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
|
||||||
|
|
||||||
def vMoshi = "1.8.0"
|
def vMoshi = '1.9.2'
|
||||||
implementation "com.squareup.moshi:moshi:${vMoshi}"
|
implementation "com.squareup.moshi:moshi:${vMoshi}"
|
||||||
|
|
||||||
def vKotshi = "2.0.1"
|
def vKotshi = '2.0.2'
|
||||||
implementation "se.ansman.kotshi:api:${vKotshi}"
|
implementation "se.ansman.kotshi:api:${vKotshi}"
|
||||||
kapt "se.ansman.kotshi:compiler:${vKotshi}"
|
kapt "se.ansman.kotshi:compiler:${vKotshi}"
|
||||||
|
|
||||||
@@ -100,20 +111,25 @@ dependencies {
|
|||||||
replacedBy('com.github.topjohnwu:room-runtime')
|
replacedBy('com.github.topjohnwu:room-runtime')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def vRoom = "2.1.0"
|
def vRoom = '2.2.2'
|
||||||
implementation "com.github.topjohnwu:room-runtime:${vRoom}"
|
implementation "com.github.topjohnwu:room-runtime:${vRoom}"
|
||||||
|
implementation "androidx.room:room-rxjava2:${vRoom}"
|
||||||
kapt "androidx.room:room-compiler:${vRoom}"
|
kapt "androidx.room:room-compiler:${vRoom}"
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlinVer}"
|
def vNav = '2.1.0'
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVer}"
|
implementation "androidx.navigation:navigation-fragment-ktx:${vNav}"
|
||||||
|
implementation "androidx.navigation:navigation-ui-ktx:${vNav}"
|
||||||
|
|
||||||
|
implementation 'androidx.biometric:biometric:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.browser:browser:1.0.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03'
|
||||||
implementation 'androidx.preference:preference:1.1.0'
|
implementation 'androidx.preference:preference:1.1.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0-beta04'
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
|
implementation 'androidx.fragment:fragment-ktx:1.2.0-rc03'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.work:work-runtime:2.2.0'
|
implementation 'androidx.work:work-runtime:2.2.0'
|
||||||
implementation 'androidx.transition:transition:1.2.0-rc01'
|
implementation 'androidx.transition:transition:1.3.0-rc02'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
implementation 'com.google.android.material:material:1.1.0-alpha10'
|
implementation 'androidx.core:core-ktx:1.1.0'
|
||||||
|
implementation 'com.google.android.material:material:1.2.0-alpha02'
|
||||||
}
|
}
|
||||||
|
|||||||
8
app/proguard-rules.pro
vendored
8
app/proguard-rules.pro
vendored
@@ -29,10 +29,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
# DelegateWorker
|
# DelegateWorker
|
||||||
-keep,allowobfuscation class * extends com.topjohnwu.magisk.model.worker.DelegateWorker
|
-keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker
|
||||||
|
|
||||||
# BootSigner
|
# BootSigner
|
||||||
-keepclassmembers class com.topjohnwu.signing.BootSigner { *; }
|
-keep class a.a { *; }
|
||||||
|
|
||||||
|
# Workaround R8 bug
|
||||||
|
-keep,allowobfuscation class com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
||||||
|
-keepclassmembers class a.e { *; }
|
||||||
|
|
||||||
# Strip logging
|
# Strip logging
|
||||||
-assumenosideeffects class timber.log.Timber.Tree { *; }
|
-assumenosideeffects class timber.log.Timber.Tree { *; }
|
||||||
|
|||||||
5
app/res-ids.txt
Normal file
5
app/res-ids.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
com.topjohnwu.magisk:color/xxxxxxxx = 0x7f010000
|
||||||
|
com.topjohnwu.magisk:drawable/xxxxxxxx = 0x7f020000
|
||||||
|
com.topjohnwu.magisk:string/xxxxxxxx = 0x7f030000
|
||||||
|
com.topjohnwu.magisk:style/xxxxxxxx = 0x7f040000
|
||||||
|
com.topjohnwu.magisk:xml/xxxxxxxx = 0x7f050000
|
||||||
@@ -1,59 +1,54 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.topjohnwu.magisk">
|
package="com.topjohnwu.magisk">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<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.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="a.e"
|
android:name="a.e"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:theme="@style/MagiskTheme"
|
|
||||||
android:usesCleartextTraffic="true"
|
|
||||||
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
|
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
|
||||||
|
|
||||||
<!-- Activities -->
|
<!-- Splash -->
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="a.b"
|
|
||||||
android:configChanges="orientation|screenSize"
|
|
||||||
android:exported="true" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name="a.c"
|
android:name="a.c"
|
||||||
android:configChanges="orientation|screenSize"
|
|
||||||
android:exported="true"
|
|
||||||
android:theme="@style/SplashTheme">
|
android:theme="@style/SplashTheme">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
|
||||||
android:name="a.f"
|
<!-- Main -->
|
||||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
<activity android:name="a.b" />
|
||||||
android:screenOrientation="nosensor"
|
|
||||||
android:theme="@style/MagiskTheme.Flashing" />
|
<!-- Flashing -->
|
||||||
|
<activity android:name="a.f" />
|
||||||
|
|
||||||
<!-- Superuser -->
|
<!-- Superuser -->
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="a.m"
|
android:name="a.m"
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/MagiskTheme.SU" />
|
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||||
|
tools:ignore="AppLinkUrlError">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<!-- Receiver -->
|
<!-- Receiver -->
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name="a.h"
|
android:name="a.h"
|
||||||
android:directBootAware="true">
|
android:directBootAware="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.REBOOT" />
|
||||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -64,16 +59,30 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<!-- Service -->
|
<!-- DownloadService -->
|
||||||
|
<service android:name="a.j" />
|
||||||
<service android:name="a.j"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<!-- Hardcode GMS version -->
|
<!-- Hardcode GMS version -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.gms.version"
|
android:name="com.google.android.gms.version"
|
||||||
android:value="12451000" />
|
android:value="12451000" />
|
||||||
|
|
||||||
|
<!-- Initialize WorkManager on-demand -->
|
||||||
|
<provider
|
||||||
|
android:name="androidx.work.impl.WorkManagerInitializer"
|
||||||
|
android:authorities="${applicationId}.workmanager-init"
|
||||||
|
tools:node="remove" />
|
||||||
|
|
||||||
|
<!-- We don't invalidate Room -->
|
||||||
|
<service
|
||||||
|
android:name="androidx.room.MultiInstanceInvalidationService"
|
||||||
|
tools:node="remove"/>
|
||||||
|
|
||||||
|
<!-- We don't use Device Credentials -->
|
||||||
|
<activity
|
||||||
|
android:name="androidx.biometric.DeviceCredentialHandlerActivity"
|
||||||
|
tools:node="remove" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -3,11 +3,18 @@ package a;
|
|||||||
import com.topjohnwu.magisk.utils.PatchAPK;
|
import com.topjohnwu.magisk.utils.PatchAPK;
|
||||||
import com.topjohnwu.signing.BootSigner;
|
import com.topjohnwu.signing.BootSigner;
|
||||||
|
|
||||||
import androidx.annotation.Keep;
|
public class a {
|
||||||
|
|
||||||
@Keep
|
@Deprecated
|
||||||
public class a extends BootSigner {
|
|
||||||
public static boolean patchAPK(String in, String out, String pkg) {
|
public static boolean patchAPK(String in, String out, String pkg) {
|
||||||
return PatchAPK.patch(in, out, pkg);
|
return PatchAPK.patch(in, out, pkg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean patchAPK(String in, String out, String pkg, String label) {
|
||||||
|
return PatchAPK.patch(in, out, pkg, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
BootSigner.main(args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,11 @@ package a;
|
|||||||
import com.topjohnwu.magisk.App;
|
import com.topjohnwu.magisk.App;
|
||||||
|
|
||||||
public class e extends App {
|
public class e extends App {
|
||||||
/* stub */
|
public e() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public e(Object o) {
|
||||||
|
super(o);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ 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 com.topjohnwu.magisk.base.DelegateWorker;
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
|
||||||
|
|||||||
@@ -6,57 +6,85 @@ import android.content.res.Configuration
|
|||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.multidex.MultiDex
|
import androidx.multidex.MultiDex
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
|
import androidx.work.WorkManager
|
||||||
import androidx.work.impl.WorkDatabase
|
import androidx.work.impl.WorkDatabase
|
||||||
import androidx.work.impl.WorkDatabase_Impl
|
import androidx.work.impl.WorkDatabase_Impl
|
||||||
import com.topjohnwu.magisk.data.database.RepoDatabase
|
import com.topjohnwu.magisk.data.database.RepoDatabase
|
||||||
import com.topjohnwu.magisk.data.database.RepoDatabase_Impl
|
import com.topjohnwu.magisk.data.database.RepoDatabase_Impl
|
||||||
|
import com.topjohnwu.magisk.data.database.SuLogDatabase
|
||||||
|
import com.topjohnwu.magisk.data.database.SuLogDatabase_Impl
|
||||||
import com.topjohnwu.magisk.di.ActivityTracker
|
import com.topjohnwu.magisk.di.ActivityTracker
|
||||||
import com.topjohnwu.magisk.di.koinModules
|
import com.topjohnwu.magisk.di.koinModules
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.net.Networking
|
import com.topjohnwu.magisk.extensions.unwrap
|
||||||
import com.topjohnwu.magisk.utils.LocaleManager
|
import com.topjohnwu.magisk.utils.RootInit
|
||||||
import com.topjohnwu.magisk.utils.RootUtils
|
import com.topjohnwu.magisk.utils.SuHandler
|
||||||
|
import com.topjohnwu.magisk.utils.updateConfig
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.core.context.startKoin
|
import org.koin.core.context.startKoin
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
open class App : Application() {
|
open class App() : Application() {
|
||||||
|
|
||||||
|
constructor(o: Any) : this() {
|
||||||
|
Info.stub = DynAPK.load(o)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||||
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX)
|
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX)
|
||||||
Shell.Config.verboseLogging(BuildConfig.DEBUG)
|
Shell.Config.verboseLogging(BuildConfig.DEBUG)
|
||||||
Shell.Config.addInitializers(RootUtils::class.java)
|
Shell.Config.addInitializers(RootInit::class.java)
|
||||||
Shell.Config.setTimeout(2)
|
Shell.Config.setTimeout(2)
|
||||||
|
FileProvider.callHandler = SuHandler
|
||||||
Room.setFactory {
|
Room.setFactory {
|
||||||
when (it) {
|
when (it) {
|
||||||
WorkDatabase::class.java -> WorkDatabase_Impl()
|
WorkDatabase::class.java -> WorkDatabase_Impl()
|
||||||
RepoDatabase::class.java -> RepoDatabase_Impl()
|
RepoDatabase::class.java -> RepoDatabase_Impl()
|
||||||
|
SuLogDatabase::class.java -> SuLogDatabase_Impl()
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
override fun attachBaseContext(base: Context) {
|
||||||
super.attachBaseContext(base)
|
// Basic setup
|
||||||
if (BuildConfig.DEBUG)
|
if (BuildConfig.DEBUG)
|
||||||
MultiDex.install(base)
|
MultiDex.install(base)
|
||||||
Timber.plant(Timber.DebugTree())
|
Timber.plant(Timber.DebugTree())
|
||||||
|
|
||||||
|
// Some context magic
|
||||||
|
val app: Application
|
||||||
|
val impl: Context
|
||||||
|
if (base is Application) {
|
||||||
|
app = base
|
||||||
|
impl = base.baseContext
|
||||||
|
} else {
|
||||||
|
app = this
|
||||||
|
impl = base
|
||||||
|
}
|
||||||
|
val wrapped = impl.wrap()
|
||||||
|
super.attachBaseContext(wrapped)
|
||||||
|
|
||||||
|
// Normal startup
|
||||||
startKoin {
|
startKoin {
|
||||||
androidContext(this@App)
|
androidContext(wrapped)
|
||||||
modules(koinModules)
|
modules(koinModules)
|
||||||
}
|
}
|
||||||
|
ResourceMgr.init(impl)
|
||||||
|
app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
|
||||||
|
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
|
||||||
|
}
|
||||||
|
|
||||||
registerActivityLifecycleCallbacks(get<ActivityTracker>())
|
// This is required as some platforms expect ContextImpl
|
||||||
|
override fun getBaseContext(): Context {
|
||||||
Networking.init(base)
|
return super.getBaseContext().unwrap()
|
||||||
LocaleManager.setLocale(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
super.onConfigurationChanged(newConfig)
|
resources.updateConfig(newConfig)
|
||||||
LocaleManager.setLocale(this)
|
if (!isRunningAsStub)
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,9 +11,8 @@ import com.topjohnwu.magisk.data.repository.DBConfig
|
|||||||
import com.topjohnwu.magisk.di.Protected
|
import com.topjohnwu.magisk.di.Protected
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
import com.topjohnwu.magisk.extensions.inject
|
||||||
import com.topjohnwu.magisk.extensions.packageName
|
|
||||||
import com.topjohnwu.magisk.model.preference.PreferenceModel
|
import com.topjohnwu.magisk.model.preference.PreferenceModel
|
||||||
import com.topjohnwu.magisk.utils.FingerprintHelper
|
import com.topjohnwu.magisk.utils.BiometricHelper
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.io.SuFile
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
@@ -32,8 +31,9 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
const val ROOT_ACCESS = "root_access"
|
const val ROOT_ACCESS = "root_access"
|
||||||
const val SU_MULTIUSER_MODE = "multiuser_mode"
|
const val SU_MULTIUSER_MODE = "multiuser_mode"
|
||||||
const val SU_MNT_NS = "mnt_ns"
|
const val SU_MNT_NS = "mnt_ns"
|
||||||
|
const val SU_BIOMETRIC = "su_biometric"
|
||||||
const val SU_MANAGER = "requester"
|
const val SU_MANAGER = "requester"
|
||||||
const val SU_FINGERPRINT = "su_fingerprint"
|
const val KEYSTORE = "keystore"
|
||||||
|
|
||||||
// prefs
|
// prefs
|
||||||
const val SU_REQUEST_TIMEOUT = "su_request_timeout"
|
const val SU_REQUEST_TIMEOUT = "su_request_timeout"
|
||||||
@@ -48,6 +48,7 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
const val REPO_ORDER = "repo_order"
|
const val REPO_ORDER = "repo_order"
|
||||||
const val SHOW_SYSTEM_APP = "show_system"
|
const val SHOW_SYSTEM_APP = "show_system"
|
||||||
const val DOWNLOAD_PATH = "download_path"
|
const val DOWNLOAD_PATH = "download_path"
|
||||||
|
const val BOOT_ID = "boot_id"
|
||||||
|
|
||||||
// system state
|
// system state
|
||||||
const val MAGISKHIDE = "magiskhide"
|
const val MAGISKHIDE = "magiskhide"
|
||||||
@@ -97,9 +98,16 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val defaultChannel =
|
private val defaultChannel =
|
||||||
if (Utils.isCanary) Value.CANARY_DEBUG_CHANNEL
|
if (Utils.isCanary) {
|
||||||
|
if (BuildConfig.DEBUG)
|
||||||
|
Value.CANARY_DEBUG_CHANNEL
|
||||||
|
else
|
||||||
|
Value.CANARY_CHANNEL
|
||||||
|
}
|
||||||
else Value.DEFAULT_CHANNEL
|
else Value.DEFAULT_CHANNEL
|
||||||
|
|
||||||
|
var bootId by preference(Key.BOOT_ID, "")
|
||||||
|
|
||||||
var downloadPath by preference(Key.DOWNLOAD_PATH, Environment.DIRECTORY_DOWNLOADS)
|
var downloadPath by preference(Key.DOWNLOAD_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||||
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
|
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
|
||||||
|
|
||||||
@@ -121,18 +129,25 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
var rootMode by dbSettings(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB)
|
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 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 suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY)
|
||||||
var suFingerprint by dbSettings(Key.SU_FINGERPRINT, false)
|
var suBiometric by dbSettings(Key.SU_BIOMETRIC, false)
|
||||||
var suManager by dbStrings(Key.SU_MANAGER, "", true)
|
var suManager by dbStrings(Key.SU_MANAGER, "", true)
|
||||||
|
var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true)
|
||||||
|
|
||||||
// Always return a path in external storage where we can write
|
// Always return a path in external storage where we can write
|
||||||
val downloadDirectory get() =
|
val downloadDirectory get() =
|
||||||
Utils.ensureDownloadPath(downloadPath) ?: get<Context>().getExternalFilesDir(null)!!
|
Utils.ensureDownloadPath(downloadPath) ?: get<Context>().getExternalFilesDir(null)!!
|
||||||
|
|
||||||
fun initialize() = prefs.edit {
|
private const val SU_FINGERPRINT = "su_fingerprint"
|
||||||
|
|
||||||
|
fun initialize() = prefs.also {
|
||||||
|
if (it.getBoolean(SU_FINGERPRINT, false)) {
|
||||||
|
suBiometric = true
|
||||||
|
}
|
||||||
|
}.edit {
|
||||||
parsePrefs(this)
|
parsePrefs(this)
|
||||||
|
|
||||||
if (!prefs.contains(Key.UPDATE_CHANNEL))
|
// Legacy stuff
|
||||||
putString(Key.UPDATE_CHANNEL, defaultChannel.toString())
|
remove(SU_FINGERPRINT)
|
||||||
|
|
||||||
// Get actual state
|
// Get actual state
|
||||||
putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
|
putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
|
||||||
@@ -141,13 +156,16 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
putString(Key.ROOT_ACCESS, rootMode.toString())
|
putString(Key.ROOT_ACCESS, rootMode.toString())
|
||||||
putString(Key.SU_MNT_NS, suMntNamespaceMode.toString())
|
putString(Key.SU_MNT_NS, suMntNamespaceMode.toString())
|
||||||
putString(Key.SU_MULTIUSER_MODE, suMultiuserMode.toString())
|
putString(Key.SU_MULTIUSER_MODE, suMultiuserMode.toString())
|
||||||
putBoolean(Key.SU_FINGERPRINT, FingerprintHelper.useFingerprint())
|
putBoolean(Key.SU_BIOMETRIC, BiometricHelper.isEnabled)
|
||||||
|
}.also {
|
||||||
|
if (!prefs.contains(Key.UPDATE_CHANNEL))
|
||||||
|
prefs.edit().putString(Key.UPDATE_CHANNEL, defaultChannel.toString()).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parsePrefs(editor: SharedPreferences.Editor) = editor.apply {
|
private fun parsePrefs(editor: SharedPreferences.Editor) = editor.apply {
|
||||||
val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS)
|
val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS)
|
||||||
if (config.exists()) runCatching {
|
if (config.exists()) runCatching {
|
||||||
val input = SuFileInputStream(config).buffered()
|
val input = SuFileInputStream(config)
|
||||||
val parser = Xml.newPullParser()
|
val parser = Xml.newPullParser()
|
||||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||||
parser.setInput(input, "UTF-8")
|
parser.setInput(input, "UTF-8")
|
||||||
@@ -198,11 +216,12 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
fun export() {
|
fun export() {
|
||||||
// Flush prefs to disk
|
// Flush prefs to disk
|
||||||
prefs.edit().commit()
|
prefs.edit().commit()
|
||||||
|
val context = get<Context>(Protected)
|
||||||
val xml = File(
|
val xml = File(
|
||||||
"${get<Context>(Protected).filesDir.parent}/shared_prefs",
|
"${context.filesDir.parent}/shared_prefs",
|
||||||
"${packageName}_preferences.xml"
|
"${context.packageName}_preferences.xml"
|
||||||
)
|
)
|
||||||
Shell.su("cat $xml > /data/adb/${Const.MANAGER_CONFIGS}").exec()
|
Shell.su("cat $xml > /data/adb/${Const.MANAGER_CONFIGS}").exec()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ object Const {
|
|||||||
|
|
||||||
// Versions
|
// Versions
|
||||||
const val SNET_EXT_VER = 13
|
const val SNET_EXT_VER = 13
|
||||||
const val SNET_REVISION = "5adbc435ce93ded953c30ebe587edfd50b5503bc"
|
const val SNET_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
|
||||||
const val BOOTCTL_REVISION = "9c5dfc1b8245c0b5b524901ef0ff0f8335757b77"
|
const val BOOTCTL_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
const val ANDROID_MANIFEST = "AndroidManifest.xml"
|
const val ANDROID_MANIFEST = "AndroidManifest.xml"
|
||||||
@@ -22,8 +22,11 @@ object Const {
|
|||||||
const val MANAGER_CONFIGS = ".tmp.magisk.config"
|
const val MANAGER_CONFIGS = ".tmp.magisk.config"
|
||||||
val USER_ID = Process.myUid() / 100000
|
val USER_ID = Process.myUid() / 100000
|
||||||
|
|
||||||
object MagiskVersion {
|
object Version {
|
||||||
const val MIN_SUPPORT = 18000
|
const val MIN_VERSION = "v18.0"
|
||||||
|
const val MIN_VERCODE = 18000
|
||||||
|
const val CONNECT_MODE = 20100
|
||||||
|
const val PROVIDER_CONNECT = 20102
|
||||||
}
|
}
|
||||||
|
|
||||||
object ID {
|
object ID {
|
||||||
|
|||||||
155
app/src/main/java/com/topjohnwu/magisk/Hacks.kt
Normal file
155
app/src/main/java/com/topjohnwu/magisk/Hacks.kt
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
|
package com.topjohnwu.magisk
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.job.JobInfo
|
||||||
|
import android.app.job.JobScheduler
|
||||||
|
import android.app.job.JobWorkItem
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.res.AssetManager
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.content.res.Resources
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import com.topjohnwu.magisk.extensions.forceGetDeclaredField
|
||||||
|
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
|
||||||
|
import com.topjohnwu.magisk.utils.refreshLocale
|
||||||
|
import com.topjohnwu.magisk.utils.updateConfig
|
||||||
|
|
||||||
|
fun AssetManager.addAssetPath(path: String) {
|
||||||
|
DynAPK.addAssetPath(this, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.wrap(global: Boolean = true): Context
|
||||||
|
= if (global) GlobalResContext(this) else ResContext(this)
|
||||||
|
|
||||||
|
fun Context.wrapJob(): Context = object : GlobalResContext(this) {
|
||||||
|
|
||||||
|
override fun getApplicationContext(): Context {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
override fun getSystemService(name: String): Any? {
|
||||||
|
return if (!isRunningAsStub) super.getSystemService(name) else
|
||||||
|
when (name) {
|
||||||
|
Context.JOB_SCHEDULER_SERVICE ->
|
||||||
|
JobSchedulerWrapper(super.getSystemService(name) as JobScheduler)
|
||||||
|
else -> super.getSystemService(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Class<*>.cmp(pkg: String): ComponentName {
|
||||||
|
val name = ClassMap[this].name
|
||||||
|
return ComponentName(pkg, Info.stub?.classToComponent?.get(name) ?: name)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> Context.intent() = Intent().setComponent(T::class.java.cmp(packageName))
|
||||||
|
|
||||||
|
private open class GlobalResContext(base: Context) : ContextWrapper(base) {
|
||||||
|
open val mRes: Resources get() = ResourceMgr.resource
|
||||||
|
|
||||||
|
override fun getResources(): Resources {
|
||||||
|
return mRes
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getClassLoader(): ClassLoader {
|
||||||
|
return javaClass.classLoader!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createConfigurationContext(config: Configuration): Context {
|
||||||
|
return ResContext(super.createConfigurationContext(config))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ResContext(base: Context) : GlobalResContext(base) {
|
||||||
|
override val mRes by lazy { base.resources.patch() }
|
||||||
|
|
||||||
|
private fun Resources.patch(): Resources {
|
||||||
|
updateConfig()
|
||||||
|
if (isRunningAsStub)
|
||||||
|
assets.addAssetPath(ResourceMgr.resApk)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ResourceMgr {
|
||||||
|
|
||||||
|
lateinit var resource: Resources
|
||||||
|
lateinit var resApk: String
|
||||||
|
|
||||||
|
fun init(context: Context) {
|
||||||
|
resource = context.resources
|
||||||
|
refreshLocale()
|
||||||
|
if (isRunningAsStub) {
|
||||||
|
resApk = DynAPK.current(context).path
|
||||||
|
resource.assets.addAssetPath(resApk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(28)
|
||||||
|
private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler() {
|
||||||
|
|
||||||
|
override fun schedule(job: JobInfo): Int {
|
||||||
|
return base.schedule(job.patch())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enqueue(job: JobInfo, work: JobWorkItem): Int {
|
||||||
|
return base.enqueue(job.patch(), work)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancel(jobId: Int) {
|
||||||
|
base.cancel(jobId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancelAll() {
|
||||||
|
base.cancelAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAllPendingJobs(): List<JobInfo> {
|
||||||
|
return base.allPendingJobs
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPendingJob(jobId: Int): JobInfo? {
|
||||||
|
return base.getPendingJob(jobId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JobInfo.patch(): JobInfo {
|
||||||
|
// We need to swap out the service of JobInfo
|
||||||
|
val name = service.className
|
||||||
|
val component = ComponentName(
|
||||||
|
service.packageName,
|
||||||
|
Info.stub!!.classToComponent[name] ?: name)
|
||||||
|
|
||||||
|
javaClass.forceGetDeclaredField("service")?.set(this, component)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
ProcessPhoenix::class.java to a.r::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
operator fun get(c: Class<*>) = map.getOrElse(c) { c }
|
||||||
|
}
|
||||||
@@ -1,26 +1,77 @@
|
|||||||
package com.topjohnwu.magisk
|
package com.topjohnwu.magisk
|
||||||
|
|
||||||
|
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
||||||
|
import com.topjohnwu.magisk.extensions.get
|
||||||
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.model.entity.UpdateInfo
|
import com.topjohnwu.magisk.model.entity.UpdateInfo
|
||||||
|
import com.topjohnwu.magisk.utils.CachedValue
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
val isRunningAsStub get() = Info.stub != null
|
||||||
|
|
||||||
object Info {
|
object Info {
|
||||||
|
|
||||||
var magiskVersionCode = -1
|
val envRef = CachedValue { loadState() }
|
||||||
|
|
||||||
var magiskVersionString = ""
|
val env by envRef // Local
|
||||||
|
var remote = UpdateInfo() // Remote
|
||||||
var remote = UpdateInfo()
|
var stub: DynAPK.Data? = null // Stub
|
||||||
|
|
||||||
var keepVerity = false
|
var keepVerity = false
|
||||||
var keepEnc = false
|
var keepEnc = false
|
||||||
var recovery = false
|
var recovery = false
|
||||||
|
|
||||||
fun loadMagiskInfo() {
|
val isConnected by lazy {
|
||||||
runCatching {
|
KObservableField(false).also { field ->
|
||||||
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":".toRegex())[0]
|
ReactiveNetwork.observeNetworkConnectivity(get())
|
||||||
magiskVersionCode = ShellUtils.fastCmd("magisk -V").toInt()
|
.subscribeK {
|
||||||
Config.magiskHide = Shell.su("magiskhide --status").exec().isSuccess
|
field.value = it.available()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isNewReboot by lazy {
|
||||||
|
try {
|
||||||
|
FileInputStream("/proc/sys/kernel/random/boot_id").bufferedReader().use {
|
||||||
|
val id = it.readLine()
|
||||||
|
if (id != Config.bootId) {
|
||||||
|
Config.bootId = id
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadState() = runCatching {
|
||||||
|
val str = ShellUtils.fastCmd("magisk -v").split(":".toRegex())[0]
|
||||||
|
val code = ShellUtils.fastCmd("magisk -V").toInt()
|
||||||
|
val hide = Shell.su("magiskhide --status").exec().isSuccess
|
||||||
|
Env(str, code, hide)
|
||||||
|
}.getOrElse { Env() }
|
||||||
|
|
||||||
|
class Env(
|
||||||
|
val magiskVersionString: String = "",
|
||||||
|
code: Int = -1,
|
||||||
|
hide: Boolean = false
|
||||||
|
) {
|
||||||
|
val magiskHide get() = Config.magiskHide
|
||||||
|
val magiskVersionCode = when (code) {
|
||||||
|
in Int.MIN_VALUE..Const.Version.MIN_VERCODE -> -1
|
||||||
|
else -> if(Shell.rootAccess()) code else -1
|
||||||
|
}
|
||||||
|
val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE
|
||||||
|
val isActive = magiskVersionCode >= 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
Config.magiskHide = hide
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
124
app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt
Normal file
124
app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package com.topjohnwu.magisk.base
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.collection.SparseArrayCompat
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.extensions.set
|
||||||
|
import com.topjohnwu.magisk.model.events.EventHandler
|
||||||
|
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
||||||
|
import com.topjohnwu.magisk.utils.currentLocale
|
||||||
|
import com.topjohnwu.magisk.wrap
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
typealias RequestCallback = BaseActivity<*, *>.(Int, Intent?) -> Unit
|
||||||
|
|
||||||
|
abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
|
||||||
|
AppCompatActivity(), EventHandler {
|
||||||
|
|
||||||
|
protected lateinit var binding: Binding
|
||||||
|
protected abstract val layoutRes: Int
|
||||||
|
protected abstract val viewModel: ViewModel
|
||||||
|
protected open val themeRes: Int = R.style.MagiskTheme
|
||||||
|
protected open val snackbarView get() = binding.root
|
||||||
|
|
||||||
|
private val resultCallbacks by lazy { SparseArrayCompat<RequestCallback>() }
|
||||||
|
|
||||||
|
init {
|
||||||
|
val theme = if (Config.darkTheme) {
|
||||||
|
AppCompatDelegate.MODE_NIGHT_YES
|
||||||
|
} else {
|
||||||
|
AppCompatDelegate.MODE_NIGHT_NO
|
||||||
|
}
|
||||||
|
AppCompatDelegate.setDefaultNightMode(theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun applyOverrideConfiguration(config: Configuration?) {
|
||||||
|
// Force applying our preferred local
|
||||||
|
config?.setLocale(currentLocale)
|
||||||
|
super.applyOverrideConfiguration(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
super.attachBaseContext(base.wrap(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
setTheme(themeRes)
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
viewModel.viewEvents.observe(this, viewEventObserver)
|
||||||
|
|
||||||
|
binding = DataBindingUtil.setContentView<Binding>(this, layoutRes).apply {
|
||||||
|
setVariable(BR.viewModel, viewModel)
|
||||||
|
lifecycleOwner = this@BaseActivity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withPermissions(vararg permissions: String, builder: PermissionRequestBuilder.() -> Unit) {
|
||||||
|
val request = PermissionRequestBuilder().apply(builder).build()
|
||||||
|
val ungranted = permissions.filter {
|
||||||
|
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ungranted.isEmpty()) {
|
||||||
|
request.onSuccess()
|
||||||
|
} else {
|
||||||
|
val requestCode = Random.nextInt(256, 512)
|
||||||
|
resultCallbacks[requestCode] = { result, _ ->
|
||||||
|
if (result > 0)
|
||||||
|
request.onSuccess()
|
||||||
|
else
|
||||||
|
request.onFailure()
|
||||||
|
}
|
||||||
|
ActivityCompat.requestPermissions(this, ungranted.toTypedArray(), requestCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withExternalRW(builder: PermissionRequestBuilder.() -> Unit) {
|
||||||
|
withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, builder = builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
|
var success = true
|
||||||
|
for (res in grantResults) {
|
||||||
|
if (res != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
success = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultCallbacks[requestCode]?.apply {
|
||||||
|
resultCallbacks.remove(requestCode)
|
||||||
|
invoke(this@BaseActivity, if (success) 1 else -1, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
resultCallbacks[requestCode]?.apply {
|
||||||
|
resultCallbacks.remove(requestCode)
|
||||||
|
invoke(this@BaseActivity, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startActivityForResult(intent: Intent, requestCode: Int, listener: RequestCallback) {
|
||||||
|
resultCallbacks[requestCode] = listener
|
||||||
|
startActivityForResult(intent, requestCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
50
app/src/main/java/com/topjohnwu/magisk/base/BaseFragment.kt
Normal file
50
app/src/main/java/com/topjohnwu/magisk/base/BaseFragment.kt
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package com.topjohnwu.magisk.base
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.model.events.EventHandler
|
||||||
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
|
|
||||||
|
abstract class BaseFragment<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
|
||||||
|
Fragment(), EventHandler {
|
||||||
|
|
||||||
|
protected val activity get() = requireActivity() as BaseActivity<*, *>
|
||||||
|
protected lateinit var binding: Binding
|
||||||
|
protected abstract val layoutRes: Int
|
||||||
|
protected abstract val viewModel: ViewModel
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
viewModel.viewEvents.observe(this, viewEventObserver)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
binding = DataBindingUtil.inflate<Binding>(inflater, layoutRes, container, false).apply {
|
||||||
|
setVariable(BR.viewModel, viewModel)
|
||||||
|
lifecycleOwner = this@BaseFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onEventDispatched(event: ViewEvent) {
|
||||||
|
super.onEventDispatched(event)
|
||||||
|
activity.onEventDispatched(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onBackPressed(): Boolean = false
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.topjohnwu.magisk.base
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.preference.*
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
|
||||||
|
abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
protected val prefs: SharedPreferences by inject()
|
||||||
|
protected val activity get() = requireActivity() as BaseActivity<*, *>
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val v = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
prefs.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
prefs.unregisterOnSharedPreferenceChangeListener(this)
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setAllPreferencesToAvoidHavingExtraSpace(preference: Preference) {
|
||||||
|
preference.isIconSpaceReserved = false
|
||||||
|
if (preference is PreferenceGroup)
|
||||||
|
for (i in 0 until preference.preferenceCount)
|
||||||
|
setAllPreferencesToAvoidHavingExtraSpace(preference.getPreference(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
|
||||||
|
if (preferenceScreen != null)
|
||||||
|
setAllPreferencesToAvoidHavingExtraSpace(preferenceScreen)
|
||||||
|
super.setPreferenceScreen(preferenceScreen)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateAdapter(preferenceScreen: PreferenceScreen?): RecyclerView.Adapter<*> =
|
||||||
|
object : PreferenceGroupAdapter(preferenceScreen) {
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
override fun onPreferenceHierarchyChange(preference: Preference?) {
|
||||||
|
if (preference != null)
|
||||||
|
setAllPreferencesToAvoidHavingExtraSpace(preference)
|
||||||
|
super.onPreferenceHierarchyChange(preference)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/src/main/java/com/topjohnwu/magisk/base/BaseReceiver.kt
Normal file
17
app/src/main/java/com/topjohnwu/magisk/base/BaseReceiver.kt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package com.topjohnwu.magisk.base
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.content.Intent
|
||||||
|
import com.topjohnwu.magisk.wrap
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
|
|
||||||
|
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent {
|
||||||
|
|
||||||
|
final override fun onReceive(context: Context, intent: Intent?) {
|
||||||
|
onReceive(context.wrap() as ContextWrapper, intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun onReceive(context: ContextWrapper, intent: Intent?)
|
||||||
|
}
|
||||||
12
app/src/main/java/com/topjohnwu/magisk/base/BaseService.kt
Normal file
12
app/src/main/java/com/topjohnwu/magisk/base/BaseService.kt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package com.topjohnwu.magisk.base
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
|
import com.topjohnwu.magisk.wrap
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
|
|
||||||
|
abstract class BaseService : Service(), KoinComponent {
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
super.attachBaseContext(base.wrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.model.worker
|
package com.topjohnwu.magisk.base
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Network
|
import android.net.Network
|
||||||
@@ -1,32 +1,27 @@
|
|||||||
package com.topjohnwu.magisk.ui.base
|
package com.topjohnwu.magisk.base.viewmodel
|
||||||
|
|
||||||
import android.app.Activity
|
import com.topjohnwu.magisk.base.BaseActivity
|
||||||
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
import com.topjohnwu.magisk.extensions.doOnSubscribeUi
|
||||||
import com.skoumal.teanity.extensions.doOnSubscribeUi
|
|
||||||
import com.skoumal.teanity.extensions.subscribeK
|
|
||||||
import com.skoumal.teanity.util.KObservableField
|
|
||||||
import com.skoumal.teanity.viewmodel.LoadingViewModel
|
|
||||||
import com.topjohnwu.magisk.extensions.get
|
|
||||||
import com.topjohnwu.magisk.model.events.BackPressEvent
|
import com.topjohnwu.magisk.model.events.BackPressEvent
|
||||||
import com.topjohnwu.magisk.model.events.PermissionEvent
|
import com.topjohnwu.magisk.model.events.PermissionEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
||||||
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.subjects.PublishSubject
|
import io.reactivex.subjects.PublishSubject
|
||||||
|
import com.topjohnwu.magisk.Info.isConnected as gIsConnected
|
||||||
|
|
||||||
|
|
||||||
abstract class MagiskViewModel(
|
abstract class BaseViewModel(
|
||||||
initialState: State = State.LOADING
|
initialState: State = State.LOADING
|
||||||
) : LoadingViewModel(initialState) {
|
) : LoadingViewModel(initialState) {
|
||||||
|
|
||||||
val isConnected = KObservableField(true)
|
val isConnected = object : KObservableField<Boolean>(gIsConnected.value, gIsConnected) {
|
||||||
|
override fun get(): Boolean {
|
||||||
init {
|
return gIsConnected.value
|
||||||
ReactiveNetwork.observeNetworkConnectivity(get())
|
}
|
||||||
.subscribeK { isConnected.value = it.available() }
|
|
||||||
.add()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun withView(action: Activity.() -> Unit) {
|
fun withView(action: BaseActivity<*, *>.() -> Unit) {
|
||||||
ViewActionEvent(action).publish()
|
ViewActionEvent(action).publish()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.topjohnwu.magisk.base.viewmodel
|
||||||
|
|
||||||
|
import androidx.databinding.Bindable
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import io.reactivex.*
|
||||||
|
|
||||||
|
abstract class LoadingViewModel(defaultState: State = State.LOADING) :
|
||||||
|
StatefulViewModel<LoadingViewModel.State>(defaultState) {
|
||||||
|
|
||||||
|
val loading @Bindable get() = state == State.LOADING
|
||||||
|
val loaded @Bindable get() = state == State.LOADED
|
||||||
|
val loadingFailed @Bindable get() = state == State.LOADING_FAILED
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"Direct access is recommended since 0.2. This access method will be removed in 1.0",
|
||||||
|
ReplaceWith("state = State.LOADING", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
|
||||||
|
DeprecationLevel.WARNING
|
||||||
|
)
|
||||||
|
fun setLoading() {
|
||||||
|
state = State.LOADING
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"Direct access is recommended since 0.2. This access method will be removed in 1.0",
|
||||||
|
ReplaceWith("state = State.LOADED", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
|
||||||
|
DeprecationLevel.WARNING
|
||||||
|
)
|
||||||
|
fun setLoaded() {
|
||||||
|
state = State.LOADED
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"Direct access is recommended since 0.2. This access method will be removed in 1.0",
|
||||||
|
ReplaceWith("state = State.LOADING_FAILED", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
|
||||||
|
DeprecationLevel.WARNING
|
||||||
|
)
|
||||||
|
fun setLoadingFailed() {
|
||||||
|
state = State.LOADING_FAILED
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun notifyStateChanged() {
|
||||||
|
notifyPropertyChanged(BR.loading)
|
||||||
|
notifyPropertyChanged(BR.loaded)
|
||||||
|
notifyPropertyChanged(BR.loadingFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
LOADED, LOADING, LOADING_FAILED
|
||||||
|
}
|
||||||
|
|
||||||
|
//region Rx
|
||||||
|
protected fun <T> Observable<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
|
||||||
|
doOnSubscribe { viewModel.state = State.LOADING }
|
||||||
|
.doOnError { viewModel.state = State.LOADING_FAILED }
|
||||||
|
.doOnNext { if (allowFinishing) viewModel.state = State.LOADED }
|
||||||
|
|
||||||
|
protected fun <T> Single<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
|
||||||
|
doOnSubscribe { viewModel.state = State.LOADING }
|
||||||
|
.doOnError { viewModel.state = State.LOADING_FAILED }
|
||||||
|
.doOnSuccess { if (allowFinishing) viewModel.state = State.LOADED }
|
||||||
|
|
||||||
|
protected fun <T> Maybe<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
|
||||||
|
doOnSubscribe { viewModel.state = State.LOADING }
|
||||||
|
.doOnError { viewModel.state = State.LOADING_FAILED }
|
||||||
|
.doOnComplete { if (allowFinishing) viewModel.state = State.LOADED }
|
||||||
|
.doOnSuccess { if (allowFinishing) viewModel.state = State.LOADED }
|
||||||
|
|
||||||
|
protected fun <T> Flowable<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
|
||||||
|
doOnSubscribe { viewModel.state = State.LOADING }
|
||||||
|
.doOnError { viewModel.state = State.LOADING_FAILED }
|
||||||
|
.doOnNext { if (allowFinishing) viewModel.state = State.LOADED }
|
||||||
|
|
||||||
|
protected fun Completable.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
|
||||||
|
doOnSubscribe { viewModel.state = State.LOADING }
|
||||||
|
.doOnError { viewModel.state = State.LOADING_FAILED }
|
||||||
|
.doOnComplete { if (allowFinishing) viewModel.state = State.LOADED }
|
||||||
|
//endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.topjohnwu.magisk.base.viewmodel
|
||||||
|
|
||||||
|
import androidx.databinding.Observable
|
||||||
|
import androidx.databinding.PropertyChangeRegistry
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy of [android.databinding.BaseObservable] which extends [ViewModel]
|
||||||
|
*/
|
||||||
|
abstract class ObservableViewModel : TeanityViewModel(), Observable {
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private var callbacks: PropertyChangeRegistry? = null
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
|
||||||
|
if (callbacks == null) {
|
||||||
|
callbacks = PropertyChangeRegistry()
|
||||||
|
}
|
||||||
|
callbacks?.add(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
|
||||||
|
callbacks?.remove(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies listeners that all properties of this instance have changed.
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
fun notifyChange() {
|
||||||
|
callbacks?.notifyCallbacks(this, 0, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies listeners that a specific property has changed. The getter for the property
|
||||||
|
* that changes should be marked with [android.databinding.Bindable] to generate a field in
|
||||||
|
* `BR` to be used as `fieldId`.
|
||||||
|
*
|
||||||
|
* @param fieldId The generated BR id for the Bindable field.
|
||||||
|
*/
|
||||||
|
fun notifyPropertyChanged(fieldId: Int) {
|
||||||
|
callbacks?.notifyCallbacks(this, fieldId, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.topjohnwu.magisk.base.viewmodel
|
||||||
|
|
||||||
|
abstract class StatefulViewModel<State : Enum<*>>(
|
||||||
|
val defaultState: State
|
||||||
|
) : ObservableViewModel() {
|
||||||
|
|
||||||
|
var state: State = defaultState
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
notifyStateChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun notifyStateChanged() = Unit
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.topjohnwu.magisk.base.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.topjohnwu.magisk.model.events.SimpleViewEvent
|
||||||
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
|
|
||||||
|
abstract class TeanityViewModel : ViewModel() {
|
||||||
|
|
||||||
|
private val disposables = CompositeDisposable()
|
||||||
|
private val _viewEvents = MutableLiveData<ViewEvent>()
|
||||||
|
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
disposables.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <Event : ViewEvent> Event.publish() {
|
||||||
|
_viewEvents.value = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Int.publish() {
|
||||||
|
_viewEvents.value = SimpleViewEvent(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Disposable.add() {
|
||||||
|
disposables.add(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
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()
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,10 @@ package com.topjohnwu.magisk.data.database
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.data.database.base.*
|
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
|
||||||
|
import com.topjohnwu.magisk.data.database.magiskdb.Delete
|
||||||
|
import com.topjohnwu.magisk.data.database.magiskdb.Replace
|
||||||
|
import com.topjohnwu.magisk.data.database.magiskdb.Select
|
||||||
import com.topjohnwu.magisk.extensions.now
|
import com.topjohnwu.magisk.extensions.now
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
import com.topjohnwu.magisk.model.entity.toMap
|
import com.topjohnwu.magisk.model.entity.toMap
|
||||||
@@ -16,7 +19,7 @@ class PolicyDao(
|
|||||||
private val context: Context
|
private val context: Context
|
||||||
) : BaseDao() {
|
) : BaseDao() {
|
||||||
|
|
||||||
override val table: String = DatabaseDefinition.Table.POLICY
|
override val table: String = Table.POLICY
|
||||||
|
|
||||||
fun deleteOutdated(
|
fun deleteOutdated(
|
||||||
nowSeconds: Long = TimeUnit.MILLISECONDS.toSeconds(now)
|
nowSeconds: Long = TimeUnit.MILLISECONDS.toSeconds(now)
|
||||||
@@ -72,4 +75,4 @@ class PolicyDao(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,14 @@ import androidx.room.*
|
|||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||||
|
|
||||||
|
@Database(version = 6, entities = [Repo::class, RepoEtag::class])
|
||||||
|
abstract class RepoDatabase : RoomDatabase() {
|
||||||
|
|
||||||
|
abstract fun repoDao() : RepoDao
|
||||||
|
}
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
abstract class RepoDao {
|
abstract class RepoDao(private val db: RepoDatabase) {
|
||||||
|
|
||||||
val repoIDList get() = getRepoID().map { it.id }
|
val repoIDList get() = getRepoID().map { it.id }
|
||||||
|
|
||||||
@@ -15,13 +21,10 @@ abstract class RepoDao {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var etagKey: String
|
var etagKey: String
|
||||||
set(etag) = addEtagRaw(RepoEtag(0, etag))
|
set(value) = addEtagRaw(RepoEtag(0, value))
|
||||||
get() = etagRaw()?.key.orEmpty()
|
get() = etagRaw()?.key.orEmpty()
|
||||||
|
|
||||||
fun clear() {
|
fun clear() = db.clearAllTables()
|
||||||
clearRepos()
|
|
||||||
clearEtag()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Query("SELECT * FROM repos ORDER BY last_update DESC")
|
@Query("SELECT * FROM repos ORDER BY last_update DESC")
|
||||||
protected abstract fun getReposDateOrder(): List<Repo>
|
protected abstract fun getReposDateOrder(): List<Repo>
|
||||||
@@ -52,12 +55,6 @@ abstract class RepoDao {
|
|||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
protected abstract fun addEtagRaw(etag: RepoEtag)
|
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(
|
data class RepoID(
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
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,10 +1,13 @@
|
|||||||
package com.topjohnwu.magisk.data.database
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
import com.topjohnwu.magisk.data.database.base.*
|
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
|
||||||
|
import com.topjohnwu.magisk.data.database.magiskdb.Delete
|
||||||
|
import com.topjohnwu.magisk.data.database.magiskdb.Replace
|
||||||
|
import com.topjohnwu.magisk.data.database.magiskdb.Select
|
||||||
|
|
||||||
class SettingsDao : BaseDao() {
|
class SettingsDao : BaseDao() {
|
||||||
|
|
||||||
override val table = DatabaseDefinition.Table.SETTINGS
|
override val table = Table.SETTINGS
|
||||||
|
|
||||||
fun delete(key: String) = query<Delete> {
|
fun delete(key: String) = query<Delete> {
|
||||||
condition { equals("key", key) }
|
condition { equals("key", key) }
|
||||||
@@ -19,4 +22,4 @@ class SettingsDao : BaseDao() {
|
|||||||
condition { equals("key", key) }
|
condition { equals("key", key) }
|
||||||
}.map { it.firstOrNull()?.values?.firstOrNull()?.toIntOrNull() ?: default }
|
}.map { it.firstOrNull()?.values?.firstOrNull()?.toIntOrNull() ?: default }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package com.topjohnwu.magisk.data.database
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
import com.topjohnwu.magisk.data.database.base.*
|
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
|
||||||
|
import com.topjohnwu.magisk.data.database.magiskdb.Delete
|
||||||
|
import com.topjohnwu.magisk.data.database.magiskdb.Replace
|
||||||
|
import com.topjohnwu.magisk.data.database.magiskdb.Select
|
||||||
|
|
||||||
class StringDao : BaseDao() {
|
class StringDao : BaseDao() {
|
||||||
|
|
||||||
override val table = DatabaseDefinition.Table.STRINGS
|
override val table = Table.STRINGS
|
||||||
|
|
||||||
fun delete(key: String) = query<Delete> {
|
fun delete(key: String) = query<Delete> {
|
||||||
condition { equals("key", key) }
|
condition { equals("key", key) }
|
||||||
@@ -19,4 +22,4 @@ class StringDao : BaseDao() {
|
|||||||
condition { equals("key", key) }
|
condition { equals("key", key) }
|
||||||
}.map { it.firstOrNull()?.values?.firstOrNull() ?: default }
|
}.map { it.firstOrNull()?.values?.firstOrNull() ?: default }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user