mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-14 07:07:27 +00:00
Compare commits
209 Commits
manager-v7
...
v20.3
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 |
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 {
|
||||||
@@ -75,7 +74,7 @@ dependencies {
|
|||||||
implementation "${bindingAdapter}:${vBAdapt}"
|
implementation "${bindingAdapter}:${vBAdapt}"
|
||||||
implementation "${bindingAdapter}-recyclerview:${vBAdapt}"
|
implementation "${bindingAdapter}-recyclerview:${vBAdapt}"
|
||||||
|
|
||||||
def vMarkwon = '4.1.1'
|
def vMarkwon = '4.2.0'
|
||||||
implementation "io.noties.markwon:core:${vMarkwon}"
|
implementation "io.noties.markwon:core:${vMarkwon}"
|
||||||
implementation "io.noties.markwon:html:${vMarkwon}"
|
implementation "io.noties.markwon:html:${vMarkwon}"
|
||||||
implementation "io.noties.markwon:image:${vMarkwon}"
|
implementation "io.noties.markwon:image:${vMarkwon}"
|
||||||
@@ -85,7 +84,7 @@ dependencies {
|
|||||||
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}"
|
||||||
@@ -100,10 +99,10 @@ dependencies {
|
|||||||
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}"
|
||||||
|
|
||||||
@@ -112,22 +111,25 @@ dependencies {
|
|||||||
replacedBy('com.github.topjohnwu:room-runtime')
|
replacedBy('com.github.topjohnwu:room-runtime')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def vRoom = "2.2.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}"
|
||||||
|
|
||||||
def vNav = "2.1.0"
|
def vNav = '2.1.0'
|
||||||
implementation "androidx.navigation:navigation-fragment-ktx:$vNav"
|
implementation "androidx.navigation:navigation-fragment-ktx:${vNav}"
|
||||||
implementation "androidx.navigation:navigation-ui-ktx:$vNav"
|
implementation "androidx.navigation:navigation-ui-ktx:${vNav}"
|
||||||
|
|
||||||
|
implementation 'androidx.biometric:biometric:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03'
|
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-beta05'
|
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'
|
implementation 'androidx.transition:transition:1.3.0-rc02'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
implementation 'androidx.core:core-ktx:1.1.0'
|
implementation 'androidx.core:core-ktx:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.1.0-beta01'
|
implementation 'com.google.android.material:material:1.2.0-alpha02'
|
||||||
}
|
}
|
||||||
|
6
app/proguard-rules.pro
vendored
6
app/proguard-rules.pro
vendored
@@ -32,7 +32,11 @@
|
|||||||
-keep,allowobfuscation class * extends com.topjohnwu.magisk.base.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,54 +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.utils.LocaleManager
|
import com.topjohnwu.magisk.extensions.unwrap
|
||||||
import com.topjohnwu.magisk.utils.RootUtils
|
import com.topjohnwu.magisk.utils.RootInit
|
||||||
|
import com.topjohnwu.magisk.utils.SuHandler
|
||||||
|
import com.topjohnwu.magisk.utils.updateConfig
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.core.context.startKoin
|
import org.koin.core.context.startKoin
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
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
|
||||||
LocaleManager.setLocale(this)
|
override fun getBaseContext(): Context {
|
||||||
|
return super.getBaseContext().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,12 +15,13 @@ import androidx.databinding.DataBindingUtil
|
|||||||
import androidx.databinding.ViewDataBinding
|
import androidx.databinding.ViewDataBinding
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||||
import com.topjohnwu.magisk.extensions.set
|
import com.topjohnwu.magisk.extensions.set
|
||||||
import com.topjohnwu.magisk.model.events.EventHandler
|
import com.topjohnwu.magisk.model.events.EventHandler
|
||||||
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
|
||||||
import com.topjohnwu.magisk.utils.LocaleManager
|
|
||||||
import com.topjohnwu.magisk.utils.currentLocale
|
import com.topjohnwu.magisk.utils.currentLocale
|
||||||
|
import com.topjohnwu.magisk.wrap
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
typealias RequestCallback = BaseActivity<*, *>.(Int, Intent?) -> Unit
|
typealias RequestCallback = BaseActivity<*, *>.(Int, Intent?) -> Unit
|
||||||
@@ -31,9 +32,8 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
|
|||||||
protected lateinit var binding: Binding
|
protected lateinit var binding: Binding
|
||||||
protected abstract val layoutRes: Int
|
protected abstract val layoutRes: Int
|
||||||
protected abstract val viewModel: ViewModel
|
protected abstract val viewModel: ViewModel
|
||||||
|
protected open val themeRes: Int = R.style.MagiskTheme
|
||||||
protected open val snackbarView get() = binding.root
|
protected open val snackbarView get() = binding.root
|
||||||
protected open val navHostId: Int = 0
|
|
||||||
protected open val defaultPosition: Int = 0
|
|
||||||
|
|
||||||
private val resultCallbacks by lazy { SparseArrayCompat<RequestCallback>() }
|
private val resultCallbacks by lazy { SparseArrayCompat<RequestCallback>() }
|
||||||
|
|
||||||
@@ -53,10 +53,11 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
override fun attachBaseContext(base: Context) {
|
||||||
super.attachBaseContext(LocaleManager.getLocaleContext(base))
|
super.attachBaseContext(base.wrap(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
setTheme(themeRes)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
viewModel.viewEvents.observe(this, viewEventObserver)
|
viewModel.viewEvents.observe(this, viewEventObserver)
|
||||||
|
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,31 +1,27 @@
|
|||||||
package com.topjohnwu.magisk.base.viewmodel
|
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.topjohnwu.magisk.extensions.doOnSubscribeUi
|
||||||
import com.topjohnwu.magisk.extensions.get
|
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
|
||||||
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 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 BaseViewModel(
|
abstract class BaseViewModel(
|
||||||
initialState: State = State.LOADING
|
initialState: State = State.LOADING
|
||||||
) : LoadingViewModel(initialState) {
|
) : LoadingViewModel(initialState) {
|
||||||
|
|
||||||
val isConnected = KObservableField(false)
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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 }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,36 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||||
|
import io.reactivex.Completable
|
||||||
|
import io.reactivex.Single
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Database(version = 1, entities = [MagiskLog::class])
|
||||||
|
abstract class SuLogDatabase : RoomDatabase() {
|
||||||
|
|
||||||
|
abstract fun suLogDao(): SuLogDao
|
||||||
|
}
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class SuLogDao(private val db: SuLogDatabase) {
|
||||||
|
|
||||||
|
private val twoWeeksAgo =
|
||||||
|
Calendar.getInstance().apply { add(Calendar.WEEK_OF_YEAR, -2) }.timeInMillis
|
||||||
|
|
||||||
|
fun deleteAll() = Completable.fromAction { db.clearAllTables() }
|
||||||
|
|
||||||
|
fun fetchAll() = deleteOutdated().andThen(fetch())
|
||||||
|
|
||||||
|
@Query("SELECT * FROM logs ORDER BY time DESC")
|
||||||
|
protected abstract fun fetch(): Single<MutableList<MagiskLog>>
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
abstract fun insert(log: MagiskLog): Completable
|
||||||
|
|
||||||
|
@Query("DELETE FROM logs WHERE time < :timeout")
|
||||||
|
protected abstract fun deleteOutdated(
|
||||||
|
timeout: Long = twoWeeksAgo
|
||||||
|
): Completable
|
||||||
|
|
||||||
|
}
|
@@ -1,15 +0,0 @@
|
|||||||
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()
|
|
||||||
|
|
||||||
}
|
|
@@ -1,30 +0,0 @@
|
|||||||
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()
|
|
@@ -1,5 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.data.database.base
|
|
||||||
|
|
||||||
inline class MagiskQuery(private val _query: String) {
|
|
||||||
val query get() = "magisk --sqlite '$_query'"
|
|
||||||
}
|
|
@@ -0,0 +1,44 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database.magiskdb
|
||||||
|
|
||||||
|
import androidx.annotation.StringDef
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import io.reactivex.Single
|
||||||
|
|
||||||
|
abstract class BaseDao {
|
||||||
|
|
||||||
|
object Table {
|
||||||
|
const val POLICY = "policies"
|
||||||
|
const val LOG = "logs"
|
||||||
|
const val SETTINGS = "settings"
|
||||||
|
const val STRINGS = "strings"
|
||||||
|
}
|
||||||
|
|
||||||
|
@StringDef(Table.POLICY, Table.LOG, Table.SETTINGS, Table.STRINGS)
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
annotation class TableStrict
|
||||||
|
|
||||||
|
@TableStrict
|
||||||
|
abstract val table: String
|
||||||
|
|
||||||
|
inline fun <reified Builder : Query.Builder> query(builder: Builder.() -> Unit = {}) =
|
||||||
|
Builder::class.java.newInstance()
|
||||||
|
.apply { table = this@BaseDao.table }
|
||||||
|
.apply(builder)
|
||||||
|
.toString()
|
||||||
|
.let { Query(it) }
|
||||||
|
.query()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Query.query() = query.su()
|
||||||
|
|
||||||
|
private fun String.suRaw() = Single.fromCallable { Shell.su(this).exec().out }
|
||||||
|
private fun String.su() = suRaw().map { it.toMap() }
|
||||||
|
|
||||||
|
private 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()
|
@@ -1,27 +1,17 @@
|
|||||||
package com.topjohnwu.magisk.data.database.base
|
package com.topjohnwu.magisk.data.database.magiskdb
|
||||||
|
|
||||||
import androidx.annotation.StringDef
|
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 {
|
class Query(private val _query: String) {
|
||||||
|
val query get() = "magisk --sqlite '$_query'"
|
||||||
|
|
||||||
val requestType: String
|
interface Builder {
|
||||||
var table: String
|
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 {
|
class Delete : Query.Builder {
|
||||||
override val requestType: String = "DELETE FROM"
|
override val requestType: String = "DELETE FROM"
|
||||||
override var table = ""
|
override var table = ""
|
||||||
|
|
||||||
@@ -36,7 +26,7 @@ class Delete : MagiskQueryBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Select : MagiskQueryBuilder {
|
class Select : Query.Builder {
|
||||||
override val requestType: String get() = "SELECT $fields FROM"
|
override val requestType: String get() = "SELECT $fields FROM"
|
||||||
override lateinit var table: String
|
override lateinit var table: String
|
||||||
|
|
||||||
@@ -69,7 +59,7 @@ class Replace : Insert() {
|
|||||||
override val requestType: String = "REPLACE INTO"
|
override val requestType: String = "REPLACE INTO"
|
||||||
}
|
}
|
||||||
|
|
||||||
open class Insert : MagiskQueryBuilder {
|
open class Insert : Query.Builder {
|
||||||
override val requestType: String = "INSERT INTO"
|
override val requestType: String = "INSERT INTO"
|
||||||
override lateinit var table: String
|
override lateinit var table: String
|
||||||
|
|
||||||
@@ -137,19 +127,11 @@ class Condition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Order {
|
object Order {
|
||||||
|
const val ASC = "ASC"
|
||||||
@set:OrderStrict
|
const val DESC = "DESC"
|
||||||
var order = DESC
|
|
||||||
var field = ""
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val ASC = "ASC"
|
|
||||||
const val DESC = "DESC"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@StringDef(ASC, DESC)
|
@StringDef(Order.ASC, Order.DESC)
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
annotation class OrderStrict
|
annotation class OrderStrict
|
@@ -29,8 +29,8 @@ interface DBConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DBSettingsValue(
|
class DBSettingsValue(
|
||||||
private val name: String,
|
private val name: String,
|
||||||
private val default: Int
|
private val default: Int
|
||||||
) : ReadWriteProperty<DBConfig, Int> {
|
) : ReadWriteProperty<DBConfig, Int> {
|
||||||
|
|
||||||
private var value: Int? = null
|
private var value: Int? = null
|
||||||
@@ -47,29 +47,29 @@ class DBSettingsValue(
|
|||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
thisRef.settingsDao.put(name, value)
|
thisRef.settingsDao.put(name, value)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DBBoolSettings(
|
class DBBoolSettings(
|
||||||
name: String,
|
name: String,
|
||||||
default: Boolean
|
default: Boolean
|
||||||
) : ReadWriteProperty<DBConfig, Boolean> {
|
) : ReadWriteProperty<DBConfig, Boolean> {
|
||||||
|
|
||||||
val base = DBSettingsValue(name, if (default) 1 else 0)
|
val base = DBSettingsValue(name, if (default) 1 else 0)
|
||||||
|
|
||||||
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Boolean
|
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Boolean =
|
||||||
= base.getValue(thisRef, property) != 0
|
base.getValue(thisRef, property) != 0
|
||||||
|
|
||||||
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Boolean) =
|
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Boolean) =
|
||||||
base.setValue(thisRef, property, if (value) 1 else 0)
|
base.setValue(thisRef, property, if (value) 1 else 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
class DBStringsValue(
|
class DBStringsValue(
|
||||||
private val name: String,
|
private val name: String,
|
||||||
private val default: String,
|
private val default: String,
|
||||||
private val sync: Boolean
|
private val sync: Boolean
|
||||||
) : ReadWriteProperty<DBConfig, String> {
|
) : ReadWriteProperty<DBConfig, String> {
|
||||||
|
|
||||||
private var value: String? = null
|
private var value: String? = null
|
||||||
@@ -90,16 +90,16 @@ class DBStringsValue(
|
|||||||
thisRef.stringDao.delete(name).blockingAwait()
|
thisRef.stringDao.delete(name).blockingAwait()
|
||||||
} else {
|
} else {
|
||||||
thisRef.stringDao.delete(name)
|
thisRef.stringDao.delete(name)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (sync) {
|
if (sync) {
|
||||||
thisRef.stringDao.put(name, value).blockingAwait()
|
thisRef.stringDao.put(name, value).blockingAwait()
|
||||||
} else {
|
} else {
|
||||||
thisRef.stringDao.put(name, value)
|
thisRef.stringDao.put(name, value)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,40 +1,38 @@
|
|||||||
package com.topjohnwu.magisk.data.repository
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.data.database.LogDao
|
import com.topjohnwu.magisk.data.database.SuLogDao
|
||||||
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.MagiskLog
|
||||||
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
|
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import io.reactivex.Completable
|
||||||
|
import io.reactivex.Single
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
class LogRepository(
|
class LogRepository(
|
||||||
private val logDao: LogDao
|
private val logDao: SuLogDao
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun fetchLogs() = logDao.fetchAll()
|
fun fetchLogs() = logDao.fetchAll().map { it.wrap() }
|
||||||
.map { it.sortByDescending { it.date.time }; it }
|
|
||||||
.map { it.wrap() }
|
|
||||||
|
|
||||||
fun fetchMagiskLogs() = "tail -n 5000 ${Const.MAGISK_LOG}".suRaw()
|
fun fetchMagiskLogs() = Single.fromCallable {
|
||||||
.filter { it.isNotEmpty() }
|
Shell.su("tail -n 5000 ${Const.MAGISK_LOG}").exec().out
|
||||||
|
}.flattenAsFlowable { it }.filter { it.isNotEmpty() }
|
||||||
|
|
||||||
fun clearLogs() = logDao.deleteAll()
|
fun clearLogs() = logDao.deleteAll()
|
||||||
fun clearOutdated() = logDao.deleteOutdated()
|
|
||||||
|
|
||||||
fun clearMagiskLogs() = Shell.su("echo -n > " + Const.MAGISK_LOG)
|
fun clearMagiskLogs() = Completable.fromAction {
|
||||||
.toSingle()
|
Shell.su("echo -n > ${Const.MAGISK_LOG}").exec()
|
||||||
.map { it.exec() }
|
}
|
||||||
|
|
||||||
fun put(log: MagiskLog) = logDao.put(log)
|
fun insert(log: MagiskLog) = logDao.insert(log)
|
||||||
|
|
||||||
private fun List<MagiskLog>.wrap(): List<WrappedMagiskLog> {
|
private fun List<MagiskLog>.wrap(): List<WrappedMagiskLog> {
|
||||||
val day = TimeUnit.DAYS.toMillis(1)
|
val day = TimeUnit.DAYS.toMillis(1)
|
||||||
return groupBy { it.date.time / day }
|
return groupBy { it.time / day }
|
||||||
.map { WrappedMagiskLog(it.key * day, it.value) }
|
.map { WrappedMagiskLog(it.key * day, it.value) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@ package com.topjohnwu.magisk.data.repository
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.Info
|
||||||
import com.topjohnwu.magisk.data.database.base.su
|
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
import com.topjohnwu.magisk.extensions.getLabel
|
import com.topjohnwu.magisk.extensions.getLabel
|
||||||
import com.topjohnwu.magisk.extensions.packageName
|
import com.topjohnwu.magisk.extensions.packageName
|
||||||
@@ -29,7 +28,7 @@ class MagiskRepository(
|
|||||||
else -> throw IllegalArgumentException()
|
else -> throw IllegalArgumentException()
|
||||||
}.flatMap {
|
}.flatMap {
|
||||||
// If remote version is lower than current installed, try switching to beta
|
// If remote version is lower than current installed, try switching to beta
|
||||||
if (it.magisk.versionCode < Info.magiskVersionCode
|
if (it.magisk.versionCode < Info.env.magiskVersionCode
|
||||||
&& Config.updateChannel == Config.Value.DEFAULT_CHANNEL) {
|
&& Config.updateChannel == Config.Value.DEFAULT_CHANNEL) {
|
||||||
Config.updateChannel = Config.Value.BETA_CHANNEL
|
Config.updateChannel = Config.Value.BETA_CHANNEL
|
||||||
apiRaw.fetchBetaUpdate()
|
apiRaw.fetchBetaUpdate()
|
||||||
@@ -57,7 +56,7 @@ class MagiskRepository(
|
|||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
fun toggleHide(isEnabled: Boolean, packageName: String, process: String) =
|
fun toggleHide(isEnabled: Boolean, packageName: String, process: String) =
|
||||||
"magiskhide --%s %s %s".format(isEnabled.state, packageName, process).su().ignoreElement()
|
Shell.su("magiskhide --${isEnabled.state} $packageName $process").submit()
|
||||||
|
|
||||||
private val Boolean.state get() = if (this) "add" else "rm"
|
private val Boolean.state get() = if (this) "add" else "rm"
|
||||||
|
|
||||||
@@ -74,4 +73,4 @@ class MagiskRepository(
|
|||||||
) }
|
) }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -8,16 +8,20 @@ import org.koin.dsl.module
|
|||||||
|
|
||||||
|
|
||||||
val databaseModule = module {
|
val databaseModule = module {
|
||||||
single { LogDao() }
|
|
||||||
single { PolicyDao(get()) }
|
single { PolicyDao(get()) }
|
||||||
single { SettingsDao() }
|
single { SettingsDao() }
|
||||||
single { StringDao() }
|
single { StringDao() }
|
||||||
single { createRepoDatabase(get()) }
|
single { createRepoDatabase(get()).repoDao() }
|
||||||
single { get<RepoDatabase>().repoDao() }
|
single { createSuLogDatabase(get(Protected)).suLogDao() }
|
||||||
single { RepoUpdater(get(), get()) }
|
single { RepoUpdater(get(), get()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createRepoDatabase(context: Context) =
|
fun createRepoDatabase(context: Context) =
|
||||||
Room.databaseBuilder(context, RepoDatabase::class.java, "repo.db")
|
Room.databaseBuilder(context, RepoDatabase::class.java, "repo.db")
|
||||||
.fallbackToDestructiveMigration()
|
.fallbackToDestructiveMigration()
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
fun createSuLogDatabase(context: Context) =
|
||||||
|
Room.databaseBuilder(context, SuLogDatabase::class.java, "sulogs.db")
|
||||||
|
.fallbackToDestructiveMigration()
|
||||||
|
.build()
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
|
||||||
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
|
|
||||||
fun ui(body: () -> Unit) = Handler(Looper.getMainLooper()).post(body)
|
|
@@ -2,6 +2,7 @@ package com.topjohnwu.magisk.extensions
|
|||||||
|
|
||||||
import androidx.databinding.ObservableField
|
import androidx.databinding.ObservableField
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
import io.reactivex.*
|
import io.reactivex.*
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.Disposables
|
import io.reactivex.disposables.Disposables
|
||||||
@@ -43,35 +44,35 @@ typealias OnErrorListener = (Throwable) -> Unit
|
|||||||
/*=== ALIASES FOR OBSERVABLES ===*/
|
/*=== ALIASES FOR OBSERVABLES ===*/
|
||||||
|
|
||||||
fun <T> Observable<T>.subscribeK(
|
fun <T> Observable<T>.subscribeK(
|
||||||
onError: OnErrorListener = { it.printStackTrace() },
|
onError: OnErrorListener = { it.printStackTrace() },
|
||||||
onComplete: OnCompleteListener = {},
|
onComplete: OnCompleteListener = {},
|
||||||
onNext: OnSuccessListener<T> = {}
|
onNext: OnSuccessListener<T> = {}
|
||||||
) = applySchedulers()
|
) = applySchedulers()
|
||||||
.subscribe(onNext, onError, onComplete)
|
.subscribe(onNext, onError, onComplete)
|
||||||
|
|
||||||
fun <T> Single<T>.subscribeK(
|
fun <T> Single<T>.subscribeK(
|
||||||
onError: OnErrorListener = { it.printStackTrace() },
|
onError: OnErrorListener = { it.printStackTrace() },
|
||||||
onNext: OnSuccessListener<T> = {}
|
onSuccess: OnSuccessListener<T> = {}
|
||||||
) = applySchedulers()
|
) = applySchedulers()
|
||||||
.subscribe(onNext, onError)
|
.subscribe(onSuccess, onError)
|
||||||
|
|
||||||
fun <T> Maybe<T>.subscribeK(
|
fun <T> Maybe<T>.subscribeK(
|
||||||
onError: OnErrorListener = { it.printStackTrace() },
|
onError: OnErrorListener = { it.printStackTrace() },
|
||||||
onComplete: OnCompleteListener = {},
|
onComplete: OnCompleteListener = {},
|
||||||
onNext: OnSuccessListener<T> = {}
|
onSuccess: OnSuccessListener<T> = {}
|
||||||
) = applySchedulers()
|
) = applySchedulers()
|
||||||
.subscribe(onNext, onError, onComplete)
|
.subscribe(onSuccess, onError, onComplete)
|
||||||
|
|
||||||
fun <T> Flowable<T>.subscribeK(
|
fun <T> Flowable<T>.subscribeK(
|
||||||
onError: OnErrorListener = { it.printStackTrace() },
|
onError: OnErrorListener = { it.printStackTrace() },
|
||||||
onComplete: OnCompleteListener = {},
|
onComplete: OnCompleteListener = {},
|
||||||
onNext: OnSuccessListener<T> = {}
|
onNext: OnSuccessListener<T> = {}
|
||||||
) = applySchedulers()
|
) = applySchedulers()
|
||||||
.subscribe(onNext, onError, onComplete)
|
.subscribe(onNext, onError, onComplete)
|
||||||
|
|
||||||
fun Completable.subscribeK(
|
fun Completable.subscribeK(
|
||||||
onError: OnErrorListener = { it.printStackTrace() },
|
onError: OnErrorListener = { it.printStackTrace() },
|
||||||
onComplete: OnCompleteListener = {}
|
onComplete: OnCompleteListener = {}
|
||||||
) = applySchedulers()
|
) = applySchedulers()
|
||||||
.subscribe(onComplete, onError)
|
.subscribe(onComplete, onError)
|
||||||
|
|
||||||
@@ -104,54 +105,54 @@ fun Completable.updateBy(
|
|||||||
|
|
||||||
|
|
||||||
fun <T> Observable<T>.doOnSubscribeUi(body: () -> Unit) =
|
fun <T> Observable<T>.doOnSubscribeUi(body: () -> Unit) =
|
||||||
doOnSubscribe { ui { body() } }
|
doOnSubscribe { UiThreadHandler.run { body() } }
|
||||||
|
|
||||||
fun <T> Single<T>.doOnSubscribeUi(body: () -> Unit) =
|
fun <T> Single<T>.doOnSubscribeUi(body: () -> Unit) =
|
||||||
doOnSubscribe { ui { body() } }
|
doOnSubscribe { UiThreadHandler.run { body() } }
|
||||||
|
|
||||||
fun <T> Maybe<T>.doOnSubscribeUi(body: () -> Unit) =
|
fun <T> Maybe<T>.doOnSubscribeUi(body: () -> Unit) =
|
||||||
doOnSubscribe { ui { body() } }
|
doOnSubscribe { UiThreadHandler.run { body() } }
|
||||||
|
|
||||||
fun <T> Flowable<T>.doOnSubscribeUi(body: () -> Unit) =
|
fun <T> Flowable<T>.doOnSubscribeUi(body: () -> Unit) =
|
||||||
doOnSubscribe { ui { body() } }
|
doOnSubscribe { UiThreadHandler.run { body() } }
|
||||||
|
|
||||||
fun Completable.doOnSubscribeUi(body: () -> Unit) =
|
fun Completable.doOnSubscribeUi(body: () -> Unit) =
|
||||||
doOnSubscribe { ui { body() } }
|
doOnSubscribe { UiThreadHandler.run { body() } }
|
||||||
|
|
||||||
|
|
||||||
fun <T> Observable<T>.doOnErrorUi(body: (Throwable) -> Unit) =
|
fun <T> Observable<T>.doOnErrorUi(body: (Throwable) -> Unit) =
|
||||||
doOnError { ui { body(it) } }
|
doOnError { UiThreadHandler.run { body(it) } }
|
||||||
|
|
||||||
fun <T> Single<T>.doOnErrorUi(body: (Throwable) -> Unit) =
|
fun <T> Single<T>.doOnErrorUi(body: (Throwable) -> Unit) =
|
||||||
doOnError { ui { body(it) } }
|
doOnError { UiThreadHandler.run { body(it) } }
|
||||||
|
|
||||||
fun <T> Maybe<T>.doOnErrorUi(body: (Throwable) -> Unit) =
|
fun <T> Maybe<T>.doOnErrorUi(body: (Throwable) -> Unit) =
|
||||||
doOnError { ui { body(it) } }
|
doOnError { UiThreadHandler.run { body(it) } }
|
||||||
|
|
||||||
fun <T> Flowable<T>.doOnErrorUi(body: (Throwable) -> Unit) =
|
fun <T> Flowable<T>.doOnErrorUi(body: (Throwable) -> Unit) =
|
||||||
doOnError { ui { body(it) } }
|
doOnError { UiThreadHandler.run { body(it) } }
|
||||||
|
|
||||||
fun Completable.doOnErrorUi(body: (Throwable) -> Unit) =
|
fun Completable.doOnErrorUi(body: (Throwable) -> Unit) =
|
||||||
doOnError { ui { body(it) } }
|
doOnError { UiThreadHandler.run { body(it) } }
|
||||||
|
|
||||||
|
|
||||||
fun <T> Observable<T>.doOnNextUi(body: (T) -> Unit) =
|
fun <T> Observable<T>.doOnNextUi(body: (T) -> Unit) =
|
||||||
doOnNext { ui { body(it) } }
|
doOnNext { UiThreadHandler.run { body(it) } }
|
||||||
|
|
||||||
fun <T> Flowable<T>.doOnNextUi(body: (T) -> Unit) =
|
fun <T> Flowable<T>.doOnNextUi(body: (T) -> Unit) =
|
||||||
doOnNext { ui { body(it) } }
|
doOnNext { UiThreadHandler.run { body(it) } }
|
||||||
|
|
||||||
fun <T> Single<T>.doOnSuccessUi(body: (T) -> Unit) =
|
fun <T> Single<T>.doOnSuccessUi(body: (T) -> Unit) =
|
||||||
doOnSuccess { ui { body(it) } }
|
doOnSuccess { UiThreadHandler.run { body(it) } }
|
||||||
|
|
||||||
fun <T> Maybe<T>.doOnSuccessUi(body: (T) -> Unit) =
|
fun <T> Maybe<T>.doOnSuccessUi(body: (T) -> Unit) =
|
||||||
doOnSuccess { ui { body(it) } }
|
doOnSuccess { UiThreadHandler.run { body(it) } }
|
||||||
|
|
||||||
fun <T> Maybe<T>.doOnCompleteUi(body: () -> Unit) =
|
fun <T> Maybe<T>.doOnCompleteUi(body: () -> Unit) =
|
||||||
doOnComplete { ui { body() } }
|
doOnComplete { UiThreadHandler.run { body() } }
|
||||||
|
|
||||||
fun Completable.doOnCompleteUi(body: () -> Unit) =
|
fun Completable.doOnCompleteUi(body: () -> Unit) =
|
||||||
doOnComplete { ui { body() } }
|
doOnComplete { UiThreadHandler.run { body() } }
|
||||||
|
|
||||||
|
|
||||||
fun <T, R> Observable<List<T>>.mapList(
|
fun <T, R> Observable<List<T>>.mapList(
|
||||||
@@ -198,4 +199,4 @@ fun <T> ObservableField<T>.toObservable(): Observable<T> {
|
|||||||
fun <T : Any> T.toSingle() = Single.just(this)
|
fun <T : Any> T.toSingle() = Single.just(this)
|
||||||
|
|
||||||
fun <T1, T2, R> zip(t1: Single<T1>, t2: Single<T2>, zipper: (T1, T2) -> R) =
|
fun <T1, T2, R> zip(t1: Single<T1>, t2: Single<T2>, zipper: (T1, T2) -> R) =
|
||||||
Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })
|
Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.ComponentInfo
|
import android.content.pm.ComponentInfo
|
||||||
@@ -10,19 +12,31 @@ import android.content.pm.PackageManager.*
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.drawable.AdaptiveIconDrawable
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.graphics.drawable.LayerDrawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.ColorRes
|
import androidx.annotation.ColorRes
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.net.toFile
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.topjohnwu.magisk.utils.FileProvider
|
import com.topjohnwu.magisk.Const
|
||||||
|
import com.topjohnwu.magisk.FileProvider
|
||||||
|
import com.topjohnwu.magisk.utils.DynamicClassLoader
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.magisk.utils.currentLocale
|
import com.topjohnwu.magisk.utils.currentLocale
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
import java.lang.reflect.Array as JArray
|
||||||
|
|
||||||
val packageName: String get() = get<Context>().packageName
|
val packageName: String get() = get<Context>().packageName
|
||||||
|
|
||||||
@@ -91,8 +105,129 @@ fun Context.rawResource(id: Int) = resources.openRawResource(id)
|
|||||||
fun Context.readUri(uri: Uri) =
|
fun Context.readUri(uri: Uri) =
|
||||||
contentResolver.openInputStream(uri) ?: throw FileNotFoundException()
|
contentResolver.openInputStream(uri) ?: throw FileNotFoundException()
|
||||||
|
|
||||||
|
fun Context.getBitmap(id: Int): Bitmap {
|
||||||
|
var drawable = AppCompatResources.getDrawable(this, id)!!
|
||||||
|
if (drawable is BitmapDrawable)
|
||||||
|
return drawable.bitmap
|
||||||
|
if (SDK_INT >= 26 && drawable is AdaptiveIconDrawable) {
|
||||||
|
drawable = LayerDrawable(arrayOf(drawable.background, drawable.foreground))
|
||||||
|
}
|
||||||
|
val bitmap = Bitmap.createBitmap(
|
||||||
|
drawable.intrinsicWidth, drawable.intrinsicHeight,
|
||||||
|
Bitmap.Config.ARGB_8888
|
||||||
|
)
|
||||||
|
val canvas = Canvas(bitmap)
|
||||||
|
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||||
|
drawable.draw(canvas)
|
||||||
|
return bitmap
|
||||||
|
}
|
||||||
|
|
||||||
fun Intent.startActivity(context: Context) = context.startActivity(this)
|
fun Intent.startActivity(context: Context) = context.startActivity(this)
|
||||||
|
|
||||||
|
fun Intent.startActivityWithRoot() {
|
||||||
|
val args = mutableListOf("am", "start", "--user", Const.USER_ID.toString())
|
||||||
|
val cmd = toCommand(args).joinToString(" ")
|
||||||
|
Shell.su(cmd).submit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Intent.toCommand(args: MutableList<String> = mutableListOf()): MutableList<String> {
|
||||||
|
action?.also {
|
||||||
|
args.add("-a")
|
||||||
|
args.add(it)
|
||||||
|
}
|
||||||
|
component?.also {
|
||||||
|
args.add("-n")
|
||||||
|
args.add(it.flattenToString())
|
||||||
|
}
|
||||||
|
data?.also {
|
||||||
|
args.add("-d")
|
||||||
|
args.add(it.toString())
|
||||||
|
}
|
||||||
|
categories?.also {
|
||||||
|
for (cat in it) {
|
||||||
|
args.add("-c")
|
||||||
|
args.add(cat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type?.also {
|
||||||
|
args.add("-t")
|
||||||
|
args.add(it)
|
||||||
|
}
|
||||||
|
extras?.also {
|
||||||
|
loop@ for (key in it.keySet()) {
|
||||||
|
val v = it[key] ?: continue
|
||||||
|
var value: Any = v
|
||||||
|
val arg: String
|
||||||
|
when {
|
||||||
|
v is String -> arg = "--es"
|
||||||
|
v is Boolean -> arg = "--ez"
|
||||||
|
v is Int -> arg = "--ei"
|
||||||
|
v is Long -> arg = "--el"
|
||||||
|
v is Float -> arg = "--ef"
|
||||||
|
v is Uri -> arg = "--eu"
|
||||||
|
v is ComponentName -> {
|
||||||
|
arg = "--ecn"
|
||||||
|
value = v.flattenToString()
|
||||||
|
}
|
||||||
|
v is List<*> -> {
|
||||||
|
if (v.isEmpty())
|
||||||
|
continue@loop
|
||||||
|
|
||||||
|
arg = if (v[0] is Int)
|
||||||
|
"--eial"
|
||||||
|
else if (v[0] is Long)
|
||||||
|
"--elal"
|
||||||
|
else if (v[0] is Float)
|
||||||
|
"--efal"
|
||||||
|
else if (v[0] is String)
|
||||||
|
"--esal"
|
||||||
|
else
|
||||||
|
continue@loop /* Unsupported */
|
||||||
|
|
||||||
|
val sb = StringBuilder()
|
||||||
|
for (o in v) {
|
||||||
|
sb.append(o.toString().replace(",", "\\,"))
|
||||||
|
sb.append(',')
|
||||||
|
}
|
||||||
|
// Remove trailing comma
|
||||||
|
sb.deleteCharAt(sb.length - 1)
|
||||||
|
value = sb
|
||||||
|
}
|
||||||
|
v.javaClass.isArray -> {
|
||||||
|
arg = if (v is IntArray)
|
||||||
|
"--eia"
|
||||||
|
else if (v is LongArray)
|
||||||
|
"--ela"
|
||||||
|
else if (v is FloatArray)
|
||||||
|
"--efa"
|
||||||
|
else if (v is Array<*> && v.isArrayOf<String>())
|
||||||
|
"--esa"
|
||||||
|
else
|
||||||
|
continue@loop /* Unsupported */
|
||||||
|
|
||||||
|
val sb = StringBuilder()
|
||||||
|
val len = JArray.getLength(v)
|
||||||
|
for (i in 0 until len) {
|
||||||
|
sb.append(JArray.get(v, i)!!.toString().replace(",", "\\,"))
|
||||||
|
sb.append(',')
|
||||||
|
}
|
||||||
|
// Remove trailing comma
|
||||||
|
sb.deleteCharAt(sb.length - 1)
|
||||||
|
value = sb
|
||||||
|
}
|
||||||
|
else -> continue@loop
|
||||||
|
} /* Unsupported */
|
||||||
|
|
||||||
|
args.add(arg)
|
||||||
|
args.add(key)
|
||||||
|
args.add(value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args.add("-f")
|
||||||
|
args.add(flags.toString())
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
fun File.provide(context: Context = get()): Uri {
|
fun File.provide(context: Context = get()): Uri {
|
||||||
return FileProvider.getUriForFile(context, context.packageName + ".provider", this)
|
return FileProvider.getUriForFile(context, context.packageName + ".provider", this)
|
||||||
}
|
}
|
||||||
@@ -157,3 +292,20 @@ fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
|
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
|
||||||
|
|
||||||
|
@Suppress("FunctionName")
|
||||||
|
inline fun <reified T> T.DynamicClassLoader(apk: File)
|
||||||
|
= DynamicClassLoader(apk, T::class.java.classLoader)
|
||||||
|
|
||||||
|
fun Context.unwrap() : Context {
|
||||||
|
var context = this
|
||||||
|
while (true) {
|
||||||
|
if (context is ContextWrapper)
|
||||||
|
context = context.baseContext
|
||||||
|
else
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Uri.writeTo(file: File) = toFile().copyTo(file)
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
package com.topjohnwu.magisk.extensions
|
package com.topjohnwu.magisk.extensions
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.core.net.toFile
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import java.lang.reflect.Method
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
@@ -19,8 +19,6 @@ fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Uri.writeTo(file: File) = toFile().copyTo(file)
|
|
||||||
|
|
||||||
fun InputStream.writeTo(file: File) =
|
fun InputStream.writeTo(file: File) =
|
||||||
withStreams(this, file.outputStream()) { reader, writer -> reader.copyTo(writer) }
|
withStreams(this, file.outputStream()) { reader, writer -> reader.copyTo(writer) }
|
||||||
|
|
||||||
@@ -100,4 +98,33 @@ fun Locale.toLangTag(): String {
|
|||||||
tag.append('-').append(variant)
|
tag.append('-').append(variant)
|
||||||
return tag.toString()
|
return tag.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reflection hacks
|
||||||
|
|
||||||
|
private val loadClass = ClassLoader::class.java.getMethod("loadClass", String::class.java)
|
||||||
|
private val getDeclaredMethod = Class::class.java.getMethod("getDeclaredMethod",
|
||||||
|
String::class.java, arrayOf<Class<*>>()::class.java)
|
||||||
|
private val getDeclaredField = Class::class.java.getMethod("getDeclaredField", String::class.java)
|
||||||
|
|
||||||
|
fun ClassLoader.forceLoadClass(name: String) =
|
||||||
|
runCatching { loadClass.invoke(this, name) }.getOrNull() as Class<*>?
|
||||||
|
|
||||||
|
fun Class<*>.forceGetDeclaredMethod(name: String, vararg types: Class<*>) =
|
||||||
|
(runCatching { getDeclaredMethod.invoke(this, name, types) }.getOrNull() as Method?)?.also {
|
||||||
|
it.isAccessible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Class<*>.forceGetDeclaredField(name: String) =
|
||||||
|
(runCatching { getDeclaredField.invoke(this, name) }.getOrNull() as Field?)?.also {
|
||||||
|
it.isAccessible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> T.forceGetClass(name: String) =
|
||||||
|
T::class.java.classLoader?.forceLoadClass(name)
|
||||||
|
|
||||||
|
fun Class<*>.forceGetField(name: String): Field? =
|
||||||
|
forceGetDeclaredField(name) ?: superclass?.forceGetField(name)
|
||||||
|
|
||||||
|
fun Class<*>.forceGetMethod(name: String, vararg types: Class<*>): Method? =
|
||||||
|
forceGetDeclaredMethod(name, *types) ?: superclass?.forceGetMethod(name, *types)
|
||||||
|
@@ -25,3 +25,5 @@ fun String.trimEmptyToNull(): String? = if (isBlank()) null else this
|
|||||||
fun String.legalFilename() = replace(" ", "_").replace("'", "").replace("\"", "")
|
fun String.legalFilename() = replace(" ", "_").replace("'", "").replace("\"", "")
|
||||||
.replace("$", "").replace("`", "").replace("*", "").replace("/", "_")
|
.replace("$", "").replace("`", "").replace("*", "").replace("/", "_")
|
||||||
.replace("#", "").replace("@", "").replace("\\", "_")
|
.replace("#", "").replace("@", "").replace("\\", "_")
|
||||||
|
|
||||||
|
fun String.isEmptyInternal() = isNullOrBlank()
|
@@ -1,17 +1,17 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.model.download
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Notification
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import com.topjohnwu.magisk.ClassMap
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.extensions.chooser
|
import com.topjohnwu.magisk.extensions.chooser
|
||||||
import com.topjohnwu.magisk.extensions.exists
|
import com.topjohnwu.magisk.extensions.exists
|
||||||
import com.topjohnwu.magisk.extensions.provide
|
import com.topjohnwu.magisk.extensions.provide
|
||||||
|
import com.topjohnwu.magisk.intent
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.*
|
import com.topjohnwu.magisk.model.entity.internal.Configuration.*
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary
|
import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||||
@@ -63,20 +63,20 @@ open class DownloadService : RemoteFileService() {
|
|||||||
remove(id)
|
remove(id)
|
||||||
when (subject.configuration) {
|
when (subject.configuration) {
|
||||||
is APK.Upgrade -> APKInstall.install(this, subject.file)
|
is APK.Upgrade -> APKInstall.install(this, subject.file)
|
||||||
else -> Unit
|
is APK.Restore -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
override fun NotificationCompat.Builder.addActions(subject: DownloadSubject)
|
override fun Notification.Builder.addActions(subject: DownloadSubject)
|
||||||
= when (subject) {
|
= when (subject) {
|
||||||
is Magisk -> addActionsInternal(subject)
|
is Magisk -> addActionsInternal(subject)
|
||||||
is Module -> addActionsInternal(subject)
|
is Module -> addActionsInternal(subject)
|
||||||
is Manager -> addActionsInternal(subject)
|
is Manager -> addActionsInternal(subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun NotificationCompat.Builder.addActionsInternal(subject: Magisk)
|
private fun Notification.Builder.addActionsInternal(subject: Magisk)
|
||||||
= when (val conf = subject.configuration) {
|
= when (val conf = subject.configuration) {
|
||||||
Download -> this.apply {
|
Download -> this.apply {
|
||||||
fileIntent(subject.file.parentFile!!)
|
fileIntent(subject.file.parentFile!!)
|
||||||
@@ -92,7 +92,7 @@ open class DownloadService : RemoteFileService() {
|
|||||||
else -> this
|
else -> this
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun NotificationCompat.Builder.addActionsInternal(subject: Module)
|
private fun Notification.Builder.addActionsInternal(subject: Module)
|
||||||
= when (subject.configuration) {
|
= when (subject.configuration) {
|
||||||
Download -> this.apply {
|
Download -> this.apply {
|
||||||
fileIntent(subject.file.parentFile!!)
|
fileIntent(subject.file.parentFile!!)
|
||||||
@@ -106,19 +106,19 @@ open class DownloadService : RemoteFileService() {
|
|||||||
else -> this
|
else -> this
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun NotificationCompat.Builder.addActionsInternal(subject: Manager)
|
private fun Notification.Builder.addActionsInternal(subject: Manager)
|
||||||
= when (subject.configuration) {
|
= when (subject.configuration) {
|
||||||
APK.Upgrade -> setContentIntent(APKInstall.installIntent(context, subject.file))
|
APK.Upgrade -> setContentIntent(APKInstall.installIntent(context, subject.file))
|
||||||
else -> this
|
else -> this
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("ReplaceSingleLineLet")
|
@Suppress("ReplaceSingleLineLet")
|
||||||
private fun NotificationCompat.Builder.setContentIntent(intent: Intent) =
|
private fun Notification.Builder.setContentIntent(intent: Intent) =
|
||||||
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
||||||
.let { setContentIntent(it) }
|
.let { setContentIntent(it) }
|
||||||
|
|
||||||
@Suppress("ReplaceSingleLineLet")
|
@Suppress("ReplaceSingleLineLet")
|
||||||
private fun NotificationCompat.Builder.addAction(icon: Int, title: Int, intent: Intent) =
|
private fun Notification.Builder.addAction(icon: Int, title: Int, intent: Intent) =
|
||||||
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
|
||||||
.let { addAction(icon, getString(title), it) }
|
.let { addAction(icon, getString(title), it) }
|
||||||
|
|
||||||
@@ -140,8 +140,7 @@ open class DownloadService : RemoteFileService() {
|
|||||||
inline operator fun invoke(context: Context, argBuilder: Builder.() -> Unit) {
|
inline operator fun invoke(context: Context, argBuilder: Builder.() -> Unit) {
|
||||||
val app = context.applicationContext
|
val app = context.applicationContext
|
||||||
val builder = Builder().apply(argBuilder)
|
val builder = Builder().apply(argBuilder)
|
||||||
val intent = Intent(app, ClassMap[DownloadService::class.java])
|
val intent = app.intent<DownloadService>().putExtra(ARG_URL, builder.subject)
|
||||||
.putExtra(ARG_URL, builder.subject)
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
app.startForegroundService(intent)
|
app.startForegroundService(intent)
|
||||||
@@ -152,4 +151,4 @@ open class DownloadService : RemoteFileService() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,43 +1,47 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.model.download
|
||||||
|
|
||||||
import android.content.ComponentName
|
import com.topjohnwu.magisk.*
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.extensions.writeTo
|
||||||
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.Restore
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade
|
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity
|
|
||||||
import com.topjohnwu.magisk.utils.DynamicClassLoader
|
|
||||||
import com.topjohnwu.magisk.utils.PatchAPK
|
import com.topjohnwu.magisk.utils.PatchAPK
|
||||||
import com.topjohnwu.magisk.utils.RootUtils
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import timber.log.Timber
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
private fun RemoteFileService.patchPackage(apk: File, id: Int) {
|
private fun RemoteFileService.patch(apk: File, id: Int) {
|
||||||
if (packageName != BuildConfig.APPLICATION_ID) {
|
if (packageName == BuildConfig.APPLICATION_ID)
|
||||||
update(id) { notification ->
|
return
|
||||||
notification.setProgress(0, 0, true)
|
|
||||||
.setProgress(0, 0, true)
|
update(id) { notification ->
|
||||||
.setContentTitle(getString(R.string.hide_manager_title))
|
notification.setProgress(0, 0, true)
|
||||||
.setContentText("")
|
.setProgress(0, 0, true)
|
||||||
}
|
.setContentTitle(getString(R.string.hide_manager_title))
|
||||||
val patched = File(apk.parent, "patched.apk")
|
.setContentText("")
|
||||||
try {
|
}
|
||||||
// Try using the new APK to patch itself
|
val patched = File(apk.parent, "patched.apk")
|
||||||
val loader = DynamicClassLoader(apk)
|
PatchAPK.patch(apk, patched, packageName, applicationInfo.nonLocalizedLabel.toString())
|
||||||
loader.loadClass("a.a")
|
apk.delete()
|
||||||
.getMethod("patchAPK", String::class.java, String::class.java, String::class.java)
|
patched.renameTo(apk)
|
||||||
.invoke(null, apk.path, patched.path, packageName)
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e)
|
private fun RemoteFileService.upgrade(apk: File, id: Int) {
|
||||||
// Fallback to use the current implementation
|
if (isRunningAsStub) {
|
||||||
PatchAPK.patch(apk.path, patched.path, packageName)
|
// Move to upgrade location
|
||||||
}
|
apk.copyTo(DynAPK.update(this), overwrite = true)
|
||||||
apk.delete()
|
apk.delete()
|
||||||
patched.renameTo(apk)
|
if (Info.stub!!.version < Info.remote.stub.versionCode) {
|
||||||
|
// We also want to upgrade stub
|
||||||
|
service.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use {
|
||||||
|
it.writeTo(apk)
|
||||||
|
}
|
||||||
|
patch(apk, id)
|
||||||
|
} else {
|
||||||
|
// Simply relaunch the app
|
||||||
|
ProcessPhoenix.triggerRebirth(this, intent<ProcessPhoenix>())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
patch(apk, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,15 +55,11 @@ private fun RemoteFileService.restore(apk: File, id: Int) {
|
|||||||
Config.export()
|
Config.export()
|
||||||
// Make it world readable
|
// Make it world readable
|
||||||
apk.setReadable(true, false)
|
apk.setReadable(true, false)
|
||||||
if (Shell.su("pm install $apk").exec().isSuccess) {
|
Shell.su("pm install $apk && pm uninstall $packageName").exec()
|
||||||
val component = ComponentName(BuildConfig.APPLICATION_ID,
|
|
||||||
ClassMap.get<Class<*>>(SplashActivity::class.java).name)
|
|
||||||
RootUtils.rmAndLaunch(packageName, component)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RemoteFileService.handleAPK(subject: DownloadSubject.Manager)
|
fun RemoteFileService.handleAPK(subject: DownloadSubject.Manager)
|
||||||
= when (subject.configuration) {
|
= when (subject.configuration) {
|
||||||
is Upgrade -> patchPackage(subject.file, subject.hashCode())
|
is Upgrade -> upgrade(subject.file, subject.hashCode())
|
||||||
is Restore -> restore(subject.file, subject.hashCode())
|
is Restore -> restore(subject.file, subject.hashCode())
|
||||||
}
|
}
|
||||||
|
@@ -1,24 +1,22 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.model.download
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.Service
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.core.app.NotificationCompat
|
import com.topjohnwu.magisk.base.BaseService
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import org.koin.core.KoinComponent
|
import org.koin.core.KoinComponent
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.random.Random.Default.nextInt
|
import kotlin.random.Random.Default.nextInt
|
||||||
|
|
||||||
abstract class NotificationService : Service(), KoinComponent {
|
abstract class NotificationService : BaseService(), KoinComponent {
|
||||||
|
|
||||||
abstract val defaultNotification: NotificationCompat.Builder
|
abstract val defaultNotification: Notification.Builder
|
||||||
|
|
||||||
private val manager by lazy { NotificationManagerCompat.from(this) }
|
|
||||||
private val hasNotifications get() = notifications.isNotEmpty()
|
private val hasNotifications get() = notifications.isNotEmpty()
|
||||||
|
|
||||||
private val notifications =
|
private val notifications =
|
||||||
Collections.synchronizedMap(mutableMapOf<Int, NotificationCompat.Builder>())
|
Collections.synchronizedMap(mutableMapOf<Int, Notification.Builder>())
|
||||||
|
|
||||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||||
super.onTaskRemoved(rootIntent)
|
super.onTaskRemoved(rootIntent)
|
||||||
@@ -30,7 +28,7 @@ abstract class NotificationService : Service(), KoinComponent {
|
|||||||
|
|
||||||
fun update(
|
fun update(
|
||||||
id: Int,
|
id: Int,
|
||||||
body: (NotificationCompat.Builder) -> Unit = {}
|
body: (Notification.Builder) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val notification = notifications.getOrPut(id) { defaultNotification }
|
val notification = notifications.getOrPut(id) { defaultNotification }
|
||||||
|
|
||||||
@@ -43,7 +41,7 @@ abstract class NotificationService : Service(), KoinComponent {
|
|||||||
|
|
||||||
protected fun finishNotify(
|
protected fun finishNotify(
|
||||||
id: Int,
|
id: Int,
|
||||||
editBody: (NotificationCompat.Builder) -> NotificationCompat.Builder? = { null }
|
editBody: (Notification.Builder) -> Notification.Builder? = { null }
|
||||||
) : Int {
|
) : Int {
|
||||||
val currentNotification = remove(id)?.run(editBody)
|
val currentNotification = remove(id)?.run(editBody)
|
||||||
|
|
||||||
@@ -62,11 +60,11 @@ abstract class NotificationService : Service(), KoinComponent {
|
|||||||
// ---
|
// ---
|
||||||
|
|
||||||
private fun notify(id: Int, notification: Notification) {
|
private fun notify(id: Int, notification: Notification) {
|
||||||
manager.notify(id, notification)
|
Notifications.mgr.notify(id, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancel(id: Int) {
|
private fun cancel(id: Int) {
|
||||||
manager.cancel(id)
|
Notifications.mgr.cancel(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun remove(id: Int) = notifications.remove(id).also {
|
protected fun remove(id: Int) = notifications.remove(id).also {
|
||||||
@@ -84,4 +82,4 @@ abstract class NotificationService : Service(), KoinComponent {
|
|||||||
// --
|
// --
|
||||||
|
|
||||||
override fun onBind(p0: Intent?): IBinder? = null
|
override fun onBind(p0: Intent?): IBinder? = null
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
package com.topjohnwu.magisk.model.download
|
package com.topjohnwu.magisk.model.download
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.Notification
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
import com.topjohnwu.magisk.di.NullActivity
|
import com.topjohnwu.magisk.di.NullActivity
|
||||||
@@ -22,9 +22,9 @@ import java.io.InputStream
|
|||||||
|
|
||||||
abstract class RemoteFileService : NotificationService() {
|
abstract class RemoteFileService : NotificationService() {
|
||||||
|
|
||||||
private val service: GithubRawServices by inject()
|
val service: GithubRawServices by inject()
|
||||||
|
|
||||||
override val defaultNotification: NotificationCompat.Builder
|
override val defaultNotification
|
||||||
get() = Notifications.progress(this, "")
|
get() = Notifications.progress(this, "")
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
@@ -108,11 +108,11 @@ abstract class RemoteFileService : NotificationService() {
|
|||||||
@Throws(Throwable::class)
|
@Throws(Throwable::class)
|
||||||
protected abstract fun onFinished(subject: DownloadSubject, id: Int)
|
protected abstract fun onFinished(subject: DownloadSubject, id: Int)
|
||||||
|
|
||||||
protected abstract fun NotificationCompat.Builder.addActions(subject: DownloadSubject)
|
protected abstract fun Notification.Builder.addActions(subject: DownloadSubject)
|
||||||
: NotificationCompat.Builder
|
: Notification.Builder
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ARG_URL = "arg_url"
|
const val ARG_URL = "arg_url"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,14 @@
|
|||||||
package com.topjohnwu.magisk.model.entity
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.Ignore
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.topjohnwu.magisk.extensions.now
|
||||||
import com.topjohnwu.magisk.extensions.timeFormatTime
|
import com.topjohnwu.magisk.extensions.timeFormatTime
|
||||||
import com.topjohnwu.magisk.extensions.toTime
|
import com.topjohnwu.magisk.extensions.toTime
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.ALLOW
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.ALLOW
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
@Entity(tableName = "logs")
|
||||||
data class MagiskLog(
|
data class MagiskLog(
|
||||||
val fromUid: Int,
|
val fromUid: Int,
|
||||||
val toUid: Int,
|
val toUid: Int,
|
||||||
@@ -13,9 +17,10 @@ data class MagiskLog(
|
|||||||
val appName: String,
|
val appName: String,
|
||||||
val command: String,
|
val command: String,
|
||||||
val action: Boolean,
|
val action: Boolean,
|
||||||
val date: Date
|
val time: Long = -1
|
||||||
) {
|
) {
|
||||||
val timeString = date.time.toTime(timeFormatTime)
|
@PrimaryKey(autoGenerate = true) var id: Int = 0
|
||||||
|
@Ignore val timeString = time.toTime(timeFormatTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class WrappedMagiskLog(
|
data class WrappedMagiskLog(
|
||||||
@@ -23,35 +28,8 @@ data class WrappedMagiskLog(
|
|||||||
val items: List<MagiskLog>
|
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(
|
fun MagiskPolicy.toLog(
|
||||||
toUid: Int,
|
toUid: Int,
|
||||||
fromPid: Int,
|
fromPid: Int,
|
||||||
command: String,
|
command: String
|
||||||
date: Date
|
) = MagiskLog(uid, toUid, fromPid, packageName, appName, command, policy == ALLOW, now)
|
||||||
) = MagiskLog(uid, toUid, fromPid, packageName, appName, command, policy == ALLOW, date)
|
|
||||||
|
@@ -7,11 +7,11 @@ import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.INTERACTIVE
|
|||||||
|
|
||||||
|
|
||||||
data class MagiskPolicy(
|
data class MagiskPolicy(
|
||||||
val uid: Int,
|
var uid: Int,
|
||||||
val packageName: String,
|
val packageName: String,
|
||||||
val appName: String,
|
val appName: String,
|
||||||
val policy: Int = INTERACTIVE,
|
var policy: Int = INTERACTIVE,
|
||||||
val until: Long = -1L,
|
var until: Long = -1L,
|
||||||
val logging: Boolean = true,
|
val logging: Boolean = true,
|
||||||
val notification: Boolean = true,
|
val notification: Boolean = true,
|
||||||
val applicationInfo: ApplicationInfo
|
val applicationInfo: ApplicationInfo
|
||||||
@@ -38,7 +38,7 @@ fun MagiskPolicy.toMap() = mapOf(
|
|||||||
fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
|
fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
|
||||||
val uid = get("uid")?.toIntOrNull() ?: -1
|
val uid = get("uid")?.toIntOrNull() ?: -1
|
||||||
val packageName = get("package_name").orEmpty()
|
val packageName = get("package_name").orEmpty()
|
||||||
val info = pm.getApplicationInfo(packageName, 0)
|
val info = pm.getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES)
|
||||||
|
|
||||||
if (info.uid != uid)
|
if (info.uid != uid)
|
||||||
throw PackageManager.NameNotFoundException()
|
throw PackageManager.NameNotFoundException()
|
||||||
@@ -56,14 +56,15 @@ fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(PackageManager.NameNotFoundException::class)
|
@Throws(PackageManager.NameNotFoundException::class)
|
||||||
fun Int.toPolicy(pm: PackageManager): MagiskPolicy {
|
fun Int.toPolicy(pm: PackageManager, policy: Int = INTERACTIVE): MagiskPolicy {
|
||||||
val pkg = pm.getPackagesForUid(this)?.firstOrNull()
|
val pkg = pm.getPackagesForUid(this)?.firstOrNull()
|
||||||
?: throw PackageManager.NameNotFoundException()
|
?: throw PackageManager.NameNotFoundException()
|
||||||
val info = pm.getApplicationInfo(pkg, 0)
|
val info = pm.getApplicationInfo(pkg, PackageManager.GET_UNINSTALLED_PACKAGES)
|
||||||
return MagiskPolicy(
|
return MagiskPolicy(
|
||||||
uid = this,
|
uid = info.uid,
|
||||||
packageName = pkg,
|
packageName = pkg,
|
||||||
|
policy = policy,
|
||||||
applicationInfo = info,
|
applicationInfo = info,
|
||||||
appName = info.loadLabel(pm).toString()
|
appName = info.getLabel(pm)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,8 @@ import se.ansman.kotshi.JsonSerializable
|
|||||||
data class UpdateInfo(
|
data class UpdateInfo(
|
||||||
val app: ManagerJson = ManagerJson(),
|
val app: ManagerJson = ManagerJson(),
|
||||||
val uninstaller: UninstallerJson = UninstallerJson(),
|
val uninstaller: UninstallerJson = UninstallerJson(),
|
||||||
val magisk: MagiskJson = MagiskJson()
|
val magisk: MagiskJson = MagiskJson(),
|
||||||
|
val stub: StubJson = StubJson()
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonSerializable
|
@JsonSerializable
|
||||||
@@ -33,3 +34,9 @@ data class ManagerJson(
|
|||||||
val link: String = "",
|
val link: String = "",
|
||||||
val note: String = ""
|
val note: String = ""
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
@JsonSerializable
|
||||||
|
data class StubJson(
|
||||||
|
val versionCode: Int = -1,
|
||||||
|
val link: String = ""
|
||||||
|
)
|
||||||
|
@@ -13,17 +13,21 @@ class Module(path: String) : BaseModule() {
|
|||||||
override var versionCode: Int = -1
|
override var versionCode: Int = -1
|
||||||
override var description: String = ""
|
override var description: String = ""
|
||||||
|
|
||||||
private val removeFile: SuFile = SuFile(path, "remove")
|
private val removeFile = SuFile(path, "remove")
|
||||||
private val disableFile: SuFile = SuFile(path, "disable")
|
private val disableFile = SuFile(path, "disable")
|
||||||
private val updateFile: SuFile = SuFile(path, "update")
|
private val updateFile = SuFile(path, "update")
|
||||||
|
private val ruleFile = SuFile(path, "sepolicy.rule")
|
||||||
|
|
||||||
val updated: Boolean = updateFile.exists()
|
val updated: Boolean = updateFile.exists()
|
||||||
|
|
||||||
var enable: Boolean = !disableFile.exists()
|
var enable: Boolean = !disableFile.exists()
|
||||||
set(enable) {
|
set(enable) {
|
||||||
|
val dir = "$PERSIST/$id"
|
||||||
field = if (enable) {
|
field = if (enable) {
|
||||||
|
Shell.su("mkdir -p $dir", "cp -af $ruleFile $dir").submit()
|
||||||
disableFile.delete()
|
disableFile.delete()
|
||||||
} else {
|
} else {
|
||||||
|
Shell.su("rm -rf $dir").submit()
|
||||||
!disableFile.createNewFile()
|
!disableFile.createNewFile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,8 +35,10 @@ class Module(path: String) : BaseModule() {
|
|||||||
var remove: Boolean = removeFile.exists()
|
var remove: Boolean = removeFile.exists()
|
||||||
set(remove) {
|
set(remove) {
|
||||||
field = if (remove) {
|
field = if (remove) {
|
||||||
|
Shell.su("rm -rf $PERSIST/$id").submit()
|
||||||
removeFile.createNewFile()
|
removeFile.createNewFile()
|
||||||
} else {
|
} else {
|
||||||
|
Shell.su("cp -af $ruleFile $PERSIST/$id").submit()
|
||||||
!removeFile.delete()
|
!removeFile.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,6 +60,8 @@ class Module(path: String) : BaseModule() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
private const val PERSIST = "/sbin/.magisk/mirror/persist/magisk"
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun loadModules(): List<Module> {
|
fun loadModules(): List<Module> {
|
||||||
val moduleList = mutableListOf<Module>()
|
val moduleList = mutableListOf<Module>()
|
||||||
@@ -65,7 +73,7 @@ class Module(path: String) : BaseModule() {
|
|||||||
val module = Module(Const.MAGISK_PATH + "/" + file.name)
|
val module = Module(Const.MAGISK_PATH + "/" + file.name)
|
||||||
moduleList.add(module)
|
moduleList.add(module)
|
||||||
}
|
}
|
||||||
return moduleList.sortedBy { it.name }
|
return moduleList.sortedBy { it.name.toLowerCase() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -37,8 +37,10 @@ class LogItemRvItem(
|
|||||||
|
|
||||||
fun toggle() = isExpanded.toggle()
|
fun toggle() = isExpanded.toggle()
|
||||||
|
|
||||||
override fun contentSameAs(other: LogItemRvItem): Boolean = items
|
override fun contentSameAs(other: LogItemRvItem): Boolean {
|
||||||
.any { !other.items.contains(it) }
|
if (items.size != other.items.size) return false
|
||||||
|
return items.all { it in other.items }
|
||||||
|
}
|
||||||
|
|
||||||
override fun itemSameAs(other: LogItemRvItem): Boolean = date == other.date
|
override fun itemSameAs(other: LogItemRvItem): Boolean = date == other.date
|
||||||
}
|
}
|
||||||
@@ -50,13 +52,7 @@ class LogItemEntryRvItem(val item: MagiskLog) : ComparableRvItem<LogItemEntryRvI
|
|||||||
|
|
||||||
fun toggle() = isExpanded.toggle()
|
fun toggle() = isExpanded.toggle()
|
||||||
|
|
||||||
override fun contentSameAs(other: LogItemEntryRvItem) = item.fromUid == other.item.fromUid &&
|
override fun contentSameAs(other: LogItemEntryRvItem) = item == other.item
|
||||||
item.toUid == other.item.toUid &&
|
|
||||||
item.fromPid == other.item.fromPid &&
|
|
||||||
item.packageName == other.item.packageName &&
|
|
||||||
item.command == other.item.command &&
|
|
||||||
item.action == other.item.action &&
|
|
||||||
item.date == other.item.date
|
|
||||||
|
|
||||||
override fun itemSameAs(other: LogItemEntryRvItem) = item.appName == other.item.appName
|
override fun itemSameAs(other: LogItemEntryRvItem) = item.appName == other.item.appName
|
||||||
}
|
}
|
||||||
@@ -74,4 +70,4 @@ class MagiskLogRvItem : ComparableRvItem<MagiskLogRvItem>() {
|
|||||||
override fun contentSameAs(other: MagiskLogRvItem): Boolean = false
|
override fun contentSameAs(other: MagiskLogRvItem): Boolean = false
|
||||||
|
|
||||||
override fun itemSameAs(other: MagiskLogRvItem): Boolean = false
|
override fun itemSameAs(other: MagiskLogRvItem): Boolean = false
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.model.events
|
package com.topjohnwu.magisk.model.events
|
||||||
|
|
||||||
import android.app.Activity
|
import com.topjohnwu.magisk.base.BaseActivity
|
||||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||||
import io.reactivex.subjects.PublishSubject
|
import io.reactivex.subjects.PublishSubject
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ class EnvFixEvent : ViewEvent()
|
|||||||
|
|
||||||
class UpdateSafetyNetEvent : ViewEvent()
|
class UpdateSafetyNetEvent : ViewEvent()
|
||||||
|
|
||||||
class ViewActionEvent(val action: Activity.() -> Unit) : ViewEvent()
|
class ViewActionEvent(val action: BaseActivity<*, *>.() -> Unit) : ViewEvent()
|
||||||
|
|
||||||
class OpenFilePickerEvent : ViewEvent()
|
class OpenFilePickerEvent : ViewEvent()
|
||||||
|
|
||||||
|
@@ -1,76 +1,46 @@
|
|||||||
package com.topjohnwu.magisk.model.receiver
|
package com.topjohnwu.magisk.model.receiver
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.ContextWrapper
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
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.Info
|
import com.topjohnwu.magisk.Info
|
||||||
|
import com.topjohnwu.magisk.base.BaseReceiver
|
||||||
import com.topjohnwu.magisk.data.database.PolicyDao
|
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.extensions.reboot
|
||||||
import com.topjohnwu.magisk.model.download.DownloadService
|
import com.topjohnwu.magisk.model.download.DownloadService
|
||||||
import com.topjohnwu.magisk.model.entity.ManagerJson
|
import com.topjohnwu.magisk.model.entity.ManagerJson
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
|
import com.topjohnwu.magisk.utils.SuHandler
|
||||||
import com.topjohnwu.magisk.utils.SuLogger
|
|
||||||
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
|
||||||
|
import org.koin.core.inject
|
||||||
|
|
||||||
open class GeneralReceiver : BroadcastReceiver() {
|
open class GeneralReceiver : BaseReceiver() {
|
||||||
|
|
||||||
private val policyDB: PolicyDao by inject()
|
private val policyDB: PolicyDao by inject()
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val REQUEST = "request"
|
|
||||||
const val LOG = "log"
|
|
||||||
const val NOTIFY = "notify"
|
|
||||||
const val TEST = "test"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getPkg(intent: Intent): String {
|
private fun getPkg(intent: Intent): String {
|
||||||
return intent.data?.encodedSchemeSpecificPart.orEmpty()
|
return intent.data?.encodedSchemeSpecificPart.orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent?) {
|
override fun onReceive(context: ContextWrapper, intent: Intent?) {
|
||||||
intent ?: return
|
intent ?: return
|
||||||
|
|
||||||
when (intent.action ?: return) {
|
when (intent.action ?: return) {
|
||||||
Intent.ACTION_REBOOT, Intent.ACTION_BOOT_COMPLETED -> {
|
Intent.ACTION_REBOOT -> {
|
||||||
val action = intent.getStringExtra("action")
|
SuHandler(context, intent.getStringExtra("action"), intent.extras)
|
||||||
if (action == null) {
|
|
||||||
// Actual boot completed event
|
|
||||||
Shell.su("mm_patch_dtbo").submit {
|
|
||||||
if (it.isSuccess)
|
|
||||||
Notifications.dtboPatched(context)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
when (action) {
|
|
||||||
REQUEST -> {
|
|
||||||
val i = Intent(context, ClassMap[SuRequestActivity::class.java])
|
|
||||||
.setAction(action)
|
|
||||||
.putExtra("socket", intent.getStringExtra("socket"))
|
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
|
|
||||||
context.startActivity(i)
|
|
||||||
}
|
|
||||||
LOG -> SuLogger.handleLogs(intent)
|
|
||||||
NOTIFY -> SuLogger.handleNotify(intent)
|
|
||||||
TEST -> Shell.su("magisk --use-broadcast").submit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Intent.ACTION_PACKAGE_REPLACED ->
|
Intent.ACTION_PACKAGE_REPLACED -> {
|
||||||
// This will only work pre-O
|
// This will only work pre-O
|
||||||
if (Config.suReAuth)
|
if (Config.suReAuth)
|
||||||
policyDB.delete(getPkg(intent)).blockingGet()
|
policyDB.delete(getPkg(intent)).blockingGet()
|
||||||
|
}
|
||||||
Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
|
Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
|
||||||
val pkg = getPkg(intent)
|
val pkg = getPkg(intent)
|
||||||
policyDB.delete(pkg).blockingGet()
|
policyDB.delete(pkg).blockingGet()
|
||||||
"magiskhide --rm $pkg".su().blockingGet()
|
Shell.su("magiskhide --rm $pkg").submit()
|
||||||
}
|
}
|
||||||
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context)
|
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context)
|
||||||
Const.Key.BROADCAST_MANAGER_UPDATE -> {
|
Const.Key.BROADCAST_MANAGER_UPDATE -> {
|
||||||
|
@@ -20,7 +20,7 @@ class UpdateCheckService : DelegateWorker() {
|
|||||||
magiskRepo.fetchUpdate().blockingGet()
|
magiskRepo.fetchUpdate().blockingGet()
|
||||||
if (BuildConfig.VERSION_CODE < Info.remote.app.versionCode)
|
if (BuildConfig.VERSION_CODE < Info.remote.app.versionCode)
|
||||||
Notifications.managerUpdate(applicationContext)
|
Notifications.managerUpdate(applicationContext)
|
||||||
else if (Info.magiskVersionCode < Info.remote.magisk.versionCode)
|
else if (Info.env.magiskVersionCode < Info.remote.magisk.versionCode)
|
||||||
Notifications.magiskUpdate(applicationContext)
|
Notifications.magiskUpdate(applicationContext)
|
||||||
ListenableWorker.Result.success()
|
ListenableWorker.Result.success()
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
|
@@ -266,7 +266,7 @@ abstract class MagiskInstaller {
|
|||||||
|
|
||||||
val patched = File(installDir, "new-boot.img")
|
val patched = File(installDir, "new-boot.img")
|
||||||
if (isSigned) {
|
if (isSigned) {
|
||||||
console.add("- Signing boot image with test keys")
|
console.add("- Signing boot image with verity keys")
|
||||||
val signed = File(installDir, "signed.img")
|
val signed = File(installDir, "signed.img")
|
||||||
try {
|
try {
|
||||||
withStreams(SuFileInputStream(patched), signed.outputStream().buffered()) {
|
withStreams(SuFileInputStream(patched), signed.outputStream().buffered()) {
|
||||||
@@ -287,8 +287,10 @@ abstract class MagiskInstaller {
|
|||||||
protected fun flashBoot(): Boolean {
|
protected fun flashBoot(): Boolean {
|
||||||
if (!"direct_install $installDir $srcBoot".sh().isSuccess)
|
if (!"direct_install $installDir $srcBoot".sh().isSuccess)
|
||||||
return false
|
return false
|
||||||
if (!Info.keepVerity)
|
arrayOf(
|
||||||
"patch_dtbo_image".sh()
|
"(KEEPVERITY=${Info.keepVerity} patch_dtb_partitions)",
|
||||||
|
"run_migrations"
|
||||||
|
).sh()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,13 +2,13 @@ package com.topjohnwu.magisk.ui
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentTransaction
|
import androidx.fragment.app.FragmentTransaction
|
||||||
import com.ncapdevi.fragnav.FragNavController
|
import com.ncapdevi.fragnav.FragNavController
|
||||||
import com.ncapdevi.fragnav.FragNavTransactionOptions
|
import com.ncapdevi.fragnav.FragNavTransactionOptions
|
||||||
import com.topjohnwu.magisk.ClassMap
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.Config
|
|
||||||
import com.topjohnwu.magisk.Const.Key.OPEN_SECTION
|
import com.topjohnwu.magisk.Const.Key.OPEN_SECTION
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.Info
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
@@ -17,6 +17,7 @@ import com.topjohnwu.magisk.base.BaseFragment
|
|||||||
import com.topjohnwu.magisk.databinding.ActivityMainBinding
|
import com.topjohnwu.magisk.databinding.ActivityMainBinding
|
||||||
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||||
import com.topjohnwu.magisk.extensions.snackbar
|
import com.topjohnwu.magisk.extensions.snackbar
|
||||||
|
import com.topjohnwu.magisk.intent
|
||||||
import com.topjohnwu.magisk.model.events.*
|
import com.topjohnwu.magisk.model.events.*
|
||||||
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
|
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
|
||||||
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
|
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
|
||||||
@@ -30,7 +31,6 @@ import com.topjohnwu.magisk.ui.module.ReposFragment
|
|||||||
import com.topjohnwu.magisk.ui.settings.SettingsFragment
|
import com.topjohnwu.magisk.ui.settings.SettingsFragment
|
||||||
import com.topjohnwu.magisk.ui.superuser.SuperuserFragment
|
import com.topjohnwu.magisk.ui.superuser.SuperuserFragment
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@@ -40,8 +40,8 @@ open class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>(), Na
|
|||||||
|
|
||||||
override val layoutRes: Int = R.layout.activity_main
|
override val layoutRes: Int = R.layout.activity_main
|
||||||
override val viewModel: MainViewModel by viewModel()
|
override val viewModel: MainViewModel by viewModel()
|
||||||
override val navHostId: Int = R.id.main_nav_host
|
private val navHostId: Int = R.id.main_nav_host
|
||||||
override val defaultPosition: Int = 0
|
private val defaultPosition: Int = 0
|
||||||
|
|
||||||
private val navigationController by lazy {
|
private val navigationController by lazy {
|
||||||
FragNavController(supportFragmentManager, navHostId)
|
FragNavController(supportFragmentManager, navHostId)
|
||||||
@@ -61,12 +61,21 @@ open class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>(), Na
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
if (!SplashActivity.DONE) {
|
if (!SplashActivity.DONE) {
|
||||||
startActivity(Intent(this, ClassMap[SplashActivity::class.java]))
|
startActivity(intent<SplashActivity>())
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
if (Info.env.isUnsupported && !viewModel.shownUnsupportedDialog) {
|
||||||
|
viewModel.shownUnsupportedDialog = true
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.unsupport_magisk_title)
|
||||||
|
.setMessage(getString(R.string.unsupport_magisk_msg, Const.Version.MIN_VERSION))
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
navigationController.apply {
|
navigationController.apply {
|
||||||
rootFragmentListener = this@MainActivity
|
rootFragmentListener = this@MainActivity
|
||||||
transactionListener = this@MainActivity
|
transactionListener = this@MainActivity
|
||||||
@@ -154,16 +163,11 @@ open class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>(), Na
|
|||||||
|
|
||||||
private fun checkHideSection() {
|
private fun checkHideSection() {
|
||||||
val menu = binding.navView.menu
|
val menu = binding.navView.menu
|
||||||
menu.findItem(R.id.magiskHideFragment).isVisible =
|
menu.findItem(R.id.magiskHideFragment).isVisible = Info.env.isActive && Info.env.magiskHide
|
||||||
Shell.rootAccess() && Config.magiskHide
|
menu.findItem(R.id.modulesFragment).isVisible = Info.env.isActive
|
||||||
menu.findItem(R.id.modulesFragment).isVisible =
|
menu.findItem(R.id.reposFragment).isVisible = Info.isConnected.value && Info.env.isActive
|
||||||
Shell.rootAccess() && Info.magiskVersionCode >= 0
|
menu.findItem(R.id.logFragment).isVisible = Info.env.isActive
|
||||||
menu.findItem(R.id.reposFragment).isVisible =
|
menu.findItem(R.id.superuserFragment).isVisible = Utils.showSuperUser()
|
||||||
(viewModel.isConnected.value && Shell.rootAccess() && Info.magiskVersionCode >= 0)
|
|
||||||
menu.findItem(R.id.logFragment).isVisible =
|
|
||||||
Shell.rootAccess()
|
|
||||||
menu.findItem(R.id.superuserFragment).isVisible =
|
|
||||||
Utils.showSuperUser()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =
|
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =
|
||||||
|
@@ -8,6 +8,8 @@ import com.topjohnwu.magisk.model.navigation.Navigation
|
|||||||
|
|
||||||
class MainViewModel : BaseViewModel() {
|
class MainViewModel : BaseViewModel() {
|
||||||
|
|
||||||
|
var shownUnsupportedDialog = false
|
||||||
|
|
||||||
fun navPressed() = Navigation.Main.OPEN_NAV.publish()
|
fun navPressed() = Navigation.Main.OPEN_NAV.publish()
|
||||||
|
|
||||||
fun navigationItemPressed(item: MenuItem): Boolean {
|
fun navigationItemPressed(item: MenuItem): Boolean {
|
||||||
|
@@ -1,33 +1,24 @@
|
|||||||
package com.topjohnwu.magisk.ui
|
package com.topjohnwu.magisk.ui
|
||||||
|
|
||||||
import android.content.Intent
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.*
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
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
|
||||||
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
|
|
||||||
open class SplashActivity : AppCompatActivity() {
|
open class SplashActivity : Activity() {
|
||||||
|
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
super.attachBaseContext(base.wrap())
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
Shell.getShell { initAndStart() }
|
||||||
Shell.getShell {
|
|
||||||
if (Info.magiskVersionCode > 0 && Info.magiskVersionCode < Const.MagiskVersion.MIN_SUPPORT) {
|
|
||||||
AlertDialog.Builder(this)
|
|
||||||
.setTitle(R.string.unsupport_magisk_title)
|
|
||||||
.setMessage(R.string.unsupport_magisk_message)
|
|
||||||
.setNegativeButton(R.string.ok, null)
|
|
||||||
.setOnDismissListener { finish() }
|
|
||||||
.show()
|
|
||||||
} else {
|
|
||||||
initAndStart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initAndStart() {
|
private fun initAndStart() {
|
||||||
@@ -36,7 +27,7 @@ open class SplashActivity : AppCompatActivity() {
|
|||||||
Config.suManager = ""
|
Config.suManager = ""
|
||||||
Shell.su("pm uninstall $pkg").submit()
|
Shell.su("pm uninstall $pkg").submit()
|
||||||
}
|
}
|
||||||
if (TextUtils.equals(pkg, packageName)) {
|
if (pkg == packageName) {
|
||||||
runCatching {
|
runCatching {
|
||||||
// We are the manager, remove com.topjohnwu.magisk as it could be malware
|
// We are the manager, remove com.topjohnwu.magisk as it could be malware
|
||||||
packageManager.getApplicationInfo(BuildConfig.APPLICATION_ID, 0)
|
packageManager.getApplicationInfo(BuildConfig.APPLICATION_ID, 0)
|
||||||
@@ -44,6 +35,10 @@ open class SplashActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Info.keepVerity = ShellUtils.fastCmd("echo \$KEEPVERITY").toBoolean()
|
||||||
|
Info.keepEnc = ShellUtils.fastCmd("echo \$KEEPFORCEENCRYPT").toBoolean()
|
||||||
|
Info.recovery = ShellUtils.fastCmd("echo \$RECOVERYMODE").toBoolean()
|
||||||
|
|
||||||
// Set default configs
|
// Set default configs
|
||||||
Config.initialize()
|
Config.initialize()
|
||||||
|
|
||||||
@@ -56,10 +51,18 @@ open class SplashActivity : AppCompatActivity() {
|
|||||||
// Setup shortcuts
|
// Setup shortcuts
|
||||||
Shortcuts.setup(this)
|
Shortcuts.setup(this)
|
||||||
|
|
||||||
val intent = Intent(this, ClassMap[MainActivity::class.java])
|
if (Info.isNewReboot) {
|
||||||
intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION))
|
val shell = Shell.newInstance()
|
||||||
|
shell.newJob().add("mm_patch_dtb").submit {
|
||||||
|
if (it.isSuccess)
|
||||||
|
Notifications.dtboPatched(this)
|
||||||
|
shell.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DONE = true
|
DONE = true
|
||||||
startActivity(intent)
|
|
||||||
|
startActivity(intent<MainActivity>().apply { intent?.also { putExtras(it) } })
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,20 +2,21 @@ package com.topjohnwu.magisk.ui.flash
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.topjohnwu.magisk.ClassMap
|
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.base.BaseActivity
|
import com.topjohnwu.magisk.base.BaseActivity
|
||||||
import com.topjohnwu.magisk.databinding.ActivityFlashBinding
|
import com.topjohnwu.magisk.databinding.ActivityFlashBinding
|
||||||
import com.topjohnwu.magisk.extensions.snackbar
|
import com.topjohnwu.magisk.extensions.snackbar
|
||||||
|
import com.topjohnwu.magisk.intent
|
||||||
import com.topjohnwu.magisk.model.events.BackPressEvent
|
import com.topjohnwu.magisk.model.events.BackPressEvent
|
||||||
import com.topjohnwu.magisk.model.events.PermissionEvent
|
import com.topjohnwu.magisk.model.events.PermissionEvent
|
||||||
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
import org.koin.core.parameter.parametersOf
|
import org.koin.core.parameter.parametersOf
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -23,6 +24,7 @@ import java.io.File
|
|||||||
open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>() {
|
open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.activity_flash
|
override val layoutRes: Int = R.layout.activity_flash
|
||||||
|
override val themeRes: Int = R.style.MagiskTheme_Flashing
|
||||||
override val viewModel: FlashViewModel by viewModel {
|
override val viewModel: FlashViewModel by viewModel {
|
||||||
val uri = intent.data ?: let { finish(); Uri.EMPTY }
|
val uri = intent.data ?: let { finish(); Uri.EMPTY }
|
||||||
val additionalUri = intent.getParcelableExtra(Const.Key.FLASH_DATA) ?: uri
|
val additionalUri = intent.getParcelableExtra(Const.Key.FLASH_DATA) ?: uri
|
||||||
@@ -31,10 +33,11 @@ open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val id = intent.getIntExtra(Const.Key.DISMISS_ID, -1)
|
val id = intent.getIntExtra(Const.Key.DISMISS_ID, -1)
|
||||||
if (id != -1)
|
if (id != -1)
|
||||||
NotificationManagerCompat.from(this).cancel(id)
|
Notifications.mgr.cancel(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
@@ -59,7 +62,7 @@ open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>()
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private fun intent(context: Context) = Intent(context, ClassMap[FlashActivity::class.java])
|
private fun intent(context: Context) = context.intent<FlashActivity>()
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
private fun intent(context: Context, file: File) = intent(context).setData(file.toUri())
|
private fun intent(context: Context, file: File) = intent(context).setData(file.toUri())
|
||||||
|
|
||||||
|
@@ -90,9 +90,7 @@ class HideViewModel(
|
|||||||
.toList()
|
.toList()
|
||||||
.map { it to items.calculateDiff(it) }
|
.map { it to items.calculateDiff(it) }
|
||||||
|
|
||||||
private fun toggleItem(item: HideProcessRvItem) = magiskRepo
|
private fun toggleItem(item: HideProcessRvItem) =
|
||||||
.toggleHide(item.isHidden.value, item.packageName, item.process)
|
magiskRepo.toggleHide(item.isHidden.value, item.packageName, item.process)
|
||||||
.subscribeK()
|
|
||||||
.add()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -9,11 +9,11 @@ import com.topjohnwu.magisk.base.BaseActivity
|
|||||||
import com.topjohnwu.magisk.base.BaseFragment
|
import com.topjohnwu.magisk.base.BaseFragment
|
||||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||||
import com.topjohnwu.magisk.databinding.FragmentMagiskBinding
|
import com.topjohnwu.magisk.databinding.FragmentMagiskBinding
|
||||||
|
import com.topjohnwu.magisk.extensions.DynamicClassLoader
|
||||||
import com.topjohnwu.magisk.extensions.openUrl
|
import com.topjohnwu.magisk.extensions.openUrl
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.extensions.writeTo
|
import com.topjohnwu.magisk.extensions.writeTo
|
||||||
import com.topjohnwu.magisk.model.events.*
|
import com.topjohnwu.magisk.model.events.*
|
||||||
import com.topjohnwu.magisk.utils.DynamicClassLoader
|
|
||||||
import com.topjohnwu.magisk.utils.SafetyNetHelper
|
import com.topjohnwu.magisk.utils.SafetyNetHelper
|
||||||
import com.topjohnwu.magisk.view.MarkDownWindow
|
import com.topjohnwu.magisk.view.MarkDownWindow
|
||||||
import com.topjohnwu.magisk.view.dialogs.*
|
import com.topjohnwu.magisk.view.dialogs.*
|
||||||
@@ -87,8 +87,8 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentMagiskBinding>(),
|
|||||||
.setTitle(R.string.proprietary_title)
|
.setTitle(R.string.proprietary_title)
|
||||||
.setMessage(R.string.proprietary_notice)
|
.setMessage(R.string.proprietary_notice)
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(R.string.yes) { _, _ -> download() }
|
.setPositiveButton(android.R.string.yes) { _, _ -> download() }
|
||||||
.setNegativeButton(R.string.no_thanks) { _, _ -> viewModel.finishSafetyNetCheck(-2) }
|
.setNegativeButton(android.R.string.no) { _, _ -> viewModel.finishSafetyNetCheck(-2) }
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -77,7 +77,7 @@ class HomeViewModel(
|
|||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
val safetyNetTitle = KObservableField(R.string.safetyNet_check_text)
|
val safetyNetTitle = KObservableField(R.string.safetyNet_check_text.res())
|
||||||
val ctsState = KObservableField(SafetyNetState.IDLE)
|
val ctsState = KObservableField(SafetyNetState.IDLE)
|
||||||
val basicIntegrityState = KObservableField(SafetyNetState.IDLE)
|
val basicIntegrityState = KObservableField(SafetyNetState.IDLE)
|
||||||
val safetyNetState = Observer(ctsState, basicIntegrityState) {
|
val safetyNetState = Observer(ctsState, basicIntegrityState) {
|
||||||
@@ -92,7 +92,7 @@ class HomeViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val hasRoot = KObservableField(false)
|
val isActive = KObservableField(false)
|
||||||
|
|
||||||
private var shownDialog = false
|
private var shownDialog = false
|
||||||
|
|
||||||
@@ -107,10 +107,10 @@ class HomeViewModel(
|
|||||||
Info.recovery = it ?: return@addOnPropertyChangedCallback
|
Info.recovery = it ?: return@addOnPropertyChangedCallback
|
||||||
}
|
}
|
||||||
isConnected.addOnPropertyChangedCallback {
|
isConnected.addOnPropertyChangedCallback {
|
||||||
if (it == true) refresh()
|
if (it == true) refresh(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh()
|
refresh(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun paypalPressed() = OpenLinkEvent(Const.Url.PAYPAL_URL).publish()
|
fun paypalPressed() = OpenLinkEvent(Const.Url.PAYPAL_URL).publish()
|
||||||
@@ -135,7 +135,7 @@ class HomeViewModel(
|
|||||||
fun safetyNetPressed() {
|
fun safetyNetPressed() {
|
||||||
ctsState.value = SafetyNetState.LOADING
|
ctsState.value = SafetyNetState.LOADING
|
||||||
basicIntegrityState.value = SafetyNetState.LOADING
|
basicIntegrityState.value = SafetyNetState.LOADING
|
||||||
safetyNetTitle.value = R.string.checking_safetyNet_status
|
safetyNetTitle.value = R.string.checking_safetyNet_status.res()
|
||||||
|
|
||||||
UpdateSafetyNetEvent().publish()
|
UpdateSafetyNetEvent().publish()
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,7 @@ class HomeViewModel(
|
|||||||
response and 0x0F == 0 -> {
|
response and 0x0F == 0 -> {
|
||||||
val hasCtsPassed = response and SafetyNetHelper.CTS_PASS != 0
|
val hasCtsPassed = response and SafetyNetHelper.CTS_PASS != 0
|
||||||
val hasBasicIntegrityPassed = response and SafetyNetHelper.BASIC_PASS != 0
|
val hasBasicIntegrityPassed = response and SafetyNetHelper.BASIC_PASS != 0
|
||||||
safetyNetTitle.value = R.string.safetyNet_check_success
|
safetyNetTitle.value = R.string.safetyNet_check_success.res()
|
||||||
ctsState.value = if (hasCtsPassed) {
|
ctsState.value = if (hasCtsPassed) {
|
||||||
SafetyNetState.PASS
|
SafetyNetState.PASS
|
||||||
} else {
|
} else {
|
||||||
@@ -164,14 +164,18 @@ class HomeViewModel(
|
|||||||
ctsState.value = SafetyNetState.IDLE
|
ctsState.value = SafetyNetState.IDLE
|
||||||
basicIntegrityState.value = SafetyNetState.IDLE
|
basicIntegrityState.value = SafetyNetState.IDLE
|
||||||
safetyNetTitle.value = when (response) {
|
safetyNetTitle.value = when (response) {
|
||||||
SafetyNetHelper.RESPONSE_ERR -> R.string.safetyNet_res_invalid
|
SafetyNetHelper.RESPONSE_ERR -> R.string.safetyNet_res_invalid.res()
|
||||||
else -> R.string.safetyNet_api_error
|
else -> R.string.safetyNet_api_error.res()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refresh() {
|
@JvmOverloads
|
||||||
hasRoot.value = Shell.rootAccess()
|
fun refresh(invalidate: Boolean = true) {
|
||||||
|
if (invalidate)
|
||||||
|
Info.envRef.invalidate()
|
||||||
|
|
||||||
|
isActive.value = Info.env.isActive
|
||||||
|
|
||||||
val fetchUpdate = if (isConnected.value)
|
val fetchUpdate = if (isConnected.value)
|
||||||
magiskRepo.fetchUpdate().ignoreElement()
|
magiskRepo.fetchUpdate().ignoreElement()
|
||||||
@@ -179,7 +183,8 @@ class HomeViewModel(
|
|||||||
Completable.complete()
|
Completable.complete()
|
||||||
|
|
||||||
Completable.fromAction {
|
Completable.fromAction {
|
||||||
Info.loadMagiskInfo()
|
// Ensure value is ready
|
||||||
|
Info.env
|
||||||
}.andThen(fetchUpdate)
|
}.andThen(fetchUpdate)
|
||||||
.applyViewModel(this)
|
.applyViewModel(this)
|
||||||
.doOnSubscribeUi {
|
.doOnSubscribeUi {
|
||||||
@@ -187,7 +192,7 @@ class HomeViewModel(
|
|||||||
_managerState.value = MagiskState.LOADING
|
_managerState.value = MagiskState.LOADING
|
||||||
ctsState.value = SafetyNetState.IDLE
|
ctsState.value = SafetyNetState.IDLE
|
||||||
basicIntegrityState.value = SafetyNetState.IDLE
|
basicIntegrityState.value = SafetyNetState.IDLE
|
||||||
safetyNetTitle.value = R.string.safetyNet_check_text
|
safetyNetTitle.value = R.string.safetyNet_check_text.res()
|
||||||
}.subscribeK {
|
}.subscribeK {
|
||||||
updateSelf()
|
updateSelf()
|
||||||
ensureEnv()
|
ensureEnv()
|
||||||
@@ -197,33 +202,40 @@ class HomeViewModel(
|
|||||||
|
|
||||||
private fun refreshVersions() {
|
private fun refreshVersions() {
|
||||||
magiskCurrentVersion.value = if (magiskState.value != MagiskState.NOT_INSTALLED) {
|
magiskCurrentVersion.value = if (magiskState.value != MagiskState.NOT_INSTALLED) {
|
||||||
version.format(Info.magiskVersionString, Info.magiskVersionCode)
|
VERSION_FMT.format(Info.env.magiskVersionString, Info.env.magiskVersionCode)
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
managerCurrentVersion.value = version
|
managerCurrentVersion.value = if (isRunningAsStub) MGR_VER_FMT
|
||||||
.format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
|
.format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, Info.stub!!.version)
|
||||||
|
else
|
||||||
|
VERSION_FMT.format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSelf() {
|
private fun updateSelf() {
|
||||||
magiskState.value = when (Info.magiskVersionCode) {
|
magiskState.value = when (Info.env.magiskVersionCode) {
|
||||||
in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED
|
in Int.MIN_VALUE .. 0 -> MagiskState.NOT_INSTALLED
|
||||||
!in Info.remote.magisk.versionCode..Int.MAX_VALUE -> MagiskState.OBSOLETE
|
in 1 until Info.remote.magisk.versionCode -> MagiskState.OBSOLETE
|
||||||
else -> MagiskState.UP_TO_DATE
|
else -> MagiskState.UP_TO_DATE
|
||||||
}
|
}
|
||||||
|
|
||||||
magiskLatestVersion.value = version
|
magiskLatestVersion.value =
|
||||||
.format(Info.remote.magisk.version, Info.remote.magisk.versionCode)
|
VERSION_FMT.format(Info.remote.magisk.version, Info.remote.magisk.versionCode)
|
||||||
|
|
||||||
_managerState.value = when (Info.remote.app.versionCode) {
|
_managerState.value = when (Info.remote.app.versionCode) {
|
||||||
in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED //wrong update channel
|
in Int.MIN_VALUE .. 0 -> MagiskState.NOT_INSTALLED //wrong update channel
|
||||||
in (BuildConfig.VERSION_CODE + 1)..Int.MAX_VALUE -> MagiskState.OBSOLETE
|
in (BuildConfig.VERSION_CODE + 1) .. Int.MAX_VALUE -> MagiskState.OBSOLETE
|
||||||
else -> MagiskState.UP_TO_DATE
|
else -> {
|
||||||
|
if (Info.stub?.version ?: Int.MAX_VALUE < Info.remote.stub.versionCode)
|
||||||
|
MagiskState.OBSOLETE
|
||||||
|
else
|
||||||
|
MagiskState.UP_TO_DATE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
managerLatestVersion.value = version
|
managerLatestVersion.value = MGR_VER_FMT
|
||||||
.format(Info.remote.app.version, Info.remote.app.versionCode)
|
.format(Info.remote.app.version, Info.remote.app.versionCode, Info.remote.stub.versionCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ensureEnv() {
|
private fun ensureEnv() {
|
||||||
@@ -240,7 +252,8 @@ class HomeViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val version = "%s (%d)"
|
private const val VERSION_FMT = "%s (%d)"
|
||||||
|
private const val MGR_VER_FMT = "%s (%d) (%d)"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -104,7 +104,6 @@ class LogViewModel(
|
|||||||
.add()
|
.add()
|
||||||
|
|
||||||
private fun clearMagiskLogs(callback: () -> Unit) = logRepo.clearMagiskLogs()
|
private fun clearMagiskLogs(callback: () -> Unit) = logRepo.clearMagiskLogs()
|
||||||
.ignoreElement()
|
|
||||||
.doOnComplete(callback)
|
.doOnComplete(callback)
|
||||||
.subscribeK { SnackbarEvent(R.string.logs_cleared).publish() }
|
.subscribeK { SnackbarEvent(R.string.logs_cleared).publish() }
|
||||||
.add()
|
.add()
|
||||||
@@ -115,8 +114,7 @@ class LogViewModel(
|
|||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
private fun fetchMagiskLog() = logRepo.fetchMagiskLogs()
|
private fun fetchMagiskLog() = logRepo.fetchMagiskLogs()
|
||||||
.flattenAsFlowable { it }
|
|
||||||
.map { ConsoleRvItem(it) }
|
.map { ConsoleRvItem(it) }
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -8,12 +8,12 @@ import android.view.MenuInflater
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.topjohnwu.magisk.ClassMap
|
|
||||||
import com.topjohnwu.magisk.Const
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.base.BaseFragment
|
import com.topjohnwu.magisk.base.BaseFragment
|
||||||
import com.topjohnwu.magisk.databinding.FragmentModulesBinding
|
import com.topjohnwu.magisk.databinding.FragmentModulesBinding
|
||||||
import com.topjohnwu.magisk.extensions.reboot
|
import com.topjohnwu.magisk.extensions.reboot
|
||||||
|
import com.topjohnwu.magisk.intent
|
||||||
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
|
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
||||||
@@ -28,7 +28,7 @@ class ModulesFragment : BaseFragment<ModuleViewModel, FragmentModulesBinding>()
|
|||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
|
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
|
||||||
// Get the URI of the selected file
|
// Get the URI of the selected file
|
||||||
val intent = Intent(activity, ClassMap[FlashActivity::class.java])
|
val intent = activity.intent<FlashActivity>()
|
||||||
intent.setData(data.data).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP)
|
intent.setData(data.data).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
@@ -13,22 +13,17 @@ import androidx.preference.ListPreference
|
|||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceCategory
|
import androidx.preference.PreferenceCategory
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.*
|
||||||
import com.topjohnwu.magisk.Config
|
|
||||||
import com.topjohnwu.magisk.Const
|
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.base.BasePreferenceFragment
|
import com.topjohnwu.magisk.base.BasePreferenceFragment
|
||||||
import com.topjohnwu.magisk.data.database.RepoDao
|
import com.topjohnwu.magisk.data.database.RepoDao
|
||||||
import com.topjohnwu.magisk.databinding.CustomDownloadDialogBinding
|
import com.topjohnwu.magisk.databinding.CustomDownloadDialogBinding
|
||||||
|
import com.topjohnwu.magisk.databinding.DialogCustomNameBinding
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.extensions.toLangTag
|
|
||||||
import com.topjohnwu.magisk.model.download.DownloadService
|
import com.topjohnwu.magisk.model.download.DownloadService
|
||||||
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
||||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||||
import com.topjohnwu.magisk.model.observer.Observer
|
import com.topjohnwu.magisk.model.observer.Observer
|
||||||
import com.topjohnwu.magisk.net.Networking
|
|
||||||
import com.topjohnwu.magisk.utils.*
|
import com.topjohnwu.magisk.utils.*
|
||||||
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
@@ -56,6 +51,7 @@ class SettingsFragment : BasePreferenceFragment() {
|
|||||||
preferenceManager.setStorageDeviceProtected()
|
preferenceManager.setStorageDeviceProtected()
|
||||||
setPreferencesFromResource(R.xml.app_settings, rootKey)
|
setPreferencesFromResource(R.xml.app_settings, rootKey)
|
||||||
|
|
||||||
|
// Get preferences
|
||||||
updateChannel = findPreference(Config.Key.UPDATE_CHANNEL)!!
|
updateChannel = findPreference(Config.Key.UPDATE_CHANNEL)!!
|
||||||
rootConfig = findPreference(Config.Key.ROOT_ACCESS)!!
|
rootConfig = findPreference(Config.Key.ROOT_ACCESS)!!
|
||||||
autoRes = findPreference(Config.Key.SU_AUTO_RESPONSE)!!
|
autoRes = findPreference(Config.Key.SU_AUTO_RESPONSE)!!
|
||||||
@@ -64,28 +60,86 @@ class SettingsFragment : BasePreferenceFragment() {
|
|||||||
multiuserConfig = findPreference(Config.Key.SU_MULTIUSER_MODE)!!
|
multiuserConfig = findPreference(Config.Key.SU_MULTIUSER_MODE)!!
|
||||||
nsConfig = findPreference(Config.Key.SU_MNT_NS)!!
|
nsConfig = findPreference(Config.Key.SU_MNT_NS)!!
|
||||||
val reauth = findPreference<SwitchPreferenceCompat>(Config.Key.SU_REAUTH)!!
|
val reauth = findPreference<SwitchPreferenceCompat>(Config.Key.SU_REAUTH)!!
|
||||||
val fingerprint = findPreference<SwitchPreferenceCompat>(Config.Key.SU_FINGERPRINT)!!
|
val biometric = findPreference<SwitchPreferenceCompat>(Config.Key.SU_BIOMETRIC)!!
|
||||||
val generalCatagory = findPreference<PreferenceCategory>("general")!!
|
val generalCategory = findPreference<PreferenceCategory>("general")!!
|
||||||
val magiskCategory = findPreference<PreferenceCategory>("magisk")!!
|
val magiskCategory = findPreference<PreferenceCategory>("magisk")!!
|
||||||
val suCategory = findPreference<PreferenceCategory>("superuser")!!
|
val suCategory = findPreference<PreferenceCategory>("superuser")!!
|
||||||
val hideManager = findPreference<Preference>("hide")!!
|
val hideManager = findPreference<Preference>("hide")!!
|
||||||
hideManager.setOnPreferenceClickListener {
|
val restoreManager = findPreference<Preference>("restore")!!
|
||||||
PatchAPK.hideManager(requireContext())
|
|
||||||
true
|
// Remove/Disable entries
|
||||||
|
|
||||||
|
// Only show canary channels if user is already on canary channel
|
||||||
|
// or the user have already chosen canary channel
|
||||||
|
if (!Utils.isCanary && Config.updateChannel < Config.Value.CANARY_CHANNEL) {
|
||||||
|
// Remove the last 2 entries
|
||||||
|
val entries = updateChannel.entries
|
||||||
|
updateChannel.entries = entries.copyOf(entries.size - 2)
|
||||||
}
|
}
|
||||||
val restoreManager = findPreference<Preference>("restore")
|
|
||||||
restoreManager?.setOnPreferenceClickListener {
|
// Remove dangerous settings in secondary user
|
||||||
DownloadService(requireContext()) {
|
if (Const.USER_ID > 0) {
|
||||||
subject = DownloadSubject.Manager(Configuration.APK.Restore)
|
suCategory.removePreference(multiuserConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove re-authentication option on Android O, it will not work
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
suCategory.removePreference(reauth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable biometric option if not possible
|
||||||
|
if (!BiometricHelper.isSupported) {
|
||||||
|
biometric.isEnabled = false
|
||||||
|
biometric.isChecked = false
|
||||||
|
biometric.setSummary(R.string.no_biometric)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Const.USER_ID == 0 && Info.isConnected.value && Info.env.isActive) {
|
||||||
|
if (activity.packageName == BuildConfig.APPLICATION_ID) {
|
||||||
|
generalCategory.removePreference(restoreManager)
|
||||||
|
hideManager.setOnPreferenceClickListener {
|
||||||
|
showManagerNameDialog {
|
||||||
|
PatchAPK.hideManager(requireContext(), it)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
generalCategory.removePreference(hideManager)
|
||||||
|
restoreManager.setOnPreferenceClickListener {
|
||||||
|
DownloadService(requireContext()) {
|
||||||
|
subject = DownloadSubject.Manager(Configuration.APK.Restore)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
true
|
} else {
|
||||||
|
// Remove if not primary user, no connection, or no root
|
||||||
|
generalCategory.removePreference(restoreManager)
|
||||||
|
generalCategory.removePreference(hideManager)
|
||||||
}
|
}
|
||||||
findPreference<Preference>("clear")?.setOnPreferenceClickListener {
|
|
||||||
Completable.fromAction { repoDB.clear() }.subscribeK {
|
if (!Utils.showSuperUser()) {
|
||||||
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT)
|
preferenceScreen.removePreference(suCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Info.env.isActive) {
|
||||||
|
preferenceScreen.removePreference(magiskCategory)
|
||||||
|
generalCategory.removePreference(hideManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
findPreference<Preference>("clear")?.also {
|
||||||
|
if (Info.env.isActive) {
|
||||||
|
it.setOnPreferenceClickListener {
|
||||||
|
Completable.fromAction { repoDB.clear() }.subscribeK {
|
||||||
|
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
generalCategory.removePreference(it)
|
||||||
}
|
}
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
findPreference<Preference>("hosts")?.setOnPreferenceClickListener {
|
findPreference<Preference>("hosts")?.setOnPreferenceClickListener {
|
||||||
Shell.su("add_hosts_module").submit {
|
Shell.su("add_hosts_module").submit {
|
||||||
Utils.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
|
Utils.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
|
||||||
@@ -95,20 +149,21 @@ class SettingsFragment : BasePreferenceFragment() {
|
|||||||
|
|
||||||
findPreference<Preference>(Config.Key.DOWNLOAD_PATH)?.apply {
|
findPreference<Preference>(Config.Key.DOWNLOAD_PATH)?.apply {
|
||||||
summary = Config.downloadPath
|
summary = Config.downloadPath
|
||||||
}?.setOnPreferenceClickListener { preference ->
|
setOnPreferenceClickListener { pref ->
|
||||||
activity.withExternalRW {
|
activity.withExternalRW {
|
||||||
onSuccess {
|
onSuccess {
|
||||||
showDownloadDialog {
|
showDownloadDialog {
|
||||||
Config.downloadPath = it
|
Config.downloadPath = it
|
||||||
preference.summary = it
|
pref.summary = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateChannel.setOnPreferenceChangeListener { _, value ->
|
updateChannel.setOnPreferenceChangeListener { _, value ->
|
||||||
val channel = Integer.parseInt(value as String)
|
val channel = value.toString().toInt()
|
||||||
val previous = Config.updateChannel
|
val previous = Config.updateChannel
|
||||||
|
|
||||||
if (channel == Config.Value.CUSTOM_CHANNEL) {
|
if (channel == Config.Value.CUSTOM_CHANNEL) {
|
||||||
@@ -123,58 +178,7 @@ class SettingsFragment : BasePreferenceFragment() {
|
|||||||
|
|
||||||
setLocalePreference(findPreference(Config.Key.LOCALE)!!)
|
setLocalePreference(findPreference(Config.Key.LOCALE)!!)
|
||||||
|
|
||||||
/* We only show canary channels if user is already on canary channel
|
|
||||||
* or the user have already chosen canary channel */
|
|
||||||
if (!Utils.isCanary && Config.updateChannel < Config.Value.CANARY_CHANNEL) {
|
|
||||||
// Remove the last 2 entries
|
|
||||||
val entries = updateChannel.entries
|
|
||||||
updateChannel.entries = entries.copyOf(entries.size - 2)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
setSummary()
|
setSummary()
|
||||||
|
|
||||||
// Disable dangerous settings in secondary user
|
|
||||||
if (Const.USER_ID > 0) {
|
|
||||||
suCategory.removePreference(multiuserConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable re-authentication option on Android O, it will not work
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
reauth.isEnabled = false
|
|
||||||
reauth.isChecked = false
|
|
||||||
reauth.setSummary(R.string.android_o_not_support)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable fingerprint option if not possible
|
|
||||||
if (!FingerprintHelper.canUseFingerprint()) {
|
|
||||||
fingerprint.isEnabled = false
|
|
||||||
fingerprint.isChecked = false
|
|
||||||
fingerprint.setSummary(R.string.disable_fingerprint)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Shell.rootAccess() && Const.USER_ID == 0) {
|
|
||||||
if (activity.packageName == BuildConfig.APPLICATION_ID) {
|
|
||||||
generalCatagory.removePreference(restoreManager)
|
|
||||||
} else {
|
|
||||||
if (!Networking.checkNetworkStatus(requireContext())) {
|
|
||||||
generalCatagory.removePreference(restoreManager)
|
|
||||||
}
|
|
||||||
generalCatagory.removePreference(hideManager)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
generalCatagory.removePreference(restoreManager)
|
|
||||||
generalCatagory.removePreference(hideManager)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Utils.showSuperUser()) {
|
|
||||||
preferenceScreen.removePreference(suCategory)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Shell.rootAccess()) {
|
|
||||||
preferenceScreen.removePreference(magiskCategory)
|
|
||||||
generalCatagory.removePreference(hideManager)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
|
override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
|
||||||
@@ -201,7 +205,7 @@ class SettingsFragment : BasePreferenceFragment() {
|
|||||||
Shell.su("magiskhide --disable").submit()
|
Shell.su("magiskhide --disable").submit()
|
||||||
}
|
}
|
||||||
Config.Key.LOCALE -> {
|
Config.Key.LOCALE -> {
|
||||||
LocaleManager.setLocale(activity.application)
|
refreshLocale()
|
||||||
activity.recreate()
|
activity.recreate()
|
||||||
}
|
}
|
||||||
Config.Key.CHECK_UPDATES -> Utils.scheduleUpdateCheck(activity)
|
Config.Key.CHECK_UPDATES -> Utils.scheduleUpdateCheck(activity)
|
||||||
@@ -211,13 +215,13 @@ class SettingsFragment : BasePreferenceFragment() {
|
|||||||
|
|
||||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||||
when (preference.key) {
|
when (preference.key) {
|
||||||
Config.Key.SU_FINGERPRINT -> {
|
Config.Key.SU_BIOMETRIC -> {
|
||||||
val checked = (preference as SwitchPreferenceCompat).isChecked
|
val checked = (preference as SwitchPreferenceCompat).isChecked
|
||||||
preference.isChecked = !checked
|
preference.isChecked = !checked
|
||||||
FingerprintAuthDialog(requireActivity()) {
|
BiometricHelper.authenticate(requireActivity()) {
|
||||||
preference.isChecked = checked
|
preference.isChecked = checked
|
||||||
Config.suFingerprint = checked
|
Config.suBiometric = checked
|
||||||
}.show()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -225,27 +229,12 @@ class SettingsFragment : BasePreferenceFragment() {
|
|||||||
|
|
||||||
private fun setLocalePreference(lp: ListPreference) {
|
private fun setLocalePreference(lp: ListPreference) {
|
||||||
lp.isEnabled = false
|
lp.isEnabled = false
|
||||||
availableLocales.map {
|
availableLocales.subscribeK { (names, values) ->
|
||||||
val names = mutableListOf<String>()
|
lp.isEnabled = true
|
||||||
val values = mutableListOf<String>()
|
lp.entries = names
|
||||||
|
lp.entryValues = values
|
||||||
names.add(
|
lp.summary = currentLocale.getDisplayName(currentLocale)
|
||||||
LocaleManager.getString(defaultLocale, R.string.system_default)
|
}
|
||||||
)
|
|
||||||
values.add("")
|
|
||||||
|
|
||||||
it.forEach { locale ->
|
|
||||||
names.add(locale.getDisplayName(locale))
|
|
||||||
values.add(locale.toLangTag())
|
|
||||||
}
|
|
||||||
|
|
||||||
Pair(names.toTypedArray(), values.toTypedArray())
|
|
||||||
}.subscribeK { (names, values) ->
|
|
||||||
lp.isEnabled = true
|
|
||||||
lp.entries = names
|
|
||||||
lp.entryValues = values
|
|
||||||
lp.summary = currentLocale.getDisplayName(currentLocale)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setSummary(key: String) {
|
private fun setSummary(key: String) {
|
||||||
@@ -297,8 +286,8 @@ class SettingsFragment : BasePreferenceFragment() {
|
|||||||
AlertDialog.Builder(requireActivity())
|
AlertDialog.Builder(requireActivity())
|
||||||
.setTitle(R.string.settings_update_custom)
|
.setTitle(R.string.settings_update_custom)
|
||||||
.setView(v)
|
.setView(v)
|
||||||
.setPositiveButton(R.string.ok) { _, _ -> onSuccess(url.text.toString()) }
|
.setPositiveButton(android.R.string.ok) { _, _ -> onSuccess(url.text.toString()) }
|
||||||
.setNegativeButton(R.string.close) { _, _ -> onCancel() }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> onCancel() }
|
||||||
.setOnCancelListener { onCancel() }
|
.setOnCancelListener { onCancel() }
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
@@ -322,11 +311,35 @@ class SettingsFragment : BasePreferenceFragment() {
|
|||||||
AlertDialog.Builder(requireActivity())
|
AlertDialog.Builder(requireActivity())
|
||||||
.setTitle(R.string.settings_download_path_title)
|
.setTitle(R.string.settings_download_path_title)
|
||||||
.setView(binding.root)
|
.setView(binding.root)
|
||||||
.setPositiveButton(R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
Utils.ensureDownloadPath(data.text.value)?.let { onSuccess(data.text.value) }
|
Utils.ensureDownloadPath(data.text.value)?.let { onSuccess(data.text.value) }
|
||||||
?: Utils.toast(R.string.settings_download_path_error, Toast.LENGTH_SHORT)
|
?: Utils.toast(R.string.settings_download_path_error, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.close, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private inline fun showManagerNameDialog(
|
||||||
|
crossinline onSuccess: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val data = ManagerNameData()
|
||||||
|
val view = DialogCustomNameBinding
|
||||||
|
.inflate(LayoutInflater.from(requireContext()))
|
||||||
|
.also { it.data = data }
|
||||||
|
|
||||||
|
AlertDialog.Builder(requireActivity())
|
||||||
|
.setTitle(R.string.settings_app_name)
|
||||||
|
.setView(view.root)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
if (view.dialogNameInput.error.isNullOrBlank()) {
|
||||||
|
onSuccess(data.name.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ManagerNameData {
|
||||||
|
val name = KObservableField(resources.getString(R.string.re_app_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -15,11 +15,10 @@ import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
|||||||
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.model.events.SnackbarEvent
|
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||||
|
import com.topjohnwu.magisk.utils.BiometricHelper
|
||||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
import com.topjohnwu.magisk.utils.FingerprintHelper
|
|
||||||
import com.topjohnwu.magisk.utils.RxBus
|
import com.topjohnwu.magisk.utils.RxBus
|
||||||
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
|
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
|
||||||
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
|
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||||
@@ -42,6 +41,11 @@ class SuperuserViewModel(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
rxBus.register<PolicyEnableEvent>()
|
rxBus.register<PolicyEnableEvent>()
|
||||||
|
.filter {
|
||||||
|
val isIgnored = it.item == ignoreNext
|
||||||
|
if (isIgnored) ignoreNext = null
|
||||||
|
!isIgnored
|
||||||
|
}
|
||||||
.subscribeK { togglePolicy(it.item, it.enable) }
|
.subscribeK { togglePolicy(it.item, it.enable) }
|
||||||
.add()
|
.add()
|
||||||
rxBus.register<PolicyUpdateEvent>()
|
rxBus.register<PolicyUpdateEvent>()
|
||||||
@@ -78,14 +82,14 @@ class SuperuserViewModel(
|
|||||||
.add()
|
.add()
|
||||||
|
|
||||||
withView {
|
withView {
|
||||||
if (FingerprintHelper.useFingerprint()) {
|
if (BiometricHelper.isEnabled) {
|
||||||
FingerprintAuthDialog(this) { updateState() }.show()
|
BiometricHelper.authenticate(this) { updateState() }
|
||||||
} else {
|
} else {
|
||||||
CustomAlertDialog(this)
|
CustomAlertDialog(this)
|
||||||
.setTitle(R.string.su_revoke_title)
|
.setTitle(R.string.su_revoke_title)
|
||||||
.setMessage(getString(R.string.su_revoke_msg, item.item.appName))
|
.setMessage(getString(R.string.su_revoke_msg, item.item.appName))
|
||||||
.setPositiveButton(R.string.yes) { _, _ -> updateState() }
|
.setPositiveButton(android.R.string.yes) { _, _ -> updateState() }
|
||||||
.setNegativeButton(R.string.no_thanks, null)
|
.setNegativeButton(android.R.string.no, null)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
@@ -126,12 +130,12 @@ class SuperuserViewModel(
|
|||||||
.add()
|
.add()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FingerprintHelper.useFingerprint()) {
|
if (BiometricHelper.isEnabled) {
|
||||||
withView {
|
withView {
|
||||||
FingerprintAuthDialog(this, { updateState() }, {
|
BiometricHelper.authenticate(this, onError = {
|
||||||
ignoreNext = item
|
ignoreNext = item
|
||||||
item.isEnabled.toggle()
|
item.isEnabled.toggle()
|
||||||
}).show()
|
}) { updateState() }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updateState()
|
updateState()
|
||||||
@@ -144,4 +148,4 @@ class SuperuserViewModel(
|
|||||||
private fun deletePolicy(policy: MagiskPolicy) =
|
private fun deletePolicy(policy: MagiskPolicy) =
|
||||||
policyDB.delete(policy.uid).andThen(Single.just(policy))
|
policyDB.delete(policy.uid).andThen(Single.just(policy))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,27 +1,28 @@
|
|||||||
package com.topjohnwu.magisk.ui.surequest
|
package com.topjohnwu.magisk.ui.surequest
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.base.BaseActivity
|
import com.topjohnwu.magisk.base.BaseActivity
|
||||||
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
|
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
|
||||||
import com.topjohnwu.magisk.model.events.DieEvent
|
import com.topjohnwu.magisk.model.events.DieEvent
|
||||||
|
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
import com.topjohnwu.magisk.utils.SuHandler
|
||||||
import com.topjohnwu.magisk.utils.SuLogger
|
import com.topjohnwu.magisk.utils.SuHandler.REQUEST
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestBinding>() {
|
open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestBinding>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.activity_request
|
override val layoutRes: Int = R.layout.activity_request
|
||||||
|
override val themeRes: Int = R.style.MagiskTheme_SU
|
||||||
override val viewModel: SuRequestViewModel by viewModel()
|
override val viewModel: SuRequestViewModel by viewModel()
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
viewModel.handler?.handleAction(MagiskPolicy.DENY, -1)
|
viewModel.denyPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -29,26 +30,34 @@ open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestB
|
|||||||
lockOrientation()
|
lockOrientation()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val intent = intent
|
fun showRequest() {
|
||||||
val action = intent.action
|
|
||||||
|
|
||||||
if (TextUtils.equals(action, GeneralReceiver.REQUEST)) {
|
|
||||||
if (!viewModel.handleRequest(intent))
|
if (!viewModel.handleRequest(intent))
|
||||||
finish()
|
finish()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextUtils.equals(action, GeneralReceiver.LOG))
|
fun runHandler(action: String?) {
|
||||||
SuLogger.handleLogs(intent)
|
SuHandler(this, action, intent.extras)
|
||||||
else if (TextUtils.equals(action, GeneralReceiver.NOTIFY))
|
finish()
|
||||||
SuLogger.handleNotify(intent)
|
}
|
||||||
|
|
||||||
finish()
|
if (intent.action == Intent.ACTION_VIEW) {
|
||||||
|
val action = intent.getStringExtra("action")
|
||||||
|
if (action == REQUEST) {
|
||||||
|
showRequest()
|
||||||
|
} else {
|
||||||
|
runHandler(action)
|
||||||
|
}
|
||||||
|
} else if (intent.action == REQUEST) {
|
||||||
|
showRequest()
|
||||||
|
} else {
|
||||||
|
runHandler(intent.action)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEventDispatched(event: ViewEvent) {
|
override fun onEventDispatched(event: ViewEvent) {
|
||||||
super.onEventDispatched(event)
|
super.onEventDispatched(event)
|
||||||
when (event) {
|
when (event) {
|
||||||
|
is ViewActionEvent -> event.action(this)
|
||||||
is DieEvent -> finish()
|
is DieEvent -> finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,28 +1,25 @@
|
|||||||
package com.topjohnwu.magisk.ui.surequest
|
package com.topjohnwu.magisk.ui.surequest
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.hardware.fingerprint.FingerprintManager
|
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
import android.text.TextUtils
|
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.Const
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||||
import com.topjohnwu.magisk.data.database.PolicyDao
|
import com.topjohnwu.magisk.data.database.PolicyDao
|
||||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
|
||||||
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.recycler.SpinnerRvItem
|
import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem
|
||||||
import com.topjohnwu.magisk.model.entity.toPolicy
|
import com.topjohnwu.magisk.model.entity.toPolicy
|
||||||
import com.topjohnwu.magisk.model.events.DieEvent
|
import com.topjohnwu.magisk.model.events.DieEvent
|
||||||
|
import com.topjohnwu.magisk.utils.BiometricHelper
|
||||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||||
import com.topjohnwu.magisk.utils.FingerprintHelper
|
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import com.topjohnwu.magisk.utils.SuConnector
|
import com.topjohnwu.magisk.utils.SuConnector
|
||||||
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
|
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
|
||||||
@@ -45,7 +42,6 @@ class SuRequestViewModel(
|
|||||||
val denyText = KObservableField(resources.getString(R.string.deny))
|
val denyText = KObservableField(resources.getString(R.string.deny))
|
||||||
val warningText = KObservableField<CharSequence>(resources.getString(R.string.su_warning))
|
val warningText = KObservableField<CharSequence>(resources.getString(R.string.su_warning))
|
||||||
|
|
||||||
val canUseFingerprint = KObservableField(FingerprintHelper.useFingerprint())
|
|
||||||
val selectedItemPosition = KObservableField(0)
|
val selectedItemPosition = KObservableField(0)
|
||||||
|
|
||||||
private val items = DiffObservableList(ComparableRvItem.callback)
|
private val items = DiffObservableList(ComparableRvItem.callback)
|
||||||
@@ -58,48 +54,33 @@ class SuRequestViewModel(
|
|||||||
setItems(items)
|
setItems(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val cancelTasks = mutableListOf<() -> Unit>()
|
||||||
|
|
||||||
var handler: ActionHandler? = null
|
private lateinit var timer: CountDownTimer
|
||||||
private var timer: CountDownTimer? = null
|
private lateinit var policy: MagiskPolicy
|
||||||
private var policy: MagiskPolicy? = null
|
private lateinit var connector: SuConnector
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
updatePolicy(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
resources.getStringArray(R.array.allow_timeout)
|
|
||||||
.map { SpinnerRvItem(it) }
|
|
||||||
.let { items.update(it) }
|
|
||||||
|
|
||||||
selectedItemPosition.addOnPropertyChangedCallback {
|
|
||||||
Timber.e("Changed position to $it")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updatePolicy(policy: MagiskPolicy?) {
|
|
||||||
policy ?: return
|
|
||||||
|
|
||||||
icon.value = policy.applicationInfo.loadIcon(packageManager)
|
|
||||||
title.value = policy.appName
|
|
||||||
packageName.value = policy.packageName
|
|
||||||
|
|
||||||
selectedItemPosition.value = timeoutPrefs.getInt(policy.packageName, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cancelTimer() {
|
private fun cancelTimer() {
|
||||||
timer?.cancel()
|
timer.cancel()
|
||||||
denyText.value = resources.getString(R.string.deny)
|
denyText.value = resources.getString(R.string.deny)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun grantPressed() {
|
fun grantPressed() {
|
||||||
handler?.handleAction(MagiskPolicy.ALLOW)
|
cancelTimer()
|
||||||
timer?.cancel()
|
if (BiometricHelper.isEnabled) {
|
||||||
|
withView {
|
||||||
|
BiometricHelper.authenticate(this) {
|
||||||
|
handleAction(MagiskPolicy.ALLOW)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleAction(MagiskPolicy.ALLOW)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun denyPressed() {
|
fun denyPressed() {
|
||||||
handler?.handleAction(MagiskPolicy.DENY)
|
handleAction(MagiskPolicy.DENY)
|
||||||
timer?.cancel()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun spinnerTouched(): Boolean {
|
fun spinnerTouched(): Boolean {
|
||||||
@@ -110,75 +91,27 @@ class SuRequestViewModel(
|
|||||||
fun handleRequest(intent: Intent): Boolean {
|
fun handleRequest(intent: Intent): Boolean {
|
||||||
val socketName = intent.getStringExtra("socket") ?: return false
|
val socketName = intent.getStringExtra("socket") ?: return false
|
||||||
|
|
||||||
val connector: SuConnector
|
|
||||||
try {
|
try {
|
||||||
connector = object : SuConnector(socketName) {
|
connector = Connector(socketName)
|
||||||
@Throws(IOException::class)
|
val map = connector.readRequest()
|
||||||
override fun onResponse() {
|
val uid = map["uid"]?.toIntOrNull() ?: return false
|
||||||
out.writeInt(policy?.policy ?: return)
|
policy = uid.toPolicy(packageManager)
|
||||||
}
|
} catch (e: Exception) {
|
||||||
}
|
Timber.e(e)
|
||||||
val bundle = connector.readSocketInput()
|
|
||||||
val uid = bundle.getString("uid")?.toIntOrNull() ?: return false
|
|
||||||
policyDB.deleteOutdated().blockingGet() // wrong!
|
|
||||||
policy = runCatching { policyDB.fetch(uid).blockingGet() }
|
|
||||||
.getOrDefault(uid.toPolicy(packageManager))
|
|
||||||
} catch (e: IOException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
return false
|
return false
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
handler = object : ActionHandler() {
|
|
||||||
override fun handleAction() {
|
|
||||||
connector.response()
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ApplySharedPref")
|
|
||||||
override fun handleAction(action: Int) {
|
|
||||||
val pos = selectedItemPosition.value
|
|
||||||
timeoutPrefs.edit().putInt(policy?.packageName, pos).commit()
|
|
||||||
handleAction(action, Config.Value.TIMEOUT_LIST[pos])
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handleAction(action: Int, time: Int) {
|
|
||||||
val until = if (time >= 0) {
|
|
||||||
if (time == 0) {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
MILLISECONDS.toSeconds(now) + MINUTES.toSeconds(time.toLong())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
policy?.until ?: 0
|
|
||||||
}
|
|
||||||
policy = policy?.copy(policy = action, until = until)?.apply {
|
|
||||||
policyDB.update(this).blockingGet()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleAction()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Never allow com.topjohnwu.magisk (could be malware)
|
// Never allow com.topjohnwu.magisk (could be malware)
|
||||||
if (TextUtils.equals(policy?.packageName, BuildConfig.APPLICATION_ID))
|
if (policy.packageName == BuildConfig.APPLICATION_ID)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
// If not interactive, response directly
|
|
||||||
if (policy?.policy != MagiskPolicy.INTERACTIVE) {
|
|
||||||
handler?.handleAction()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
when (Config.suAutoReponse) {
|
when (Config.suAutoReponse) {
|
||||||
Config.Value.SU_AUTO_DENY -> {
|
Config.Value.SU_AUTO_DENY -> {
|
||||||
handler?.handleAction(MagiskPolicy.DENY, 0)
|
handleAction(MagiskPolicy.DENY, 0)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
Config.Value.SU_AUTO_ALLOW -> {
|
Config.Value.SU_AUTO_ALLOW -> {
|
||||||
handler?.handleAction(MagiskPolicy.ALLOW, 0)
|
handleAction(MagiskPolicy.ALLOW, 0)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,78 +120,65 @@ class SuRequestViewModel(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
private fun showUI() {
|
private fun showUI() {
|
||||||
|
resources.getStringArray(R.array.allow_timeout)
|
||||||
|
.map { SpinnerRvItem(it) }
|
||||||
|
.let { items.update(it) }
|
||||||
|
|
||||||
|
icon.value = policy.applicationInfo.loadIcon(packageManager)
|
||||||
|
title.value = policy.appName
|
||||||
|
packageName.value = policy.packageName
|
||||||
|
selectedItemPosition.value = timeoutPrefs.getInt(policy.packageName, 0)
|
||||||
|
|
||||||
val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
|
val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
|
||||||
timer = object : CountDownTimer(millis, 1000) {
|
timer = object : CountDownTimer(millis, 1000) {
|
||||||
override fun onTick(remains: Long) {
|
override fun onTick(remains: Long) {
|
||||||
denyText.value = "%s (%d)"
|
denyText.value = "${resources.getString(R.string.deny)} (${remains / 1000})"
|
||||||
.format(resources.getString(R.string.deny), remains / 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFinish() {
|
override fun onFinish() {
|
||||||
denyText.value = resources.getString(R.string.deny)
|
denyText.value = resources.getString(R.string.deny)
|
||||||
handler?.handleAction(MagiskPolicy.DENY)
|
handleAction(MagiskPolicy.DENY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timer?.start()
|
timer.start()
|
||||||
handler?.addCancel(Runnable { cancelTimer() })
|
cancelTasks.add { cancelTimer() }
|
||||||
|
|
||||||
val useFP = canUseFingerprint.value
|
|
||||||
|
|
||||||
if (useFP)
|
|
||||||
try {
|
|
||||||
val helper = SuFingerprint()
|
|
||||||
helper.authenticate()
|
|
||||||
handler?.addCancel(Runnable { helper.cancel() })
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class SuFingerprint @Throws(Exception::class)
|
private fun handleAction() {
|
||||||
internal constructor() : FingerprintHelper() {
|
connector.response()
|
||||||
|
cancelTasks.forEach { it() }
|
||||||
|
DieEvent().publish()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
private fun handleAction(action: Int) {
|
||||||
warningText.value = errString
|
val pos = selectedItemPosition.value
|
||||||
}
|
timeoutPrefs.edit().putInt(policy.packageName, pos).apply()
|
||||||
|
handleAction(action, Config.Value.TIMEOUT_LIST[pos])
|
||||||
|
}
|
||||||
|
|
||||||
override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence) {
|
private fun handleAction(action: Int, time: Int) {
|
||||||
warningText.value = helpString
|
val until = if (time > 0)
|
||||||
}
|
MILLISECONDS.toSeconds(now) + MINUTES.toSeconds(time.toLong())
|
||||||
|
else
|
||||||
|
time.toLong()
|
||||||
|
|
||||||
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
|
policy.policy = action
|
||||||
handler?.handleAction(MagiskPolicy.ALLOW)
|
policy.until = until
|
||||||
}
|
policy.uid = policy.uid % 100000 + Const.USER_ID * 100000
|
||||||
|
|
||||||
override fun onAuthenticationFailed() {
|
if (until >= 0)
|
||||||
warningText.value = resources.getString(R.string.auth_fail)
|
policyDB.update(policy).blockingAwait()
|
||||||
|
|
||||||
|
handleAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class Connector @Throws(Exception::class)
|
||||||
|
internal constructor(name: String) : SuConnector(name) {
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun onResponse() {
|
||||||
|
out.writeInt(policy.policy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open inner class ActionHandler {
|
}
|
||||||
private val cancelTasks = mutableListOf<Runnable>()
|
|
||||||
|
|
||||||
internal open fun handleAction() {
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal open fun handleAction(action: Int) {
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal open fun handleAction(action: Int, time: Int) {
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun addCancel(r: Runnable) {
|
|
||||||
cancelTasks.add(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun done() {
|
|
||||||
cancelTasks.forEach { it.run() }
|
|
||||||
DieEvent().publish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@@ -0,0 +1,60 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import org.koin.core.KoinComponent
|
||||||
|
import org.koin.core.get
|
||||||
|
|
||||||
|
object BiometricHelper: KoinComponent {
|
||||||
|
|
||||||
|
private val mgr by lazy { BiometricManager.from(get()) }
|
||||||
|
|
||||||
|
val isSupported get() = when (mgr.canAuthenticate()) {
|
||||||
|
BiometricManager.BIOMETRIC_SUCCESS -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
val isEnabled: Boolean get() {
|
||||||
|
val enabled = Config.suBiometric
|
||||||
|
if (enabled && !isSupported) {
|
||||||
|
Config.suBiometric = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun authenticate(
|
||||||
|
activity: FragmentActivity,
|
||||||
|
onError: () -> Unit = {},
|
||||||
|
onSuccess: () -> Unit): BiometricPrompt {
|
||||||
|
val prompt = BiometricPrompt(activity,
|
||||||
|
ContextCompat.getMainExecutor(activity),
|
||||||
|
object : BiometricPrompt.AuthenticationCallback() {
|
||||||
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||||
|
onError()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationFailed() {
|
||||||
|
onError()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||||
|
onSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val info = BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setConfirmationRequired(true)
|
||||||
|
.setDeviceCredentialAllowed(false)
|
||||||
|
.setTitle(activity.getString(R.string.authenticate))
|
||||||
|
.setNegativeButtonText(activity.getString(android.R.string.cancel))
|
||||||
|
.build()
|
||||||
|
prompt.authenticate(info)
|
||||||
|
return prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
22
app/src/main/java/com/topjohnwu/magisk/utils/CachedValue.kt
Normal file
22
app/src/main/java/com/topjohnwu/magisk/utils/CachedValue.kt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
class CachedValue<T>(private val factory: () -> T) : Lazy<T> {
|
||||||
|
|
||||||
|
private var _val : T? = null
|
||||||
|
|
||||||
|
override val value: T
|
||||||
|
get() {
|
||||||
|
val local = _val
|
||||||
|
return local ?: synchronized(this) {
|
||||||
|
_val ?: factory().also { _val = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isInitialized() = _val != null
|
||||||
|
|
||||||
|
fun invalidate() {
|
||||||
|
synchronized(this) {
|
||||||
|
_val = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -16,6 +16,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import androidx.viewpager.widget.ViewPager
|
import androidx.viewpager.widget.ViewPager
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import com.google.android.material.navigation.NavigationView
|
import com.google.android.material.navigation.NavigationView
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.extensions.replaceRandomWithSpecial
|
import com.topjohnwu.magisk.extensions.replaceRandomWithSpecial
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
@@ -220,4 +221,11 @@ fun getScrollPosition(view: RecyclerView) = (view.layoutManager as? LinearLayout
|
|||||||
@BindingAdapter("isEnabled")
|
@BindingAdapter("isEnabled")
|
||||||
fun setEnabled(view: View, isEnabled: Boolean) {
|
fun setEnabled(view: View, isEnabled: Boolean) {
|
||||||
view.isEnabled = isEnabled
|
view.isEnabled = isEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("error")
|
||||||
|
fun TextInputLayout.setErrorString(error: String) {
|
||||||
|
val newError = error.let { if (it.isEmpty()) null else it }
|
||||||
|
if (this.error == null && newError == null) return
|
||||||
|
this.error = newError
|
||||||
}
|
}
|
@@ -1,61 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
|
||||||
|
|
||||||
import dalvik.system.DexClassLoader
|
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
|
||||||
import java.net.URL
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
|
||||||
inline fun <reified T> T.DynamicClassLoader(apk: File) = DynamicClassLoader(apk, T::class.java.classLoader)
|
|
||||||
|
|
||||||
class DynamicClassLoader(apk: File, parent: ClassLoader?)
|
|
||||||
: DexClassLoader(apk.path, apk.parent, null, parent) {
|
|
||||||
|
|
||||||
private val base by lazy { Any::class.java.classLoader!! }
|
|
||||||
|
|
||||||
@Throws(ClassNotFoundException::class)
|
|
||||||
override fun loadClass(name: String, resolve: Boolean) : Class<*>
|
|
||||||
= findLoadedClass(name) ?: runCatching {
|
|
||||||
base.loadClass(name)
|
|
||||||
}.getOrElse {
|
|
||||||
runCatching {
|
|
||||||
findClass(name)
|
|
||||||
}.getOrElse { err ->
|
|
||||||
runCatching {
|
|
||||||
parent.loadClass(name)
|
|
||||||
}.getOrElse { throw err }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getResource(name: String) = base.getResource(name)
|
|
||||||
?: findResource(name)
|
|
||||||
?: parent?.getResource(name)
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun getResources(name: String): Enumeration<URL> {
|
|
||||||
val resources = mutableListOf(
|
|
||||||
base.getResources(name),
|
|
||||||
findResources(name), parent.getResources(name))
|
|
||||||
return object : Enumeration<URL> {
|
|
||||||
override fun hasMoreElements(): Boolean {
|
|
||||||
while (true) {
|
|
||||||
if (resources.isEmpty())
|
|
||||||
return false
|
|
||||||
if (!resources[0].hasMoreElements()) {
|
|
||||||
resources.removeAt(0)
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun nextElement(): URL {
|
|
||||||
if (!hasMoreElements())
|
|
||||||
throw NoSuchElementException()
|
|
||||||
return resources[0].nextElement()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,121 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
|
||||||
|
|
||||||
import android.annotation.TargetApi
|
|
||||||
import android.app.KeyguardManager
|
|
||||||
import android.content.Context
|
|
||||||
import android.hardware.fingerprint.FingerprintManager
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.CancellationSignal
|
|
||||||
import android.security.keystore.KeyGenParameterSpec
|
|
||||||
import android.security.keystore.KeyProperties
|
|
||||||
import com.topjohnwu.magisk.Config
|
|
||||||
import com.topjohnwu.magisk.extensions.get
|
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
|
||||||
import java.security.KeyStore
|
|
||||||
import javax.crypto.Cipher
|
|
||||||
import javax.crypto.KeyGenerator
|
|
||||||
import javax.crypto.SecretKey
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.M)
|
|
||||||
abstract class FingerprintHelper @Throws(Exception::class)
|
|
||||||
protected constructor() {
|
|
||||||
|
|
||||||
private val manager: FingerprintManager?
|
|
||||||
private val cipher: Cipher
|
|
||||||
private var cancel: CancellationSignal? = null
|
|
||||||
private val context: Context by inject()
|
|
||||||
|
|
||||||
init {
|
|
||||||
val keyStore = KeyStore.getInstance("AndroidKeyStore")
|
|
||||||
manager = context.getSystemService(FingerprintManager::class.java)
|
|
||||||
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
|
|
||||||
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
|
||||||
+ KeyProperties.ENCRYPTION_PADDING_PKCS7)
|
|
||||||
keyStore.load(null)
|
|
||||||
var key = keyStore.getKey(SU_KEYSTORE_KEY, null) as SecretKey? ?: generateKey()
|
|
||||||
runCatching {
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, key)
|
|
||||||
}.onFailure {
|
|
||||||
// Only happens on Marshmallow
|
|
||||||
key = generateKey()
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun onAuthenticationError(errorCode: Int, errString: CharSequence)
|
|
||||||
|
|
||||||
abstract fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence)
|
|
||||||
|
|
||||||
abstract fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult)
|
|
||||||
|
|
||||||
abstract fun onAuthenticationFailed()
|
|
||||||
|
|
||||||
fun authenticate() {
|
|
||||||
cancel = CancellationSignal()
|
|
||||||
val cryptoObject = FingerprintManager.CryptoObject(cipher)
|
|
||||||
manager!!.authenticate(cryptoObject, cancel, 0, Callback(), null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cancel() {
|
|
||||||
if (cancel != null)
|
|
||||||
cancel!!.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exception::class)
|
|
||||||
private fun generateKey(): SecretKey {
|
|
||||||
val keygen = KeyGenerator
|
|
||||||
.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
|
|
||||||
val builder = KeyGenParameterSpec.Builder(
|
|
||||||
SU_KEYSTORE_KEY,
|
|
||||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
|
||||||
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
|
||||||
.setUserAuthenticationRequired(true)
|
|
||||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
builder.setInvalidatedByBiometricEnrollment(false)
|
|
||||||
}
|
|
||||||
keygen.init(builder.build())
|
|
||||||
return keygen.generateKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class Callback : FingerprintManager.AuthenticationCallback() {
|
|
||||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
|
||||||
this@FingerprintHelper.onAuthenticationError(errorCode, errString)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence) {
|
|
||||||
this@FingerprintHelper.onAuthenticationHelp(helpCode, helpString)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
|
|
||||||
this@FingerprintHelper.onAuthenticationSucceeded(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationFailed() {
|
|
||||||
this@FingerprintHelper.onAuthenticationFailed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val SU_KEYSTORE_KEY = "su_key"
|
|
||||||
|
|
||||||
fun useFingerprint(): Boolean {
|
|
||||||
var fp = Config.suFingerprint
|
|
||||||
if (fp && !canUseFingerprint()) {
|
|
||||||
Config.suFingerprint = false
|
|
||||||
fp = false
|
|
||||||
}
|
|
||||||
return fp
|
|
||||||
}
|
|
||||||
|
|
||||||
fun canUseFingerprint(context: Context = get()): Boolean {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
|
||||||
return false
|
|
||||||
val km = context.getSystemService(KeyguardManager::class.java)
|
|
||||||
val fm = context.getSystemService(FingerprintManager::class.java)
|
|
||||||
return km?.isKeyguardSecure ?: false &&
|
|
||||||
fm != null && fm.isHardwareDetected && fm.hasEnrolledFingerprints()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -9,15 +9,11 @@ import java.io.Serializable
|
|||||||
* You can define if wrapped type is Nullable or not.
|
* You can define if wrapped type is Nullable or not.
|
||||||
* You can use kotlin get/set syntax for value
|
* You can use kotlin get/set syntax for value
|
||||||
*/
|
*/
|
||||||
class KObservableField<T> : ObservableField<T>, Serializable {
|
open class KObservableField<T> : ObservableField<T>, Serializable {
|
||||||
|
|
||||||
var value: T
|
var value: T
|
||||||
set(value) {
|
get() = get()
|
||||||
if (field != value) {
|
set(value) { set(value) }
|
||||||
field = value
|
|
||||||
notifyChange()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(init: T) {
|
constructor(init: T) {
|
||||||
value = init
|
value = init
|
||||||
@@ -27,23 +23,8 @@ class KObservableField<T> : ObservableField<T>, Serializable {
|
|||||||
value = init
|
value = init
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated(
|
@Suppress("UNCHECKED_CAST")
|
||||||
message = "Needed for data binding, use KObservableField.value syntax from code",
|
|
||||||
replaceWith = ReplaceWith("value")
|
|
||||||
)
|
|
||||||
override fun get(): T {
|
override fun get(): T {
|
||||||
return value
|
return super.get() as T
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Deprecated(
|
|
||||||
message = "Needed for data binding, use KObservableField.value = ... syntax from code",
|
|
||||||
replaceWith = ReplaceWith("value = newValue")
|
|
||||||
)
|
|
||||||
override fun set(newValue: T) {
|
|
||||||
value = newValue
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return "KObservableField(value=$value)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
143
app/src/main/java/com/topjohnwu/magisk/utils/Keygen.kt
Normal file
143
app/src/main/java/com/topjohnwu/magisk/utils/Keygen.kt
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.util.Base64
|
||||||
|
import android.util.Base64OutputStream
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.di.koinModules
|
||||||
|
import com.topjohnwu.magisk.utils.PatchAPK.ALPHANUM
|
||||||
|
import com.topjohnwu.signing.CryptoUtils.readCertificate
|
||||||
|
import com.topjohnwu.signing.CryptoUtils.readPrivateKey
|
||||||
|
import com.topjohnwu.superuser.internal.InternalUtils
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||||
|
import org.koin.core.context.GlobalContext
|
||||||
|
import org.koin.core.context.startKoin
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.security.KeyPairGenerator
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import java.util.*
|
||||||
|
import java.util.zip.GZIPInputStream
|
||||||
|
import java.util.zip.GZIPOutputStream
|
||||||
|
|
||||||
|
private interface CertKeyProvider {
|
||||||
|
val cert: X509Certificate
|
||||||
|
val key: PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
class Keygen: CertKeyProvider {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ALIAS = "magisk"
|
||||||
|
private val PASSWORD get() = "magisk".toCharArray()
|
||||||
|
private const val TESTKEY_CERT = "61ed377e85d386a8dfee6b864bd85b0bfaa5af81"
|
||||||
|
private const val BASE64_FLAG = Base64.NO_PADDING or Base64.NO_WRAP
|
||||||
|
}
|
||||||
|
|
||||||
|
private val start = Calendar.getInstance()
|
||||||
|
private val end = Calendar.getInstance().apply { add(Calendar.YEAR, 30) }
|
||||||
|
|
||||||
|
override val cert get() = provider.cert
|
||||||
|
override val key get() = provider.key
|
||||||
|
|
||||||
|
private val provider: CertKeyProvider
|
||||||
|
|
||||||
|
inner class KeyStoreProvider : CertKeyProvider {
|
||||||
|
private val ks by lazy { init() }
|
||||||
|
override val cert by lazy { ks.getCertificate(ALIAS) as X509Certificate }
|
||||||
|
override val key by lazy { ks.getKey(ALIAS, PASSWORD) as PrivateKey }
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestProvider : CertKeyProvider {
|
||||||
|
override val cert by lazy {
|
||||||
|
readCertificate(javaClass.getResourceAsStream("/keys/testkey.x509.pem"))
|
||||||
|
}
|
||||||
|
override val key by lazy {
|
||||||
|
readPrivateKey(javaClass.getResourceAsStream("/keys/testkey.pk8"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// This object could possibly be accessed from an external app
|
||||||
|
// Get context from reflection into Android's framework
|
||||||
|
val context = InternalUtils.getContext()
|
||||||
|
val pm = context.packageManager
|
||||||
|
val info = pm.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
|
||||||
|
val sig = info.signatures[0]
|
||||||
|
val digest = MessageDigest.getInstance("SHA1")
|
||||||
|
val chksum = digest.digest(sig.toByteArray())
|
||||||
|
|
||||||
|
val sb = StringBuilder()
|
||||||
|
for (b in chksum) {
|
||||||
|
sb.append("%02x".format(0xFF and b.toInt()))
|
||||||
|
}
|
||||||
|
|
||||||
|
provider = if (sb.toString() == TESTKEY_CERT) {
|
||||||
|
// The app was signed by the test key, continue to use it (legacy mode)
|
||||||
|
TestProvider()
|
||||||
|
} else {
|
||||||
|
KeyStoreProvider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun randomString(): String {
|
||||||
|
val rand = kotlin.random.Random.Default
|
||||||
|
val len = rand.nextInt(5, 10)
|
||||||
|
val sb = StringBuilder(len)
|
||||||
|
for (i in 0..len) {
|
||||||
|
val idx = rand.nextInt(ALPHANUM.length)
|
||||||
|
sb.append(ALPHANUM[idx])
|
||||||
|
}
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init(): KeyStore {
|
||||||
|
GlobalContext.getOrNull() ?: {
|
||||||
|
// Invoked externally, do some basic initialization
|
||||||
|
startKoin {
|
||||||
|
modules(koinModules)
|
||||||
|
}
|
||||||
|
Timber.plant(Timber.DebugTree())
|
||||||
|
}()
|
||||||
|
|
||||||
|
val raw = Config.keyStoreRaw
|
||||||
|
val ks = KeyStore.getInstance("PKCS12")
|
||||||
|
if (raw.isEmpty()) {
|
||||||
|
ks.load(null)
|
||||||
|
} else {
|
||||||
|
GZIPInputStream(Base64.decode(raw, BASE64_FLAG).inputStream()).use {
|
||||||
|
ks.load(it, PASSWORD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys already exist
|
||||||
|
if (ks.containsAlias(ALIAS))
|
||||||
|
return ks
|
||||||
|
|
||||||
|
// Generate new private key and certificate
|
||||||
|
val kp = KeyPairGenerator.getInstance("RSA").apply { initialize(4096) }.genKeyPair()
|
||||||
|
val dname = X500Name("CN=${randomString()}")
|
||||||
|
val builder = JcaX509v3CertificateBuilder(dname, BigInteger(160, Random()),
|
||||||
|
start.time, end.time, dname, kp.public)
|
||||||
|
val signer = JcaContentSignerBuilder("SHA256WithRSA").build(kp.private)
|
||||||
|
val cert = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
||||||
|
|
||||||
|
// Store them into keystore
|
||||||
|
ks.setKeyEntry(ALIAS, kp.private, PASSWORD, arrayOf(cert))
|
||||||
|
val bytes = ByteArrayOutputStream()
|
||||||
|
GZIPOutputStream(Base64OutputStream(bytes, BASE64_FLAG)).use {
|
||||||
|
ks.store(it, PASSWORD)
|
||||||
|
}
|
||||||
|
Config.keyStoreRaw = bytes.toString("UTF-8")
|
||||||
|
|
||||||
|
return ks
|
||||||
|
}
|
||||||
|
}
|
@@ -1,68 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.ContextWrapper
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.content.res.Resources
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import com.topjohnwu.magisk.Config
|
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.extensions.get
|
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
|
||||||
import com.topjohnwu.magisk.extensions.langTagToLocale
|
|
||||||
import com.topjohnwu.superuser.internal.InternalUtils
|
|
||||||
import io.reactivex.Single
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
var currentLocale = Locale.getDefault()!!
|
|
||||||
private set
|
|
||||||
|
|
||||||
val defaultLocale = Locale.getDefault()!!
|
|
||||||
|
|
||||||
val availableLocales = Single.fromCallable {
|
|
||||||
val compareId = R.string.app_changelog
|
|
||||||
val res: Resources by inject()
|
|
||||||
mutableListOf<Locale>().apply {
|
|
||||||
// Add default locale
|
|
||||||
add(Locale.ENGLISH)
|
|
||||||
|
|
||||||
// Add some special locales
|
|
||||||
add(Locale.TAIWAN)
|
|
||||||
add(Locale("pt", "BR"))
|
|
||||||
|
|
||||||
// Other locales
|
|
||||||
val otherLocales = res.assets.locales
|
|
||||||
.map { it.langTagToLocale() }
|
|
||||||
.distinctBy { LocaleManager.getString(it, compareId) }
|
|
||||||
|
|
||||||
listOf("", "").toTypedArray()
|
|
||||||
|
|
||||||
addAll(otherLocales)
|
|
||||||
}.sortedWith(Comparator { a, b ->
|
|
||||||
a.getDisplayName(a).toLowerCase(a)
|
|
||||||
.compareTo(b.getDisplayName(b).toLowerCase(b))
|
|
||||||
})
|
|
||||||
}.cache()!!
|
|
||||||
|
|
||||||
object LocaleManager {
|
|
||||||
|
|
||||||
fun setLocale(wrapper: ContextWrapper) {
|
|
||||||
val localeConfig = Config.locale
|
|
||||||
currentLocale = when {
|
|
||||||
localeConfig.isEmpty() -> defaultLocale
|
|
||||||
else -> localeConfig.langTagToLocale()
|
|
||||||
}
|
|
||||||
Locale.setDefault(currentLocale)
|
|
||||||
InternalUtils.replaceBaseContext(wrapper, getLocaleContext(wrapper, currentLocale))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLocaleContext(context: Context, locale: Locale = currentLocale): Context {
|
|
||||||
val config = Configuration(context.resources.configuration)
|
|
||||||
config.setLocale(locale)
|
|
||||||
return context.createConfigurationContext(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getString(locale: Locale, @StringRes id: Int): String {
|
|
||||||
return getLocaleContext(get(), locale).getString(id)
|
|
||||||
}
|
|
||||||
}
|
|
89
app/src/main/java/com/topjohnwu/magisk/utils/Locales.kt
Normal file
89
app/src/main/java/com/topjohnwu/magisk/utils/Locales.kt
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.content.res.Resources
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.ResourceMgr
|
||||||
|
import com.topjohnwu.magisk.extensions.langTagToLocale
|
||||||
|
import com.topjohnwu.magisk.extensions.toLangTag
|
||||||
|
import io.reactivex.Single
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.Comparator
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
var currentLocale: Locale = Locale.getDefault()
|
||||||
|
|
||||||
|
@SuppressLint("ConstantLocale")
|
||||||
|
val defaultLocale: Locale = Locale.getDefault()
|
||||||
|
|
||||||
|
val availableLocales = Single.fromCallable {
|
||||||
|
val compareId = R.string.app_changelog
|
||||||
|
val config = ResourceMgr.resource.configuration
|
||||||
|
val metrics = ResourceMgr.resource.displayMetrics
|
||||||
|
val res = Resources(ResourceMgr.resource.assets, metrics, config)
|
||||||
|
|
||||||
|
val locales = mutableListOf<Locale>().apply {
|
||||||
|
// Add default locale
|
||||||
|
add(Locale.ENGLISH)
|
||||||
|
|
||||||
|
// Add some special locales
|
||||||
|
add(Locale.TAIWAN)
|
||||||
|
add(Locale("pt", "BR"))
|
||||||
|
|
||||||
|
// Other locales
|
||||||
|
val otherLocales = ResourceMgr.resource.assets.locales
|
||||||
|
.map { it.langTagToLocale() }
|
||||||
|
.distinctBy {
|
||||||
|
config.setLocale(it)
|
||||||
|
res.updateConfiguration(config, metrics)
|
||||||
|
res.getString(compareId)
|
||||||
|
}
|
||||||
|
|
||||||
|
addAll(otherLocales)
|
||||||
|
}.sortedWith(Comparator { a, b ->
|
||||||
|
a.getDisplayName(a).toLowerCase(a)
|
||||||
|
.compareTo(b.getDisplayName(b).toLowerCase(b))
|
||||||
|
})
|
||||||
|
|
||||||
|
config.setLocale(defaultLocale)
|
||||||
|
res.updateConfiguration(config, metrics)
|
||||||
|
val defName = res.getString(R.string.system_default)
|
||||||
|
|
||||||
|
// Restore back to current locale
|
||||||
|
config.setLocale(currentLocale)
|
||||||
|
res.updateConfiguration(config, metrics)
|
||||||
|
|
||||||
|
Pair(locales, defName)
|
||||||
|
}.map { (locales, defName) ->
|
||||||
|
val names = ArrayList<String>(locales.size + 1)
|
||||||
|
val values = ArrayList<String>(locales.size + 1)
|
||||||
|
|
||||||
|
names.add(defName)
|
||||||
|
values.add("")
|
||||||
|
|
||||||
|
locales.forEach { locale ->
|
||||||
|
names.add(locale.getDisplayName(locale))
|
||||||
|
values.add(locale.toLangTag())
|
||||||
|
}
|
||||||
|
|
||||||
|
Pair(names.toTypedArray(), values.toTypedArray())
|
||||||
|
}.cache()!!
|
||||||
|
|
||||||
|
fun Resources.updateConfig(config: Configuration = configuration) {
|
||||||
|
config.setLocale(currentLocale)
|
||||||
|
updateConfiguration(config, displayMetrics)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshLocale() {
|
||||||
|
val localeConfig = Config.locale
|
||||||
|
currentLocale = when {
|
||||||
|
localeConfig.isEmpty() -> defaultLocale
|
||||||
|
else -> localeConfig.langTagToLocale()
|
||||||
|
}
|
||||||
|
Locale.setDefault(currentLocale)
|
||||||
|
ResourceMgr.resource.updateConfig()
|
||||||
|
}
|
@@ -1,11 +1,14 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.*
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
|
import com.topjohnwu.magisk.extensions.DynamicClassLoader
|
||||||
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.extensions.subscribeK
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity
|
import com.topjohnwu.magisk.extensions.writeTo
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import com.topjohnwu.signing.JarMap
|
import com.topjohnwu.signing.JarMap
|
||||||
import com.topjohnwu.signing.SignAPK
|
import com.topjohnwu.signing.SignAPK
|
||||||
@@ -24,7 +27,7 @@ object PatchAPK {
|
|||||||
private val UPPERALPHA = LOWERALPHA.toUpperCase()
|
private val UPPERALPHA = LOWERALPHA.toUpperCase()
|
||||||
private val ALPHA = LOWERALPHA + UPPERALPHA
|
private val ALPHA = LOWERALPHA + UPPERALPHA
|
||||||
private const val DIGITS = "0123456789"
|
private const val DIGITS = "0123456789"
|
||||||
private val ALPHANUM = ALPHA + DIGITS
|
val ALPHANUM = ALPHA + DIGITS
|
||||||
private val ALPHANUMDOTS = "$ALPHANUM............"
|
private val ALPHANUMDOTS = "$ALPHANUM............"
|
||||||
|
|
||||||
private fun genPackageName(prefix: String, length: Int): String {
|
private fun genPackageName(prefix: String, length: Int): String {
|
||||||
@@ -47,81 +50,87 @@ object PatchAPK {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findAndPatch(xml: ByteArray, from: String, to: String): Boolean {
|
private fun findAndPatch(xml: ByteArray, from: String, to: String): Boolean {
|
||||||
if (from.length != to.length)
|
if (to.length > from.length)
|
||||||
return false
|
return false
|
||||||
val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer()
|
val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer()
|
||||||
val offList = mutableListOf<Int>()
|
val offList = mutableListOf<Int>()
|
||||||
var i = 0
|
var i = 0
|
||||||
while (i < buf.length - from.length) {
|
loop@ while (i < buf.length - from.length) {
|
||||||
var match = true
|
for (j in from.indices) {
|
||||||
for (j in 0 until from.length) {
|
|
||||||
if (buf.get(i + j) != from[j]) {
|
if (buf.get(i + j) != from[j]) {
|
||||||
match = false
|
++i
|
||||||
break
|
continue@loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (match) {
|
offList.add(i)
|
||||||
offList.add(i)
|
i += from.length
|
||||||
i += from.length
|
|
||||||
}
|
|
||||||
++i
|
|
||||||
}
|
}
|
||||||
if (offList.isEmpty())
|
if (offList.isEmpty())
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
val toBuf = to.toCharArray().copyOf(from.length)
|
||||||
for (off in offList) {
|
for (off in offList) {
|
||||||
buf.position(off)
|
buf.position(off)
|
||||||
buf.put(to)
|
buf.put(toBuf)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findAndPatch(xml: ByteArray, a: Int, b: Int): Boolean {
|
private fun patchAndHide(context: Context, label: String): Boolean {
|
||||||
val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer()
|
val src = if (!isRunningAsStub && SDK_INT >= 28 &&
|
||||||
val len = xml.size / 4
|
Info.env.magiskVersionCode >= Const.Version.PROVIDER_CONNECT) {
|
||||||
for (i in 0 until len) {
|
// If not running as stub, and we are compatible with stub, use stub
|
||||||
if (buf.get(i) == a) {
|
val stub = File(context.cacheDir, "stub.apk")
|
||||||
buf.put(i, b)
|
val svc = get<GithubRawServices>()
|
||||||
return true
|
runCatching {
|
||||||
|
svc.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use {
|
||||||
|
it.writeTo(stub)
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
Timber.e(it)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
stub.path
|
||||||
|
} else {
|
||||||
|
context.packageCodePath
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun patchAndHide(context: Context): Boolean {
|
// Generate a new random package name and signature
|
||||||
// Generate a new app with random package name
|
val repack = File(context.cacheDir, "patched.apk")
|
||||||
val repack = File(context.filesDir, "patched.apk")
|
|
||||||
val pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length)
|
val pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length)
|
||||||
|
Config.keyStoreRaw = ""
|
||||||
|
|
||||||
if (!patch(context.packageCodePath, repack.path, pkg))
|
if (!patch(src, repack.path, pkg, label))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
// Install the application
|
// Install the application
|
||||||
repack.setReadable(true, false)
|
repack.setReadable(true, false)
|
||||||
if (!Shell.su("pm install $repack").exec().isSuccess)
|
if (!Shell.su("force_pm_install $repack").exec().isSuccess)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
Config.suManager = pkg
|
Config.suManager = pkg
|
||||||
Config.export()
|
Config.export()
|
||||||
RootUtils.rmAndLaunch(BuildConfig.APPLICATION_ID,
|
Shell.su("pm uninstall ${BuildConfig.APPLICATION_ID}").submit()
|
||||||
ComponentName(pkg, ClassMap.get<Class<*>>(SplashActivity::class.java).name))
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun patch(apk: String, out: String, pkg: String): Boolean {
|
@JvmOverloads
|
||||||
|
fun patch(apk: String, out: String, pkg: String, label: String = "Manager"): Boolean {
|
||||||
try {
|
try {
|
||||||
val jar = JarMap(apk)
|
val jar = JarMap.open(apk)
|
||||||
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
|
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
|
||||||
val xml = jar.getRawData(je)
|
val xml = jar.getRawData(je)
|
||||||
|
|
||||||
if (!findAndPatch(xml, BuildConfig.APPLICATION_ID, pkg) ||
|
if (!findAndPatch(xml, BuildConfig.APPLICATION_ID, pkg) ||
|
||||||
!findAndPatch(xml, R.string.app_name, R.string.re_app_name))
|
!findAndPatch(xml, "Magisk Manager", label))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
// Write apk changes
|
// Write apk changes
|
||||||
jar.getOutputStream(je).write(xml)
|
jar.getOutputStream(je).write(xml)
|
||||||
SignAPK.sign(jar, FileOutputStream(out).buffered())
|
val keys = Keygen()
|
||||||
|
SignAPK.sign(keys.cert, keys.key, jar, FileOutputStream(out).buffered())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
return false
|
return false
|
||||||
@@ -130,11 +139,36 @@ object PatchAPK {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hideManager(context: Context) {
|
fun patch(apk: File, out: File, pkg: String, label: String): Boolean {
|
||||||
|
try {
|
||||||
|
if (apk.length() < 1 shl 18) {
|
||||||
|
// APK is smaller than 256K, must be stub
|
||||||
|
return patch(apk.path, out.path, pkg, label)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try using the new APK to patch itself
|
||||||
|
val loader = DynamicClassLoader(apk)
|
||||||
|
val cls = loader.loadClass("a.a")
|
||||||
|
|
||||||
|
for (m in cls.declaredMethods) {
|
||||||
|
val pars = m.parameterTypes
|
||||||
|
if (pars.size == 4 && pars[0] == String::class.java) {
|
||||||
|
return m.invoke(null, apk.path, out.path, pkg, label) as Boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw Exception("No matching method found")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
// Fallback to use the current implementation
|
||||||
|
return patch(apk.path, out.path, pkg, label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideManager(context: Context, label: String) {
|
||||||
Completable.fromAction {
|
Completable.fromAction {
|
||||||
val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title))
|
val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title))
|
||||||
Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build())
|
Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build())
|
||||||
if (!patchAndHide(context))
|
if (!patchAndHide(context, label))
|
||||||
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
|
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
|
||||||
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID)
|
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID)
|
||||||
}.subscribeK()
|
}.subscribeK()
|
||||||
|
36
app/src/main/java/com/topjohnwu/magisk/utils/RootInit.kt
Normal file
36
app/src/main/java/com/topjohnwu/magisk/utils/RootInit.kt
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.topjohnwu.magisk.Const
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.extensions.rawResource
|
||||||
|
import com.topjohnwu.magisk.wrap
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
|
|
||||||
|
class RootInit : Shell.Initializer() {
|
||||||
|
|
||||||
|
override fun onInit(context: Context, shell: Shell): Boolean {
|
||||||
|
return init(context.wrap(), shell)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(context: Context, shell: Shell): Boolean {
|
||||||
|
val job = shell.newJob()
|
||||||
|
if (shell.isRoot) {
|
||||||
|
job.add(context.rawResource(R.raw.util_functions))
|
||||||
|
.add(context.rawResource(R.raw.utils))
|
||||||
|
Const.MAGISK_DISABLE_FILE = SuFile("/cache/.disable_magisk")
|
||||||
|
} else {
|
||||||
|
job.add(context.rawResource(R.raw.nonroot_utils))
|
||||||
|
}
|
||||||
|
|
||||||
|
job.add(
|
||||||
|
"mount_partitions",
|
||||||
|
"get_flags",
|
||||||
|
"run_migrations",
|
||||||
|
"export BOOTMODE=true"
|
||||||
|
).exec()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@@ -1,158 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
|
||||||
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import com.topjohnwu.magisk.Const
|
|
||||||
import com.topjohnwu.magisk.Info
|
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.extensions.rawResource
|
|
||||||
import com.topjohnwu.magisk.extensions.toShellCmd
|
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
|
||||||
import com.topjohnwu.superuser.io.SuFile
|
|
||||||
import java.util.*
|
|
||||||
import java.lang.reflect.Array as RArray
|
|
||||||
|
|
||||||
fun Intent.toCommand(args: MutableList<String>) {
|
|
||||||
if (action != null) {
|
|
||||||
args.add("-a")
|
|
||||||
args.add(action!!)
|
|
||||||
}
|
|
||||||
if (component != null) {
|
|
||||||
args.add("-n")
|
|
||||||
args.add(component!!.flattenToString())
|
|
||||||
}
|
|
||||||
if (data != null) {
|
|
||||||
args.add("-d")
|
|
||||||
args.add(dataString!!)
|
|
||||||
}
|
|
||||||
if (categories != null) {
|
|
||||||
for (cat in categories) {
|
|
||||||
args.add("-c")
|
|
||||||
args.add(cat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type != null) {
|
|
||||||
args.add("-t")
|
|
||||||
args.add(type!!)
|
|
||||||
}
|
|
||||||
val extras = extras
|
|
||||||
if (extras != null) {
|
|
||||||
loop@ for (key in extras.keySet()) {
|
|
||||||
val v = extras.get(key) ?: continue
|
|
||||||
var value: Any = v
|
|
||||||
val arg: String
|
|
||||||
when {
|
|
||||||
v is String -> arg = "--es"
|
|
||||||
v is Boolean -> arg = "--ez"
|
|
||||||
v is Int -> arg = "--ei"
|
|
||||||
v is Long -> arg = "--el"
|
|
||||||
v is Float -> arg = "--ef"
|
|
||||||
v is Uri -> arg = "--eu"
|
|
||||||
v is ComponentName -> {
|
|
||||||
arg = "--ecn"
|
|
||||||
value = v.flattenToString()
|
|
||||||
}
|
|
||||||
v is ArrayList<*> -> {
|
|
||||||
if (v.size <= 0)
|
|
||||||
/* Impossible to know the type due to type erasure */
|
|
||||||
continue@loop
|
|
||||||
|
|
||||||
arg = if (v[0] is Int)
|
|
||||||
"--eial"
|
|
||||||
else if (v[0] is Long)
|
|
||||||
"--elal"
|
|
||||||
else if (v[0] is Float)
|
|
||||||
"--efal"
|
|
||||||
else if (v[0] is String)
|
|
||||||
"--esal"
|
|
||||||
else
|
|
||||||
continue@loop /* Unsupported */
|
|
||||||
|
|
||||||
val sb = StringBuilder()
|
|
||||||
for (o in v) {
|
|
||||||
sb.append(o.toString().replace(",", "\\,"))
|
|
||||||
sb.append(',')
|
|
||||||
}
|
|
||||||
// Remove trailing comma
|
|
||||||
sb.deleteCharAt(sb.length - 1)
|
|
||||||
value = sb
|
|
||||||
}
|
|
||||||
v.javaClass.isArray -> {
|
|
||||||
arg = if (v is IntArray)
|
|
||||||
"--eia"
|
|
||||||
else if (v is LongArray)
|
|
||||||
"--ela"
|
|
||||||
else if (v is FloatArray)
|
|
||||||
"--efa"
|
|
||||||
else if (v is Array<*> && v.isArrayOf<String>())
|
|
||||||
"--esa"
|
|
||||||
else
|
|
||||||
continue@loop /* Unsupported */
|
|
||||||
|
|
||||||
val sb = StringBuilder()
|
|
||||||
val len = RArray.getLength(v)
|
|
||||||
for (i in 0 until len) {
|
|
||||||
sb.append(RArray.get(v, i)!!.toString().replace(",", "\\,"))
|
|
||||||
sb.append(',')
|
|
||||||
}
|
|
||||||
// Remove trailing comma
|
|
||||||
sb.deleteCharAt(sb.length - 1)
|
|
||||||
value = sb
|
|
||||||
}
|
|
||||||
else -> continue@loop
|
|
||||||
} /* Unsupported */
|
|
||||||
|
|
||||||
args.add(arg)
|
|
||||||
args.add(key)
|
|
||||||
args.add(value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
args.add("-f")
|
|
||||||
args.add(flags.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startActivity(intent: Intent) {
|
|
||||||
if (intent.component == null)
|
|
||||||
return
|
|
||||||
val args = ArrayList<String>()
|
|
||||||
args.add("am")
|
|
||||||
args.add("start")
|
|
||||||
intent.toCommand(args)
|
|
||||||
Shell.su(args.toShellCmd()).exec()
|
|
||||||
}
|
|
||||||
|
|
||||||
class RootUtils : Shell.Initializer() {
|
|
||||||
|
|
||||||
override fun onInit(context: Context, shell: Shell): Boolean {
|
|
||||||
val job = shell.newJob()
|
|
||||||
if (shell.isRoot) {
|
|
||||||
job.add(context.rawResource(R.raw.util_functions))
|
|
||||||
.add(context.rawResource(R.raw.utils))
|
|
||||||
Const.MAGISK_DISABLE_FILE = SuFile("/cache/.disable_magisk")
|
|
||||||
Info.loadMagiskInfo()
|
|
||||||
} else {
|
|
||||||
job.add(context.rawResource(R.raw.nonroot_utils))
|
|
||||||
}
|
|
||||||
|
|
||||||
job.add("mount_partitions",
|
|
||||||
"get_flags",
|
|
||||||
"run_migrations",
|
|
||||||
"export BOOTMODE=true")
|
|
||||||
.exec()
|
|
||||||
|
|
||||||
Info.keepVerity = ShellUtils.fastCmd("echo \$KEEPVERITY").toBoolean()
|
|
||||||
Info.keepEnc = ShellUtils.fastCmd("echo \$KEEPFORCEENCRYPT").toBoolean()
|
|
||||||
Info.recovery = ShellUtils.fastCmd("echo \$RECOVERYMODE").toBoolean()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun rmAndLaunch(rm: String, component: ComponentName) {
|
|
||||||
Shell.su("(rm_launch $rm ${component.flattenToString()})").exec()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,11 +2,9 @@ package com.topjohnwu.magisk.utils
|
|||||||
|
|
||||||
import android.net.LocalSocket
|
import android.net.LocalSocket
|
||||||
import android.net.LocalSocketAddress
|
import android.net.LocalSocketAddress
|
||||||
import android.os.Bundle
|
import androidx.collection.ArrayMap
|
||||||
import android.text.TextUtils
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.nio.charset.Charset
|
|
||||||
|
|
||||||
abstract class SuConnector @Throws(IOException::class)
|
abstract class SuConnector @Throws(IOException::class)
|
||||||
protected constructor(name: String) {
|
protected constructor(name: String) {
|
||||||
@@ -21,24 +19,23 @@ protected constructor(name: String) {
|
|||||||
input = DataInputStream(BufferedInputStream(socket.inputStream))
|
input = DataInputStream(BufferedInputStream(socket.inputStream))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
private fun readString(): String {
|
private fun readString(): String {
|
||||||
val len = input.readInt()
|
val len = input.readInt()
|
||||||
val buf = ByteArray(len)
|
val buf = ByteArray(len)
|
||||||
input.readFully(buf)
|
input.readFully(buf)
|
||||||
return String(buf, Charset.forName("UTF-8"))
|
return String(buf, Charsets.UTF_8)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun readSocketInput(): Bundle {
|
fun readRequest(): Map<String, String> {
|
||||||
val bundle = Bundle()
|
val ret = ArrayMap<String, String>()
|
||||||
while (true) {
|
while (true) {
|
||||||
val name = readString()
|
val name = readString()
|
||||||
if (TextUtils.equals(name, "eof"))
|
if (name == "eof")
|
||||||
break
|
break
|
||||||
bundle.putString(name, readString())
|
ret[name] = readString()
|
||||||
}
|
}
|
||||||
return bundle
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
fun response() {
|
fun response() {
|
||||||
|
135
app/src/main/java/com/topjohnwu/magisk/utils/SuHandler.kt
Normal file
135
app/src/main/java/com/topjohnwu/magisk/utils/SuHandler.kt
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Process
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.topjohnwu.magisk.*
|
||||||
|
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||||
|
import com.topjohnwu.magisk.extensions.get
|
||||||
|
import com.topjohnwu.magisk.extensions.startActivity
|
||||||
|
import com.topjohnwu.magisk.extensions.startActivityWithRoot
|
||||||
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
|
import com.topjohnwu.magisk.model.entity.toLog
|
||||||
|
import com.topjohnwu.magisk.model.entity.toPolicy
|
||||||
|
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
object SuHandler : ProviderCallHandler {
|
||||||
|
|
||||||
|
const val REQUEST = "request"
|
||||||
|
const val LOG = "log"
|
||||||
|
const val NOTIFY = "notify"
|
||||||
|
const val TEST = "test"
|
||||||
|
|
||||||
|
override fun call(context: Context, method: String, arg: String?, extras: Bundle?): Bundle? {
|
||||||
|
invoke(context.wrap(), method, extras)
|
||||||
|
return Bundle.EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun invoke(context: Context, action: String?, data: Bundle?) {
|
||||||
|
data ?: return
|
||||||
|
|
||||||
|
// Debug messages
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Timber.d(action)
|
||||||
|
data.let { bundle ->
|
||||||
|
bundle.keySet().forEach {
|
||||||
|
Timber.d("[%s]=[%s]", it, bundle[it])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when (action) {
|
||||||
|
REQUEST -> {
|
||||||
|
val intent = context.intent<SuRequestActivity>()
|
||||||
|
.setAction(action)
|
||||||
|
.putExtras(data)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
|
||||||
|
if (Build.VERSION.SDK_INT >= 29) {
|
||||||
|
// Android Q does not allow starting activity from background
|
||||||
|
intent.startActivityWithRoot()
|
||||||
|
} else {
|
||||||
|
intent.startActivity(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG -> handleLogs(context, data)
|
||||||
|
NOTIFY -> handleNotify(context, data)
|
||||||
|
TEST -> {
|
||||||
|
val mode = data.getInt("mode", 2)
|
||||||
|
Shell.su(
|
||||||
|
"magisk --connect-mode $mode",
|
||||||
|
"magisk --use-broadcast"
|
||||||
|
).submit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Any?.toInt(): Int? {
|
||||||
|
return when (this) {
|
||||||
|
is Int -> this
|
||||||
|
is Long -> this.toInt()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleLogs(context: Context, data: Bundle) {
|
||||||
|
val fromUid = data["from.uid"].toInt() ?: return
|
||||||
|
if (fromUid == Process.myUid())
|
||||||
|
return
|
||||||
|
|
||||||
|
val pm = context.packageManager
|
||||||
|
|
||||||
|
val notify = data.getBoolean("notify", true)
|
||||||
|
val allow = data["policy"].toInt() ?: return
|
||||||
|
|
||||||
|
val policy = runCatching { fromUid.toPolicy(pm, allow) }.getOrElse { return }
|
||||||
|
|
||||||
|
if (notify)
|
||||||
|
notify(context, policy)
|
||||||
|
|
||||||
|
val toUid = data["to.uid"].toInt() ?: return
|
||||||
|
val pid = data["pid"].toInt() ?: return
|
||||||
|
|
||||||
|
val command = data.getString("command") ?: return
|
||||||
|
val log = policy.toLog(
|
||||||
|
toUid = toUid,
|
||||||
|
fromPid = pid,
|
||||||
|
command = command
|
||||||
|
)
|
||||||
|
|
||||||
|
val logRepo = get<LogRepository>()
|
||||||
|
logRepo.insert(log).subscribeK(onError = { Timber.e(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleNotify(context: Context, data: Bundle) {
|
||||||
|
val fromUid = data["from.uid"].toInt() ?: return
|
||||||
|
if (fromUid == Process.myUid())
|
||||||
|
return
|
||||||
|
|
||||||
|
val pm = context.packageManager
|
||||||
|
val allow = data["policy"].toInt() ?: return
|
||||||
|
|
||||||
|
runCatching {
|
||||||
|
val policy = fromUid.toPolicy(pm, allow)
|
||||||
|
if (policy.policy >= 0)
|
||||||
|
notify(context, policy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notify(context: Context, policy: MagiskPolicy) {
|
||||||
|
if (policy.notification && Config.suNotification == Config.Value.NOTIFICATION_TOAST) {
|
||||||
|
val resId = if (policy.policy == MagiskPolicy.ALLOW)
|
||||||
|
R.string.su_allow_toast
|
||||||
|
else
|
||||||
|
R.string.su_deny_toast
|
||||||
|
|
||||||
|
Utils.toast(context.getString(resId, policy.appName), Toast.LENGTH_SHORT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,95 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Process
|
|
||||||
import android.widget.Toast
|
|
||||||
import com.topjohnwu.magisk.Config
|
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.data.database.PolicyDao
|
|
||||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
|
||||||
import com.topjohnwu.magisk.model.entity.toLog
|
|
||||||
import com.topjohnwu.magisk.model.entity.toPolicy
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
object SuLogger {
|
|
||||||
|
|
||||||
private val context: Context by inject()
|
|
||||||
|
|
||||||
fun handleLogs(intent: Intent) {
|
|
||||||
|
|
||||||
val fromUid = intent.getIntExtra("from.uid", -1)
|
|
||||||
if (fromUid < 0) return
|
|
||||||
if (fromUid == Process.myUid()) return
|
|
||||||
|
|
||||||
val pm: PackageManager by inject()
|
|
||||||
|
|
||||||
val notify: Boolean
|
|
||||||
val data = intent.extras
|
|
||||||
val policy: MagiskPolicy = if (data!!.containsKey("notify")) {
|
|
||||||
notify = data.getBoolean("notify")
|
|
||||||
runCatching {
|
|
||||||
fromUid.toPolicy(pm)
|
|
||||||
}.getOrElse { return }
|
|
||||||
} else {
|
|
||||||
// Doesn't report whether notify or not, check database ourselves
|
|
||||||
val policyDB: PolicyDao by inject()
|
|
||||||
val policy = policyDB.fetch(fromUid).blockingGet() ?: return
|
|
||||||
notify = policy.notification
|
|
||||||
policy
|
|
||||||
}.copy(policy = data.getInt("policy", -1))
|
|
||||||
|
|
||||||
if (policy.policy < 0)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (notify)
|
|
||||||
handleNotify(policy)
|
|
||||||
|
|
||||||
val toUid = intent.getIntExtra("to.uid", -1)
|
|
||||||
if (toUid < 0) return
|
|
||||||
|
|
||||||
val pid = intent.getIntExtra("pid", -1)
|
|
||||||
if (pid < 0) return
|
|
||||||
|
|
||||||
val command = intent.getStringExtra("command") ?: return
|
|
||||||
val log = policy.toLog(
|
|
||||||
toUid = toUid,
|
|
||||||
fromPid = pid,
|
|
||||||
command = command,
|
|
||||||
date = Date()
|
|
||||||
)
|
|
||||||
|
|
||||||
val logRepo: LogRepository by inject()
|
|
||||||
logRepo.put(log).blockingGet()?.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleNotify(policy: MagiskPolicy) {
|
|
||||||
if (policy.notification && Config.suNotification == Config.Value.NOTIFICATION_TOAST) {
|
|
||||||
Utils.toast(
|
|
||||||
context.getString(
|
|
||||||
if (policy.policy == MagiskPolicy.ALLOW)
|
|
||||||
R.string.su_allow_toast
|
|
||||||
else
|
|
||||||
R.string.su_deny_toast, policy.appName
|
|
||||||
),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleNotify(intent: Intent) {
|
|
||||||
val fromUid = intent.getIntExtra("from.uid", -1)
|
|
||||||
if (fromUid < 0) return
|
|
||||||
if (fromUid == Process.myUid()) return
|
|
||||||
runCatching {
|
|
||||||
val packageManager: PackageManager by inject()
|
|
||||||
val policy = fromUid.toPolicy(packageManager)
|
|
||||||
.copy(policy = intent.getIntExtra("policy", -1))
|
|
||||||
if (policy.policy >= 0)
|
|
||||||
handleNotify(policy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -11,7 +11,6 @@ import com.topjohnwu.magisk.*
|
|||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
import com.topjohnwu.magisk.model.update.UpdateCheckService
|
import com.topjohnwu.magisk.model.update.UpdateCheckService
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -34,7 +33,7 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun showSuperUser(): Boolean {
|
fun showSuperUser(): Boolean {
|
||||||
return Shell.rootAccess() && (Const.USER_ID == 0
|
return Info.env.isActive && (Const.USER_ID == 0
|
||||||
|| Config.suMultiuserMode != Config.Value.MULTIUSER_MODE_OWNER_MANAGED)
|
|| Config.suMultiuserMode != Config.Value.MULTIUSER_MODE_OWNER_MANAGED)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +44,7 @@ object Utils {
|
|||||||
.setRequiresDeviceIdle(true)
|
.setRequiresDeviceIdle(true)
|
||||||
.build()
|
.build()
|
||||||
val request = PeriodicWorkRequest
|
val request = PeriodicWorkRequest
|
||||||
.Builder(ClassMap[UpdateCheckService::class.java], 12, TimeUnit.HOURS)
|
.Builder(ClassMap[UpdateCheckService::class.java] as Class<Worker>, 12, TimeUnit.HOURS)
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
.build()
|
.build()
|
||||||
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
|
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
|
||||||
|
@@ -48,7 +48,7 @@ object MarkDownWindow : KoinComponent {
|
|||||||
AlertDialog.Builder(activity)
|
AlertDialog.Builder(activity)
|
||||||
.setTitle(title)
|
.setTitle(title)
|
||||||
.setView(mv)
|
.setView(mv)
|
||||||
.setNegativeButton(R.string.close) { dialog, _ -> dialog.dismiss() }
|
.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.dismiss() }
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,100 +1,120 @@
|
|||||||
package com.topjohnwu.magisk.view
|
package com.topjohnwu.magisk.view
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import android.os.Build
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import androidx.core.app.TaskStackBuilder
|
import androidx.core.app.TaskStackBuilder
|
||||||
import com.topjohnwu.magisk.ClassMap
|
import androidx.core.content.getSystemService
|
||||||
import com.topjohnwu.magisk.Const
|
import androidx.core.graphics.drawable.toIcon
|
||||||
import com.topjohnwu.magisk.Info
|
import com.topjohnwu.magisk.*
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.Const.ID.PROGRESS_NOTIFICATION_CHANNEL
|
||||||
|
import com.topjohnwu.magisk.Const.ID.UPDATE_NOTIFICATION_CHANNEL
|
||||||
import com.topjohnwu.magisk.extensions.get
|
import com.topjohnwu.magisk.extensions.get
|
||||||
|
import com.topjohnwu.magisk.extensions.getBitmap
|
||||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity
|
import com.topjohnwu.magisk.ui.SplashActivity
|
||||||
|
|
||||||
object Notifications {
|
object Notifications {
|
||||||
|
|
||||||
val mgr by lazy { NotificationManagerCompat.from(get()) }
|
val mgr by lazy { get<Context>().getSystemService<NotificationManager>()!! }
|
||||||
|
|
||||||
fun setup(context: Context) {
|
fun setup(context: Context) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (SDK_INT >= 26) {
|
||||||
mgr.deleteNotificationChannel("magisk_notification")
|
var channel = NotificationChannel(UPDATE_NOTIFICATION_CHANNEL,
|
||||||
var channel = NotificationChannel(Const.ID.UPDATE_NOTIFICATION_CHANNEL,
|
|
||||||
context.getString(R.string.update_channel), NotificationManager.IMPORTANCE_DEFAULT)
|
context.getString(R.string.update_channel), NotificationManager.IMPORTANCE_DEFAULT)
|
||||||
mgr.createNotificationChannel(channel)
|
mgr.createNotificationChannel(channel)
|
||||||
channel = NotificationChannel(Const.ID.PROGRESS_NOTIFICATION_CHANNEL,
|
channel = NotificationChannel(PROGRESS_NOTIFICATION_CHANNEL,
|
||||||
context.getString(R.string.progress_channel), NotificationManager.IMPORTANCE_LOW)
|
context.getString(R.string.progress_channel), NotificationManager.IMPORTANCE_LOW)
|
||||||
mgr.createNotificationChannel(channel)
|
mgr.createNotificationChannel(channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateBuilder(context: Context): Notification.Builder {
|
||||||
|
return Notification.Builder(context).apply {
|
||||||
|
val bitmap = context.getBitmap(R.drawable.ic_magisk_outline)
|
||||||
|
setLargeIcon(bitmap)
|
||||||
|
if (SDK_INT >= 26) {
|
||||||
|
setSmallIcon(bitmap.toIcon())
|
||||||
|
setChannelId(UPDATE_NOTIFICATION_CHANNEL)
|
||||||
|
} else {
|
||||||
|
setSmallIcon(R.drawable.ic_magisk_outline)
|
||||||
|
setVibrate(longArrayOf(0, 100, 100, 100))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun magiskUpdate(context: Context) {
|
fun magiskUpdate(context: Context) {
|
||||||
val intent = Intent(context, ClassMap[SplashActivity::class.java])
|
val intent = context.intent<SplashActivity>()
|
||||||
intent.putExtra(Const.Key.OPEN_SECTION, "magisk")
|
.putExtra(Const.Key.OPEN_SECTION, "magisk")
|
||||||
val stackBuilder = TaskStackBuilder.create(context)
|
val stackBuilder = TaskStackBuilder.create(context)
|
||||||
stackBuilder.addParentStack(ClassMap.get<Class<*>>(SplashActivity::class.java))
|
stackBuilder.addParentStack(SplashActivity::class.java.cmp(context.packageName))
|
||||||
stackBuilder.addNextIntent(intent)
|
stackBuilder.addNextIntent(intent)
|
||||||
val pendingIntent = stackBuilder.getPendingIntent(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
|
val pendingIntent = stackBuilder.getPendingIntent(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
|
||||||
val builder = NotificationCompat.Builder(context, Const.ID.UPDATE_NOTIFICATION_CHANNEL)
|
val builder = updateBuilder(context)
|
||||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
.setContentTitle(context.getString(R.string.magisk_update_title))
|
||||||
.setContentTitle(context.getString(R.string.magisk_update_title))
|
.setContentText(context.getString(R.string.manager_download_install))
|
||||||
.setContentText(context.getString(R.string.manager_download_install))
|
.setAutoCancel(true)
|
||||||
.setVibrate(longArrayOf(0, 100, 100, 100))
|
.setContentIntent(pendingIntent)
|
||||||
.setAutoCancel(true)
|
|
||||||
.setContentIntent(pendingIntent)
|
|
||||||
|
|
||||||
mgr.notify(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID, builder.build())
|
mgr.notify(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID, builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun managerUpdate(context: Context) {
|
fun managerUpdate(context: Context) {
|
||||||
val intent = Intent(context, ClassMap[GeneralReceiver::class.java])
|
val intent = context.intent<GeneralReceiver>()
|
||||||
intent.action = Const.Key.BROADCAST_MANAGER_UPDATE
|
.setAction(Const.Key.BROADCAST_MANAGER_UPDATE)
|
||||||
intent.putExtra(Const.Key.INTENT_SET_APP, Info.remote.app)
|
.putExtra(Const.Key.INTENT_SET_APP, Info.remote.app)
|
||||||
|
|
||||||
val pendingIntent = PendingIntent.getBroadcast(context,
|
val pendingIntent = PendingIntent.getBroadcast(context,
|
||||||
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
|
||||||
val builder = NotificationCompat.Builder(context, Const.ID.UPDATE_NOTIFICATION_CHANNEL)
|
val builder = updateBuilder(context)
|
||||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
.setContentTitle(context.getString(R.string.manager_update_title))
|
||||||
.setContentTitle(context.getString(R.string.manager_update_title))
|
.setContentText(context.getString(R.string.manager_download_install))
|
||||||
.setContentText(context.getString(R.string.manager_download_install))
|
.setAutoCancel(true)
|
||||||
.setVibrate(longArrayOf(0, 100, 100, 100))
|
.setContentIntent(pendingIntent)
|
||||||
.setAutoCancel(true)
|
|
||||||
.setContentIntent(pendingIntent)
|
|
||||||
|
|
||||||
mgr.notify(Const.ID.APK_UPDATE_NOTIFICATION_ID, builder.build())
|
mgr.notify(Const.ID.APK_UPDATE_NOTIFICATION_ID, builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dtboPatched(context: Context) {
|
fun dtboPatched(context: Context) {
|
||||||
val intent = Intent(context, ClassMap[GeneralReceiver::class.java])
|
val intent = context.intent<GeneralReceiver>()
|
||||||
.setAction(Const.Key.BROADCAST_REBOOT)
|
.setAction(Const.Key.BROADCAST_REBOOT)
|
||||||
val pendingIntent = PendingIntent.getBroadcast(context,
|
val pendingIntent = PendingIntent.getBroadcast(context,
|
||||||
Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
|
||||||
val builder = NotificationCompat.Builder(context, Const.ID.UPDATE_NOTIFICATION_CHANNEL)
|
val builder = updateBuilder(context)
|
||||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
.setContentTitle(context.getString(R.string.dtbo_patched_title))
|
||||||
.setContentTitle(context.getString(R.string.dtbo_patched_title))
|
.setContentText(context.getString(R.string.dtbo_patched_reboot))
|
||||||
.setContentText(context.getString(R.string.dtbo_patched_reboot))
|
|
||||||
.setVibrate(longArrayOf(0, 100, 100, 100))
|
if (SDK_INT >= 23) {
|
||||||
.addAction(R.drawable.ic_refresh, context.getString(R.string.reboot), pendingIntent)
|
val action = Notification.Action.Builder(
|
||||||
|
context.getBitmap(R.drawable.ic_refresh).toIcon(),
|
||||||
|
context.getString(R.string.reboot), pendingIntent).build()
|
||||||
|
builder.addAction(action)
|
||||||
|
} else {
|
||||||
|
builder.addAction(
|
||||||
|
R.drawable.ic_refresh,
|
||||||
|
context.getString(R.string.reboot), pendingIntent)
|
||||||
|
}
|
||||||
|
|
||||||
mgr.notify(Const.ID.DTBO_NOTIFICATION_ID, builder.build())
|
mgr.notify(Const.ID.DTBO_NOTIFICATION_ID, builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun progress(context: Context, title: CharSequence): NotificationCompat.Builder {
|
fun progress(context: Context, title: CharSequence): Notification.Builder {
|
||||||
val builder = NotificationCompat.Builder(context, Const.ID.PROGRESS_NOTIFICATION_CHANNEL)
|
val builder = if (SDK_INT >= 26) {
|
||||||
builder.setPriority(NotificationCompat.PRIORITY_LOW)
|
Notification.Builder(context, PROGRESS_NOTIFICATION_CHANNEL)
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
} else {
|
||||||
.setContentTitle(title)
|
Notification.Builder(context).setPriority(Notification.PRIORITY_LOW)
|
||||||
.setProgress(0, 0, true)
|
}
|
||||||
.setOngoing(true)
|
builder.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setProgress(0, 0, true)
|
||||||
|
.setOngoing(true)
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,65 +7,92 @@ import android.content.pm.ShortcutManager
|
|||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
|
import androidx.core.graphics.drawable.toAdaptiveIcon
|
||||||
|
import androidx.core.graphics.drawable.toIcon
|
||||||
import com.topjohnwu.magisk.*
|
import com.topjohnwu.magisk.*
|
||||||
|
import com.topjohnwu.magisk.extensions.getBitmap
|
||||||
import com.topjohnwu.magisk.ui.SplashActivity
|
import com.topjohnwu.magisk.ui.SplashActivity
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
|
|
||||||
object Shortcuts {
|
object Shortcuts {
|
||||||
|
|
||||||
fun setup(context: Context) {
|
fun setup(context: Context) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
if (Build.VERSION.SDK_INT >= 25) {
|
||||||
val manager = context.getSystemService(ShortcutManager::class.java)
|
val manager = context.getSystemService<ShortcutManager>()
|
||||||
manager?.dynamicShortcuts = getShortCuts(context)
|
manager?.dynamicShortcuts = getShortCuts(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
|
@RequiresApi(api = 25)
|
||||||
private fun getShortCuts(context: Context): List<ShortcutInfo> {
|
private fun getShortCuts(context: Context): List<ShortcutInfo> {
|
||||||
val shortCuts = mutableListOf<ShortcutInfo>()
|
val shortCuts = mutableListOf<ShortcutInfo>()
|
||||||
val root = Shell.rootAccess()
|
val intent = context.intent<SplashActivity>()
|
||||||
|
|
||||||
|
fun getIcon(id: Int): Icon {
|
||||||
|
return if (Build.VERSION.SDK_INT >= 26)
|
||||||
|
context.getBitmap(id).toAdaptiveIcon()
|
||||||
|
else
|
||||||
|
context.getBitmap(id).toIcon()
|
||||||
|
}
|
||||||
|
|
||||||
if (Utils.showSuperUser()) {
|
if (Utils.showSuperUser()) {
|
||||||
shortCuts.add(ShortcutInfo.Builder(context, "superuser")
|
shortCuts.add(
|
||||||
|
ShortcutInfo.Builder(context, "superuser")
|
||||||
.setShortLabel(context.getString(R.string.superuser))
|
.setShortLabel(context.getString(R.string.superuser))
|
||||||
.setIntent(Intent(context, ClassMap[SplashActivity::class.java])
|
.setIntent(
|
||||||
|
Intent(intent)
|
||||||
.putExtra(Const.Key.OPEN_SECTION, "superuser")
|
.putExtra(Const.Key.OPEN_SECTION, "superuser")
|
||||||
.setAction(Intent.ACTION_VIEW)
|
.setAction(Intent.ACTION_VIEW)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_superuser))
|
)
|
||||||
|
.setIcon(getIcon(R.drawable.sc_superuser))
|
||||||
.setRank(0)
|
.setRank(0)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (root && Config.magiskHide) {
|
if (Info.env.magiskHide) {
|
||||||
shortCuts.add(ShortcutInfo.Builder(context, "magiskhide")
|
shortCuts.add(
|
||||||
|
ShortcutInfo.Builder(context, "magiskhide")
|
||||||
.setShortLabel(context.getString(R.string.magiskhide))
|
.setShortLabel(context.getString(R.string.magiskhide))
|
||||||
.setIntent(Intent(context, ClassMap[SplashActivity::class.java])
|
.setIntent(
|
||||||
|
Intent(intent)
|
||||||
.putExtra(Const.Key.OPEN_SECTION, "magiskhide")
|
.putExtra(Const.Key.OPEN_SECTION, "magiskhide")
|
||||||
.setAction(Intent.ACTION_VIEW)
|
.setAction(Intent.ACTION_VIEW)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_magiskhide))
|
)
|
||||||
|
.setIcon(getIcon(R.drawable.sc_magiskhide))
|
||||||
.setRank(1)
|
.setRank(1)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (!Config.coreOnly && root && Info.magiskVersionCode >= 0) {
|
if (!Config.coreOnly && Info.env.isActive) {
|
||||||
shortCuts.add(ShortcutInfo.Builder(context, "modules")
|
shortCuts.add(
|
||||||
|
ShortcutInfo.Builder(context, "modules")
|
||||||
.setShortLabel(context.getString(R.string.modules))
|
.setShortLabel(context.getString(R.string.modules))
|
||||||
.setIntent(Intent(context, ClassMap[SplashActivity::class.java])
|
.setIntent(
|
||||||
|
Intent(intent)
|
||||||
.putExtra(Const.Key.OPEN_SECTION, "modules")
|
.putExtra(Const.Key.OPEN_SECTION, "modules")
|
||||||
.setAction(Intent.ACTION_VIEW)
|
.setAction(Intent.ACTION_VIEW)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_extension))
|
)
|
||||||
|
.setIcon(getIcon(R.drawable.sc_extension))
|
||||||
.setRank(3)
|
.setRank(3)
|
||||||
.build())
|
.build()
|
||||||
shortCuts.add(ShortcutInfo.Builder(context, "downloads")
|
)
|
||||||
|
shortCuts.add(
|
||||||
|
ShortcutInfo.Builder(context, "downloads")
|
||||||
.setShortLabel(context.getString(R.string.downloads))
|
.setShortLabel(context.getString(R.string.downloads))
|
||||||
.setIntent(Intent(context, ClassMap[SplashActivity::class.java])
|
.setIntent(
|
||||||
|
Intent(intent)
|
||||||
.putExtra(Const.Key.OPEN_SECTION, "downloads")
|
.putExtra(Const.Key.OPEN_SECTION, "downloads")
|
||||||
.setAction(Intent.ACTION_VIEW)
|
.setAction(Intent.ACTION_VIEW)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
.setIcon(Icon.createWithResource(context, R.drawable.sc_cloud_download))
|
)
|
||||||
|
.setIcon(getIcon(R.drawable.sc_cloud_download))
|
||||||
.setRank(2)
|
.setRank(2)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return shortCuts
|
return shortCuts
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,7 @@ class EnvFixDialog(activity: Activity) : CustomAlertDialog(activity) {
|
|||||||
setTitle(R.string.env_fix_title)
|
setTitle(R.string.env_fix_title)
|
||||||
setMessage(R.string.env_fix_msg)
|
setMessage(R.string.env_fix_msg)
|
||||||
setCancelable(true)
|
setCancelable(true)
|
||||||
setPositiveButton(R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
val pd = ProgressDialog.show(activity,
|
val pd = ProgressDialog.show(activity,
|
||||||
activity.getString(R.string.setup_title),
|
activity.getString(R.string.setup_title),
|
||||||
activity.getString(R.string.setup_msg))
|
activity.getString(R.string.setup_msg))
|
||||||
@@ -46,6 +46,6 @@ class EnvFixDialog(activity: Activity) : CustomAlertDialog(activity) {
|
|||||||
}
|
}
|
||||||
}.exec()
|
}.exec()
|
||||||
}
|
}
|
||||||
setNegativeButton(R.string.no_thanks, null)
|
setNegativeButton(android.R.string.no, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,88 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.view.dialogs
|
|
||||||
|
|
||||||
import android.annotation.TargetApi
|
|
||||||
import android.app.Activity
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.hardware.fingerprint.FingerprintManager
|
|
||||||
import android.os.Build
|
|
||||||
import android.view.Gravity
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.utils.FingerprintHelper
|
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.M)
|
|
||||||
class FingerprintAuthDialog(activity: Activity, private val callback: () -> Unit)
|
|
||||||
: CustomAlertDialog(activity) {
|
|
||||||
|
|
||||||
private var failureCallback: (() -> Unit)? = null
|
|
||||||
private var helper: DialogFingerprintHelper? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
val fingerprint = ContextCompat.getDrawable(activity, R.drawable.ic_fingerprint)
|
|
||||||
fingerprint?.setBounds(0, 0, Utils.dpInPx(50), Utils.dpInPx(50))
|
|
||||||
val theme = activity.theme
|
|
||||||
val ta = theme.obtainStyledAttributes(intArrayOf(R.attr.imageColorTint))
|
|
||||||
fingerprint?.setTint(ta.getColor(0, Color.GRAY))
|
|
||||||
ta.recycle()
|
|
||||||
binding.message.setCompoundDrawables(null, null, null, fingerprint)
|
|
||||||
binding.message.compoundDrawablePadding = Utils.dpInPx(20)
|
|
||||||
binding.message.gravity = Gravity.CENTER
|
|
||||||
setMessage(R.string.auth_fingerprint)
|
|
||||||
setNegativeButton(R.string.close) { _, _ ->
|
|
||||||
helper?.cancel()
|
|
||||||
failureCallback?.invoke()
|
|
||||||
}
|
|
||||||
setOnCancelListener {
|
|
||||||
helper?.cancel()
|
|
||||||
failureCallback?.invoke()
|
|
||||||
}
|
|
||||||
runCatching {
|
|
||||||
helper = DialogFingerprintHelper()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(activity: Activity, onSuccess: () -> Unit, onFailure: () -> Unit)
|
|
||||||
: this(activity, onSuccess) {
|
|
||||||
failureCallback = onFailure
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun show(): AlertDialog {
|
|
||||||
return create().apply {
|
|
||||||
if (helper == null) {
|
|
||||||
dismiss()
|
|
||||||
Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT)
|
|
||||||
} else {
|
|
||||||
helper?.authenticate()
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal inner class DialogFingerprintHelper @Throws(Exception::class)
|
|
||||||
constructor() : FingerprintHelper() {
|
|
||||||
|
|
||||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
|
||||||
binding.message.setTextColor(Color.RED)
|
|
||||||
binding.message.text = errString
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence) {
|
|
||||||
binding.message.setTextColor(Color.RED)
|
|
||||||
binding.message.text = helpString
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationFailed() {
|
|
||||||
binding.message.setTextColor(Color.RED)
|
|
||||||
binding.message.setText(R.string.auth_fail)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
|
|
||||||
dismiss()
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -62,12 +62,12 @@ internal class InstallMethodDialog(activity: BaseActivity<*, *>, options: List<S
|
|||||||
.setTitle(R.string.warning)
|
.setTitle(R.string.warning)
|
||||||
.setMessage(R.string.install_inactive_slot_msg)
|
.setMessage(R.string.install_inactive_slot_msg)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.setPositiveButton(R.string.yes) { _, _ ->
|
.setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
DownloadService(activity) {
|
DownloadService(activity) {
|
||||||
subject = DownloadSubject.Magisk(Configuration.Flash.Secondary)
|
subject = DownloadSubject.Magisk(Configuration.Flash.Secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.no_thanks, null)
|
.setNegativeButton(android.R.string.no, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/su_request_background" />
|
<background android:drawable="@color/light" />
|
||||||
<foreground>
|
<foreground>
|
||||||
<inset
|
<inset
|
||||||
android:drawable="@drawable/ic_cloud_download"
|
android:drawable="@drawable/ic_cloud_download"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/su_request_background" />
|
<background android:drawable="@color/light" />
|
||||||
<foreground>
|
<foreground>
|
||||||
<inset
|
<inset
|
||||||
android:drawable="@drawable/ic_extension"
|
android:drawable="@drawable/ic_extension"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/su_request_background" />
|
<background android:drawable="@color/light" />
|
||||||
<foreground>
|
<foreground>
|
||||||
<inset
|
<inset
|
||||||
android:drawable="@drawable/ic_magiskhide"
|
android:drawable="@drawable/ic_magiskhide"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/su_request_background" />
|
<background android:drawable="@color/light" />
|
||||||
<foreground>
|
<foreground>
|
||||||
<inset
|
<inset
|
||||||
android:drawable="@drawable/ic_superuser"
|
android:drawable="@drawable/ic_superuser"
|
||||||
|
@@ -4,6 +4,6 @@
|
|||||||
android:viewportWidth="24.0"
|
android:viewportWidth="24.0"
|
||||||
android:viewportHeight="24.0">
|
android:viewportHeight="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/primary_dark"
|
android:fillColor="@color/dark"
|
||||||
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z"/>
|
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
@@ -4,6 +4,6 @@
|
|||||||
android:viewportHeight="24.0"
|
android:viewportHeight="24.0"
|
||||||
android:viewportWidth="24.0">
|
android:viewportWidth="24.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/primary_dark"
|
android:fillColor="@color/dark"
|
||||||
android:pathData="M20.5,11H19V7c0,-1.1 -0.9,-2 -2,-2h-4V3.5C13,2.12 11.88,1 10.5,1S8,2.12 8,3.5V5H4c-1.1,0 -1.99,0.9 -1.99,2v3.8H3.5c1.49,0 2.7,1.21 2.7,2.7s-1.21,2.7 -2.7,2.7H2V20c0,1.1 0.9,2 2,2h3.8v-1.5c0,-1.49 1.21,-2.7 2.7,-2.7 1.49,0 2.7,1.21 2.7,2.7V22H17c1.1,0 2,-0.9 2,-2v-4h1.5c1.38,0 2.5,-1.12 2.5,-2.5S21.88,11 20.5,11z"/>
|
android:pathData="M20.5,11H19V7c0,-1.1 -0.9,-2 -2,-2h-4V3.5C13,2.12 11.88,1 10.5,1S8,2.12 8,3.5V5H4c-1.1,0 -1.99,0.9 -1.99,2v3.8H3.5c1.49,0 2.7,1.21 2.7,2.7s-1.21,2.7 -2.7,2.7H2V20c0,1.1 0.9,2 2,2h3.8v-1.5c0,-1.49 1.21,-2.7 2.7,-2.7 1.49,0 2.7,1.21 2.7,2.7V22H17c1.1,0 2,-0.9 2,-2v-4h1.5c1.38,0 2.5,-1.12 2.5,-2.5S21.88,11 20.5,11z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
@@ -5,6 +5,6 @@
|
|||||||
android:width="24dp">
|
android:width="24dp">
|
||||||
<path
|
<path
|
||||||
android:fillAlpha="1.00"
|
android:fillAlpha="1.00"
|
||||||
android:fillColor="@color/primary_dark"
|
android:fillColor="@color/dark"
|
||||||
android:pathData="M200.6,24.2C231.8,24.2 263,33 289.6,49.5C304.7,58.8 318.2,70.7 329.8,84.1C351.5,109.6 365.2,141.7 368.8,175C373.6,217.4 361.5,261.5 335.5,295.5C334.2,297.1 332.8,298.7 331.9,300.7C341.7,310.1 351,320 360.9,329.3C322.7,339.3 284.5,349.6 246.3,359.9C256.4,323.5 266,287 275.7,250.5C262.4,250.5 249.1,250.5 235.8,250.5C228.5,269.8 221.2,289.1 214.1,308.4C205.9,308.6 197.8,308.5 189.6,308.5C188.4,306.7 187.1,304.9 185.9,303C192.4,285.5 199.1,268 205.6,250.5C189.5,250.6 173.4,250.3 157.4,250.6C150.4,270 142.9,289.2 135.8,308.5C129.8,308.5 123.9,308.4 117.9,308.6C130.4,317.8 144,325.6 158.9,330.3C171.9,334.6 185.6,336.6 199.4,336.7C199.4,349.4 199.3,362.1 199.4,374.8C165.8,374.9 132.2,364.5 104.4,345.6C91.7,337 80.3,326.5 70.2,314.9C48.5,289.4 34.8,257.3 31.2,224C26.3,180.4 39.2,134.9 66.8,100.7C67.2,99.9 67.7,99.2 68.1,98.4C58.3,88.9 49,79 39.1,69.7C77.3,59.7 115.6,49.4 153.7,39.1C143.6,75.7 133.8,112.5 124.1,149.3C137.9,149.3 151.7,149.2 165.5,149.3C172.9,130 180.2,110.6 187.3,91.2C195.5,90.8 203.7,91 211.8,91C213,92.8 214.3,94.6 215.5,96.3C209.1,114 202.3,131.6 195.8,149.2C211.8,149.3 227.8,149.2 243.9,149.3C251.2,129.9 258.3,110.3 265.8,90.9C271.5,90.8 277.3,91.6 282.9,90.4C280.2,89.5 278.1,87.7 275.9,86.1C254,70.7 227.4,62.2 200.6,62.3C200.6,49.6 200.7,36.9 200.6,24.2M292.5,100C286.4,116.4 280.2,132.8 274,149.3C280.9,149.2 287.8,149.3 294.7,149.2C296,150.8 297.3,152.4 298.6,153.9C297.1,162.1 295.7,170.3 294.3,178.5C283.9,178.5 273.4,178.5 262.9,178.5C257.5,192.7 252.1,206.9 246.9,221.2C258.7,221.3 270.5,221.1 282.3,221.3C283.5,222.9 284.8,224.4 286.1,225.9C284.9,233.7 283.4,241.4 282.1,249.1C281.6,251 283.6,252.1 284.6,253.3C291.5,259.8 297.7,266.9 304.8,273.1C312.9,262.1 319.6,250.1 324.2,237.3C334.3,208.7 334.2,176.7 323.7,148.3C317,130.1 306.4,113.4 292.5,100M95.2,125.9C86.2,138 79,151.4 74.5,165.8C65.3,194.4 66.4,226.3 77.6,254.2C84.6,271.5 95.1,287.4 108.7,300.1C114.9,283.6 121.3,267.1 127.2,250.5C121.5,250.5 115.8,250.5 110,250.5C108.6,250.4 106.7,251 105.8,249.5C104.5,247.9 102.3,246.3 102.9,244.1C104.2,236.5 105.5,228.9 106.8,221.2C117.4,221.2 128.1,221.4 138.7,221.1C143.9,206.9 149.3,192.7 154.6,178.5C142.9,178.4 131.1,178.6 119.3,178.4C117.6,177.3 116.4,175.3 115,173.8C116.3,165.8 117.9,157.9 119,150C118.3,148.2 116.6,147.1 115.3,145.7C108.6,139.2 102.3,132.1 95.2,125.9M168.8,221.2C184.8,221.2 200.8,221.3 216.8,221.2C222.2,207 227.5,192.8 232.8,178.5C216.8,178.5 200.7,178.5 184.6,178.5C179.4,192.8 174,207 168.8,221.2Z"/>
|
android:pathData="M200.6,24.2C231.8,24.2 263,33 289.6,49.5C304.7,58.8 318.2,70.7 329.8,84.1C351.5,109.6 365.2,141.7 368.8,175C373.6,217.4 361.5,261.5 335.5,295.5C334.2,297.1 332.8,298.7 331.9,300.7C341.7,310.1 351,320 360.9,329.3C322.7,339.3 284.5,349.6 246.3,359.9C256.4,323.5 266,287 275.7,250.5C262.4,250.5 249.1,250.5 235.8,250.5C228.5,269.8 221.2,289.1 214.1,308.4C205.9,308.6 197.8,308.5 189.6,308.5C188.4,306.7 187.1,304.9 185.9,303C192.4,285.5 199.1,268 205.6,250.5C189.5,250.6 173.4,250.3 157.4,250.6C150.4,270 142.9,289.2 135.8,308.5C129.8,308.5 123.9,308.4 117.9,308.6C130.4,317.8 144,325.6 158.9,330.3C171.9,334.6 185.6,336.6 199.4,336.7C199.4,349.4 199.3,362.1 199.4,374.8C165.8,374.9 132.2,364.5 104.4,345.6C91.7,337 80.3,326.5 70.2,314.9C48.5,289.4 34.8,257.3 31.2,224C26.3,180.4 39.2,134.9 66.8,100.7C67.2,99.9 67.7,99.2 68.1,98.4C58.3,88.9 49,79 39.1,69.7C77.3,59.7 115.6,49.4 153.7,39.1C143.6,75.7 133.8,112.5 124.1,149.3C137.9,149.3 151.7,149.2 165.5,149.3C172.9,130 180.2,110.6 187.3,91.2C195.5,90.8 203.7,91 211.8,91C213,92.8 214.3,94.6 215.5,96.3C209.1,114 202.3,131.6 195.8,149.2C211.8,149.3 227.8,149.2 243.9,149.3C251.2,129.9 258.3,110.3 265.8,90.9C271.5,90.8 277.3,91.6 282.9,90.4C280.2,89.5 278.1,87.7 275.9,86.1C254,70.7 227.4,62.2 200.6,62.3C200.6,49.6 200.7,36.9 200.6,24.2M292.5,100C286.4,116.4 280.2,132.8 274,149.3C280.9,149.2 287.8,149.3 294.7,149.2C296,150.8 297.3,152.4 298.6,153.9C297.1,162.1 295.7,170.3 294.3,178.5C283.9,178.5 273.4,178.5 262.9,178.5C257.5,192.7 252.1,206.9 246.9,221.2C258.7,221.3 270.5,221.1 282.3,221.3C283.5,222.9 284.8,224.4 286.1,225.9C284.9,233.7 283.4,241.4 282.1,249.1C281.6,251 283.6,252.1 284.6,253.3C291.5,259.8 297.7,266.9 304.8,273.1C312.9,262.1 319.6,250.1 324.2,237.3C334.3,208.7 334.2,176.7 323.7,148.3C317,130.1 306.4,113.4 292.5,100M95.2,125.9C86.2,138 79,151.4 74.5,165.8C65.3,194.4 66.4,226.3 77.6,254.2C84.6,271.5 95.1,287.4 108.7,300.1C114.9,283.6 121.3,267.1 127.2,250.5C121.5,250.5 115.8,250.5 110,250.5C108.6,250.4 106.7,251 105.8,249.5C104.5,247.9 102.3,246.3 102.9,244.1C104.2,236.5 105.5,228.9 106.8,221.2C117.4,221.2 128.1,221.4 138.7,221.1C143.9,206.9 149.3,192.7 154.6,178.5C142.9,178.4 131.1,178.6 119.3,178.4C117.6,177.3 116.4,175.3 115,173.8C116.3,165.8 117.9,157.9 119,150C118.3,148.2 116.6,147.1 115.3,145.7C108.6,139.2 102.3,132.1 95.2,125.9M168.8,221.2C184.8,221.2 200.8,221.3 216.8,221.2C222.2,207 227.5,192.8 232.8,178.5C216.8,178.5 200.7,178.5 184.6,178.5C179.4,192.8 174,207 168.8,221.2Z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
@@ -4,6 +4,6 @@
|
|||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/primary_dark"
|
android:fillColor="@color/dark"
|
||||||
android:pathData="M5.41,21L6.12,17H2.12L2.47,15H6.47L7.53,9H3.53L3.88,7H7.88L8.59,3H10.59L9.88,7H15.88L16.59,3H18.59L17.88,7H21.88L21.53,9H17.53L16.47,15H20.47L20.12,17H16.12L15.41,21H13.41L14.12,17H8.12L7.41,21H5.41M9.53,9L8.47,15H14.47L15.53,9H9.53Z" />
|
android:pathData="M5.41,21L6.12,17H2.12L2.47,15H6.47L7.53,9H3.53L3.88,7H7.88L8.59,3H10.59L9.88,7H15.88L16.59,3H18.59L17.88,7H21.88L21.53,9H17.53L16.47,15H20.47L20.12,17H16.12L15.41,21H13.41L14.12,17H8.12L7.41,21H5.41M9.53,9L8.47,15H14.47L15.53,9H9.53Z" />
|
||||||
</vector>
|
</vector>
|
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/main_nav_host"
|
android:id="@+id/main_nav_host"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
@@ -129,24 +129,14 @@
|
|||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/grant_btn"
|
android:id="@+id/grant_btn"
|
||||||
style="@style/Widget.Button.Text"
|
style="@style/Widget.Button.Text"
|
||||||
gone="@{viewModel.canUseFingerprint}"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:onClick="@{() -> viewModel.grantPressed()}"
|
android:onClick="@{() -> viewModel.grantPressed()}"
|
||||||
android:text="@string/grant" />
|
android:text="@string/grant" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:id="@+id/fingerprint"
|
|
||||||
gone="@{!viewModel.canUseFingerprint}"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:padding="7dp"
|
|
||||||
android:tint="?attr/colorAccent"
|
|
||||||
app:srcCompat="@drawable/ic_fingerprint" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</layout>
|
</layout>
|
||||||
|
46
app/src/main/res/layout/dialog_custom_name.xml
Normal file
46
app/src/main/res/layout/dialog_custom_name.xml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<import type="com.topjohnwu.magisk.extensions.XStringKt" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="data"
|
||||||
|
type="com.topjohnwu.magisk.ui.settings.SettingsFragment.ManagerNameData" />
|
||||||
|
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="@dimen/margin_generic"
|
||||||
|
android:paddingTop="@dimen/margin_generic"
|
||||||
|
android:paddingEnd="@dimen/margin_generic">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/dialog_name_input"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/settings_app_name_hint"
|
||||||
|
app:counterEnabled="true"
|
||||||
|
app:counterMaxLength="14"
|
||||||
|
app:counterOverflowTextColor="@color/colorError"
|
||||||
|
app:error="@{data.name.length() > 14 || data.name.empty || XStringKt.isEmptyInternal(data.name) ? @string/settings_app_name_error : @string/empty}"
|
||||||
|
app:errorEnabled="true"
|
||||||
|
app:helperText="@string/settings_app_name_helper">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@={data.name}"
|
||||||
|
tools:text="@string/re_app_name" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</layout>
|
@@ -15,8 +15,6 @@
|
|||||||
|
|
||||||
<import type="com.topjohnwu.magisk.R" />
|
<import type="com.topjohnwu.magisk.R" />
|
||||||
|
|
||||||
<import type="com.topjohnwu.magisk.extensions.XStringKt" />
|
|
||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="viewModel"
|
name="viewModel"
|
||||||
type="com.topjohnwu.magisk.ui.home.HomeViewModel" />
|
type="com.topjohnwu.magisk.ui.home.HomeViewModel" />
|
||||||
@@ -203,7 +201,7 @@
|
|||||||
|
|
||||||
<View
|
<View
|
||||||
style="@style/Widget.Divider.Horizontal"
|
style="@style/Widget.Divider.Horizontal"
|
||||||
gone="@{!viewModel.hasRoot || !viewModel.isConnected}"
|
gone="@{!viewModel.isActive || !viewModel.isConnected}"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_margin="@dimen/margin_generic" />
|
android:layout_margin="@dimen/margin_generic" />
|
||||||
|
|
||||||
@@ -239,7 +237,7 @@
|
|||||||
android:layout_marginRight="@dimen/margin_generic"
|
android:layout_marginRight="@dimen/margin_generic"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:text="@{XStringKt.res(viewModel.safetyNetTitle)}"
|
android:text="@{viewModel.safetyNetTitle}"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:autoSizeMaxTextSize="14sp"
|
app:autoSizeMaxTextSize="14sp"
|
||||||
app:autoSizeTextType="uniform"
|
app:autoSizeTextType="uniform"
|
||||||
@@ -484,13 +482,13 @@
|
|||||||
|
|
||||||
<View
|
<View
|
||||||
style="@style/Widget.Divider.Horizontal"
|
style="@style/Widget.Divider.Horizontal"
|
||||||
gone="@{!viewModel.hasRoot}"
|
gone="@{!viewModel.isActive}"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_margin="@dimen/margin_generic" />
|
android:layout_margin="@dimen/margin_generic" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
style="@style/Widget.Button.Text"
|
style="@style/Widget.Button.Text"
|
||||||
gone="@{!viewModel.hasRoot}"
|
gone="@{!viewModel.isActive}"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:onClick="@{() -> viewModel.uninstallPressed()}"
|
android:onClick="@{() -> viewModel.uninstallPressed()}"
|
||||||
|
@@ -59,10 +59,12 @@
|
|||||||
android:layout_gravity="bottom|center_horizontal"
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
android:layout_margin="@dimen/fab_padding"
|
android:layout_margin="@dimen/fab_padding"
|
||||||
android:onClick="@{() -> viewModel.fabPressed()}"
|
android:onClick="@{() -> viewModel.fabPressed()}"
|
||||||
|
android:focusable="true"
|
||||||
|
android:clickable="true"
|
||||||
app:fabSize="normal"
|
app:fabSize="normal"
|
||||||
app:layout_behavior="com.google.android.material.floatingactionbutton.FloatingActionButton$Behavior"
|
app:layout_behavior="com.google.android.material.floatingactionbutton.FloatingActionButton$Behavior"
|
||||||
app:srcCompat="@drawable/ic_add" />
|
app:srcCompat="@drawable/ic_add" />
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
</layout>
|
</layout>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user