mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-15 22:47:26 +00:00
Compare commits
136 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
97c1e181c5 | ||
![]() |
ea80cddd57 | ||
![]() |
09a294c219 | ||
![]() |
408399eae0 | ||
![]() |
391852a102 | ||
![]() |
5b37de8fe5 | ||
![]() |
7df23ceb74 | ||
![]() |
6099f3b015 | ||
![]() |
a5cc31783c | ||
![]() |
6b34ec3ab9 | ||
![]() |
5c333dec33 | ||
![]() |
775d095b3c | ||
![]() |
7679b5d516 | ||
![]() |
7702094053 | ||
![]() |
3798d50457 | ||
![]() |
95e1e57407 | ||
![]() |
93ba4cca68 | ||
![]() |
fe4981da21 | ||
![]() |
e4f94c4c52 | ||
![]() |
708fe514f8 | ||
![]() |
11c882380f | ||
![]() |
fb93af665d | ||
![]() |
0db405f2cc | ||
![]() |
fb8000b58b | ||
![]() |
1b9d8e068a | ||
![]() |
038f73a5f7 | ||
![]() |
649b49ff45 | ||
![]() |
1418bc454d | ||
![]() |
29cc372bfa | ||
![]() |
69b00d3782 | ||
![]() |
a328e2bf3c | ||
![]() |
4c1ea0e421 | ||
![]() |
7e01f9c95e | ||
![]() |
8b28baabd7 | ||
![]() |
f49966d86e | ||
![]() |
f4ac7c8e7c | ||
![]() |
2b65e1ffc2 | ||
![]() |
c81a3fa286 | ||
![]() |
44f005077d | ||
![]() |
013b6e68ec | ||
![]() |
95c964673d | ||
![]() |
94ec11db58 | ||
![]() |
c4e8dda37c | ||
![]() |
e136fb3a4f | ||
![]() |
01b985eded | ||
![]() |
1f0a35f073 | ||
![]() |
2b9b019093 | ||
![]() |
10186a9e3d | ||
![]() |
89d8fea7d2 | ||
![]() |
f623b98858 | ||
![]() |
632cee1613 | ||
![]() |
c0f2164bc5 | ||
![]() |
f6e4a27fdd | ||
![]() |
257ceb99f7 | ||
![]() |
706d53065b | ||
![]() |
0f95a7babe | ||
![]() |
7cb2806878 | ||
![]() |
9c0e18975c | ||
![]() |
3da318b48e | ||
![]() |
dfe1f2c108 | ||
![]() |
f42a87b51a | ||
![]() |
ab25857176 | ||
![]() |
7da36079c1 | ||
![]() |
2bef967af1 | ||
![]() |
7e4194418a | ||
![]() |
aa02057895 | ||
![]() |
fb8dc07599 | ||
![]() |
66e30a7723 | ||
![]() |
0298ab99c4 | ||
![]() |
d11358671e | ||
![]() |
8b5cb4c7b0 | ||
![]() |
aad52ae743 | ||
![]() |
8ddab84745 | ||
![]() |
6865652125 | ||
![]() |
ed4d0867e8 | ||
![]() |
1c71e02454 | ||
![]() |
f332e87cab | ||
![]() |
023dbc6cb5 | ||
![]() |
4dd3f55407 | ||
![]() |
7b9a71c9af | ||
![]() |
901d22cdfa | ||
![]() |
93e1266ee7 | ||
![]() |
0a4e7eea41 | ||
![]() |
e3801d6965 | ||
![]() |
336f1687c1 | ||
![]() |
d4e2f2df6e | ||
![]() |
f152b4c26e | ||
![]() |
bd935b0553 | ||
![]() |
a9b3b7a359 | ||
![]() |
7a007b342a | ||
![]() |
0783f3d5b6 | ||
![]() |
afe3c2bc1b | ||
![]() |
82f8948fd4 | ||
![]() |
b9cdc755d1 | ||
![]() |
a6f81c66e5 | ||
![]() |
1ff45ac5f5 | ||
![]() |
48bde7375f | ||
![]() |
0601fa3b3d | ||
![]() |
d0d3c8dbfd | ||
![]() |
8057de1973 | ||
![]() |
43c1105d62 | ||
![]() |
6adf516b30 | ||
![]() |
bf80b08b5f | ||
![]() |
3e0b1df46d | ||
![]() |
84811c80b6 | ||
![]() |
45e0df9c57 | ||
![]() |
bc51ce7c7b | ||
![]() |
b693d13b93 | ||
![]() |
39982d57ef | ||
![]() |
15e27e54fb | ||
![]() |
851404205b | ||
![]() |
117ae71025 | ||
![]() |
027ec70262 | ||
![]() |
55fdee4d65 | ||
![]() |
0d42f937dd | ||
![]() |
ac8372dd26 | ||
![]() |
5e56a6bbee | ||
![]() |
3c6c409df0 | ||
![]() |
d05408c89f | ||
![]() |
ee0ec3fbfa | ||
![]() |
122a73e086 | ||
![]() |
29a9b18c4c | ||
![]() |
1561272109 | ||
![]() |
3e61ab0d25 | ||
![]() |
a49dc6ccb7 | ||
![]() |
60f3d62f00 | ||
![]() |
e613855a4f | ||
![]() |
22662d7e03 | ||
![]() |
6e7e5be1a2 | ||
![]() |
8b2ab778c9 | ||
![]() |
35f3766ecf | ||
![]() |
995304dabb | ||
![]() |
803982a271 | ||
![]() |
9164bf22c2 | ||
![]() |
911a576893 | ||
![]() |
79ee85c0f9 |
13
.github/ISSUE_TEMPLATE/bug_report.md
vendored
13
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -7,15 +7,17 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
## READ BEFORE OPENING ISSUES
|
||||
|
||||
All bug reports require you to **USE CANARY BUILDS**. Please include the version name and version code in the bug report.
|
||||
|
||||
If you experience a bootloop, attach a `dmesg` (kernel logs) when the device refuse to boot. This may very likely require a custom kernel on some devices as `last_kmsg` or `pstore ramoops` are usually not enabled by default. In addition, please also upload the result of `cat /proc/mounts` when your device is working correctly **WITHOUT ROOT**.
|
||||
|
||||
If you experience issues during installation, in recovery, upload the recovery logs, or in Magisk Manager, upload the install logs. Please also upload the `boot.img` or `recovery.img` that you are using for patching.
|
||||
If you experience issues during installation, in recovery, upload the recovery logs, or in Magisk, upload the install logs. Please also upload the `boot.img` or `recovery.img` that you are using for patching.
|
||||
|
||||
If you experience a crash of Magisk Manager, dump the full `logcat` **when the crash happens**. **DO NOT** upload `magisk.log`.
|
||||
If you experience a crash of Magisk app, dump the full `logcat` **when the crash happens**.
|
||||
|
||||
If you experience other issues related to Magisk, upload `magisk.log`, and preferably also include a boot `logcat` (start dumping `logcat` when the device boots up)
|
||||
|
||||
@@ -26,3 +28,10 @@ If you experience other issues related to Magisk, upload `magisk.log`, and prefe
|
||||
**DO NOT** report issues if you have any modules installed.
|
||||
|
||||
Without following the rules above, your issue will be closed without explanation.
|
||||
|
||||
-->
|
||||
|
||||
Device:
|
||||
Android version:
|
||||
Magisk version name:
|
||||
Magisk version code:
|
||||
|
6
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
6
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: XDA Community Support
|
||||
url: https://forum.xda-developers.com/f/magisk.5903/
|
||||
about: Please ask and answer questions here.
|
||||
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -78,6 +78,10 @@ jobs:
|
||||
- name: Build release
|
||||
run: python build.py -vr all
|
||||
|
||||
- name: Refresh flag
|
||||
run: touch gradle.properties
|
||||
shell: bash
|
||||
|
||||
- name: Build debug
|
||||
run: python build.py -v all
|
||||
|
||||
|
26
.github/workflows/issues.yml
vendored
Normal file
26
.github/workflows/issues.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Check Issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v2
|
||||
- name: Read latest version code
|
||||
run: |
|
||||
ver=$(sed -n 's/^magisk.versionCode=//p' gradle.properties)
|
||||
echo MAGISK_VERSION_CODE=$ver >> $GITHUB_ENV
|
||||
- if: contains(github.event.issue.body, format('Magisk version code{0} ', ':')) != true
|
||||
id: close
|
||||
name: Close Issue(template)
|
||||
uses: peter-evans/close-issue@v1
|
||||
with:
|
||||
comment: This issue is being automatically closed because it does not follow the issue template.
|
||||
- if: steps.close.conclusion == 'skipped' && contains(github.event.issue.body, format('Magisk version code{0} {1}', ':', env.MAGISK_VERSION_CODE)) != true
|
||||
name: Close Issue(latest canary)
|
||||
uses: peter-evans/close-issue@v1
|
||||
with:
|
||||
comment: This issue is being automatically closed because latest canary Magisk version code is ${{ env.MAGISK_VERSION_CODE }}.
|
8
.gitmodules
vendored
8
.gitmodules
vendored
@@ -6,7 +6,7 @@
|
||||
url = https://github.com/topjohnwu/ndk-busybox.git
|
||||
[submodule "dtc"]
|
||||
path = native/jni/external/dtc
|
||||
url = https://github.com/dgibson/dtc
|
||||
url = https://github.com/dgibson/dtc.git
|
||||
[submodule "lz4"]
|
||||
path = native/jni/external/lz4
|
||||
url = https://github.com/lz4/lz4.git
|
||||
@@ -28,6 +28,12 @@
|
||||
[submodule "xhook"]
|
||||
path = native/jni/external/xhook
|
||||
url = https://github.com/iqiyi/xHook.git
|
||||
[submodule "libcxx"]
|
||||
path = native/jni/external/libcxx
|
||||
url = https://github.com/topjohnwu/libcxx.git
|
||||
[submodule "zlib"]
|
||||
path = native/jni/external/zlib
|
||||
url = https://android.googlesource.com/platform/external/zlib
|
||||
[submodule "termux-elf-cleaner"]
|
||||
path = tools/termux-elf-cleaner
|
||||
url = https://github.com/termux/termux-elf-cleaner.git
|
||||
|
33
README.MD
33
README.MD
@@ -1,27 +1,24 @@
|
||||

|
||||
|
||||

|
||||

|
||||
[](https://raw.githubusercontent.com/topjohnwu/magisk-files/count/count.json)
|
||||
|
||||
## Introduction
|
||||
|
||||
Magisk is a suite of open source tools for customizing Android, supporting devices higher than Android 4.2. It covers fundamental parts of Android customization: root, boot scripts, SELinux patches, AVB2.0 / dm-verity / forceencrypt removals etc.
|
||||
|
||||
Magisk is a suite of open source software for customizing Android, supporting devices higher than Android 5.0.<br>
|
||||
Here are some feature highlights:
|
||||
|
||||
- **MagiskSU**: Provide root access to your device
|
||||
- **MagiskSU**: Provide root access for applications
|
||||
- **Magisk Modules**: Modify read-only partitions by installing modules
|
||||
- **MagiskHide**: Hide Magisk from root detections / system integrity checks
|
||||
- **MagiskBoot**: The most complete tool for unpacking and repacking Android boot images
|
||||
|
||||
## Downloads
|
||||
|
||||
Please note that the only source of official Magisk information and downloads is [Github](https://github.com/topjohnwu/Magisk/). Beware of any other websites.
|
||||
[Github](https://github.com/topjohnwu/Magisk/) is the only source where you can get official Magisk information and downloads.
|
||||
|
||||
[](https://github.com/topjohnwu/Magisk/releases/download/manager-v8.0.7/MagiskManager-v8.0.7.apk)
|
||||
[](https://raw.githubusercontent.com/topjohnwu/magisk_files/canary/app-debug.apk)
|
||||
<br>
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v21.4)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v21.4)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v22.1)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v22.1)
|
||||
[](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-debug.apk)
|
||||
|
||||
## Useful Links
|
||||
|
||||
@@ -30,23 +27,13 @@ Please note that the only source of official Magisk information and downloads is
|
||||
- [Magisk Documentation](https://topjohnwu.github.io/Magisk/)
|
||||
- [Magisk Troubleshoot Wiki](https://www.didgeridoohan.com/magisk/HomePage) (by [@Didgeridoohan](https://github.com/Didgeridoohan))
|
||||
|
||||
## Android Version Support
|
||||
|
||||
- Android 4.2+: MagiskSU and Magisk Modules Only
|
||||
- Android 4.4+: All core features available
|
||||
- Android 6.0+: Guaranteed MagiskHide support
|
||||
- Android 7.0+: Full MagiskHide protection
|
||||
- Android 9.0+: Magisk Manager stealth mode
|
||||
|
||||
## Bug Reports
|
||||
|
||||
Canary Channels are cutting edge builds for those adventurous. To access canary builds, install the Canary Magisk Manager, switch to the Canary Channel in settings and upgrade.
|
||||
|
||||
**Only bug reports from Canary builds will be accepted.**
|
||||
|
||||
For installation issues, upload both boot image and install logs.<br>
|
||||
For Magisk issues, upload boot logcat or dmesg.<br>
|
||||
For Magisk Manager crashes, record and upload the logcat when the crash occurs.
|
||||
For Magisk app crashes, record and upload the logcat when the crash occurs.
|
||||
|
||||
## Building and Development
|
||||
|
||||
@@ -68,7 +55,7 @@ For each action, use `-h` to access help (e.g. `./build.py all -h`)
|
||||
|
||||
## Translation Contributions
|
||||
|
||||
Default string resources for Magisk Manager and its stub APK are located here:
|
||||
Default string resources for the Magisk app and its stub APK are located here:
|
||||
|
||||
- `app/src/main/res/values/strings.xml`
|
||||
- `stub/src/main/res/values/strings.xml`
|
||||
|
@@ -16,20 +16,18 @@ kapt {
|
||||
javacOptions {
|
||||
option("-Xmaxerrs", 1000)
|
||||
}
|
||||
arguments {
|
||||
arg("room.incremental", "true")
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
multiDexEnabled = true
|
||||
versionName = Config.version
|
||||
versionCode = Config.versionCode
|
||||
ndk.abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
javaCompileOptions.annotationProcessorOptions.arguments(
|
||||
mapOf("room.incremental" to "true")
|
||||
)
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -185,6 +183,8 @@ android.applicationVariants.all {
|
||||
dependencies {
|
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
|
||||
implementation(kotlin("stdlib"))
|
||||
// Some dependencies request JDK 8 stdlib, specify manually here to prevent version mismatch
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
implementation(project(":app:shared"))
|
||||
|
||||
implementation("com.github.topjohnwu:jtar:1.0.0")
|
||||
@@ -201,40 +201,31 @@ dependencies {
|
||||
implementation("${bindingAdapter}:${vBAdapt}")
|
||||
implementation("${bindingAdapter}-recyclerview:${vBAdapt}")
|
||||
|
||||
val vMarkwon = "4.6.1"
|
||||
val vMarkwon = "4.6.2"
|
||||
implementation("io.noties.markwon:core:${vMarkwon}")
|
||||
implementation("io.noties.markwon:html:${vMarkwon}")
|
||||
implementation("io.noties.markwon:image:${vMarkwon}")
|
||||
implementation("com.caverock:androidsvg:1.4")
|
||||
|
||||
val vLibsu = "3.1.1"
|
||||
val vLibsu = "3.1.2"
|
||||
implementation("com.github.topjohnwu.libsu:core:${vLibsu}")
|
||||
implementation("com.github.topjohnwu.libsu:io:${vLibsu}")
|
||||
|
||||
val vKoin = "2.1.6"
|
||||
implementation("org.koin:koin-core:${vKoin}")
|
||||
implementation("org.koin:koin-android:${vKoin}")
|
||||
implementation("org.koin:koin-androidx-viewmodel:${vKoin}")
|
||||
|
||||
val vRetrofit = "2.9.0"
|
||||
implementation("com.squareup.retrofit2:retrofit:${vRetrofit}")
|
||||
implementation("com.squareup.retrofit2:converter-moshi:${vRetrofit}")
|
||||
implementation("com.squareup.retrofit2:converter-scalars:${vRetrofit}")
|
||||
|
||||
val vOkHttp = "3.12.12"
|
||||
implementation("com.squareup.okhttp3:okhttp") {
|
||||
version {
|
||||
strictly(vOkHttp)
|
||||
}
|
||||
}
|
||||
val vOkHttp = "4.9.1"
|
||||
implementation("com.squareup.okhttp3:okhttp:${vOkHttp}")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:${vOkHttp}")
|
||||
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:${vOkHttp}")
|
||||
|
||||
val vMoshi = "1.11.0"
|
||||
val vMoshi = "1.12.0"
|
||||
implementation("com.squareup.moshi:moshi:${vMoshi}")
|
||||
kapt("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}")
|
||||
|
||||
val vRoom = "2.3.0-beta01"
|
||||
val vRoom = "2.3.0"
|
||||
implementation("androidx.room:room-runtime:${vRoom}")
|
||||
implementation("androidx.room:room-ktx:${vRoom}")
|
||||
kapt("androidx.room:room-compiler:${vRoom}")
|
||||
@@ -248,11 +239,10 @@ dependencies {
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.browser:browser:1.3.0")
|
||||
implementation("androidx.preference:preference:1.1.1")
|
||||
implementation("androidx.recyclerview:recyclerview:1.1.0")
|
||||
implementation("androidx.fragment:fragment-ktx:1.2.5")
|
||||
implementation("androidx.recyclerview:recyclerview:1.2.0")
|
||||
implementation("androidx.fragment:fragment-ktx:1.3.3")
|
||||
implementation("androidx.work:work-runtime-ktx:2.5.0")
|
||||
implementation("androidx.transition:transition:1.4.0")
|
||||
implementation("androidx.multidex:multidex:2.0.1")
|
||||
implementation("androidx.transition:transition:1.4.1")
|
||||
implementation("androidx.core:core-ktx:1.3.2")
|
||||
implementation("com.google.android.material:material:1.3.0")
|
||||
}
|
||||
|
21
app/proguard-rules.pro
vendored
21
app/proguard-rules.pro
vendored
@@ -25,12 +25,14 @@
|
||||
# Snet
|
||||
-keepclassmembers class com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper { *; }
|
||||
-keep,allowobfuscation interface com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback
|
||||
-keepclassmembers class * implements com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback {
|
||||
void onResponse(org.json.JSONObject);
|
||||
}
|
||||
-keepclassmembers class * implements com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback { *; }
|
||||
|
||||
# Stub
|
||||
-keep class com.topjohnwu.magisk.core.App { <init>(java.lang.Object); }
|
||||
-keepclassmembers class androidx.appcompat.app.AppCompatDelegateImpl {
|
||||
boolean mActivityHandlesUiModeChecked;
|
||||
boolean mActivityHandlesUiMode;
|
||||
}
|
||||
|
||||
# Strip Timber verbose and debug logging
|
||||
-assumenosideeffects class timber.log.Timber$Tree {
|
||||
@@ -42,7 +44,12 @@
|
||||
-repackageclasses 'a'
|
||||
-allowaccessmodification
|
||||
|
||||
# QOL
|
||||
-dontnote **
|
||||
-dontwarn com.caverock.androidsvg.**
|
||||
-dontwarn ru.noties.markwon.**
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||
-dontwarn org.commonmark.ext.gfm.strikethrough.Strikethrough
|
||||
-dontwarn org.conscrypt.Conscrypt*
|
||||
-dontwarn org.conscrypt.ConscryptHostnameVerifier
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
||||
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
||||
|
4
app/shared/proguard-rules.pro
vendored
4
app/shared/proguard-rules.pro
vendored
@@ -19,7 +19,3 @@
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-keepclassmembers class * extends javax.net.ssl.SSLSocketFactory {
|
||||
** delegate;
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@ import android.content.pm.ProviderInfo;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.OpenableColumns;
|
||||
@@ -24,12 +23,11 @@ import java.util.Map;
|
||||
* Modified from androidx.core.content.FileProvider
|
||||
*/
|
||||
public class FileProvider extends ContentProvider {
|
||||
private static final String[] COLUMNS = {
|
||||
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };
|
||||
private static final String[] COLUMNS = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
|
||||
|
||||
private static final File DEVICE_ROOT = new File("/");
|
||||
|
||||
private static HashMap<String, PathStrategy> sCache = new HashMap<>();
|
||||
private static final HashMap<String, PathStrategy> sCache = new HashMap<>();
|
||||
|
||||
private PathStrategy mStrategy;
|
||||
|
||||
@@ -42,7 +40,6 @@ public class FileProvider extends ContentProvider {
|
||||
public void attachInfo(Context context, ProviderInfo info) {
|
||||
super.attachInfo(context, info);
|
||||
|
||||
|
||||
if (info.exported) {
|
||||
throw new SecurityException("Provider must not be exported");
|
||||
}
|
||||
@@ -50,21 +47,16 @@ public class FileProvider extends ContentProvider {
|
||||
throw new SecurityException("Provider must grant uri permissions");
|
||||
}
|
||||
|
||||
mStrategy = getPathStrategy(context, info.authority);
|
||||
mStrategy = getPathStrategy(context, info.authority.split(";")[0]);
|
||||
}
|
||||
|
||||
|
||||
public static Uri getUriForFile(Context context, String authority,
|
||||
File file) {
|
||||
public static Uri getUriForFile(Context context, String authority, File file) {
|
||||
final PathStrategy strategy = getPathStrategy(context, authority);
|
||||
return strategy.getUriForFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection,
|
||||
String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
final File file = mStrategy.getFileForUri(uri);
|
||||
|
||||
if (projection == null) {
|
||||
@@ -94,7 +86,6 @@ public class FileProvider extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
|
||||
final File file = mStrategy.getFileForUri(uri);
|
||||
|
||||
final int lastDot = file.getName().lastIndexOf('.');
|
||||
@@ -115,23 +106,18 @@ public class FileProvider extends ContentProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection,
|
||||
String[] selectionArgs) {
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException("No external updates");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection,
|
||||
String[] selectionArgs) {
|
||||
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
final File file = mStrategy.getFileForUri(uri);
|
||||
return file.delete() ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode)
|
||||
throws FileNotFoundException {
|
||||
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
final File file = mStrategy.getFileForUri(uri);
|
||||
final int fileMode = modeToMode(mode);
|
||||
return ParcelFileDescriptor.open(file, fileMode);
|
||||
@@ -156,30 +142,24 @@ public class FileProvider extends ContentProvider {
|
||||
strat.addRoot("internal_files", buildPath(context.getFilesDir(), "."));
|
||||
strat.addRoot("cache_files", buildPath(context.getCacheDir(), "."));
|
||||
strat.addRoot("external_files", buildPath(Environment.getExternalStorageDirectory(), "."));
|
||||
{
|
||||
File[] externalFilesDirs = getExternalFilesDirs(context, null);
|
||||
if (externalFilesDirs.length > 0) {
|
||||
strat.addRoot("external_file_files", buildPath(externalFilesDirs[0], "."));
|
||||
}
|
||||
|
||||
File[] externalFilesDirs = context.getExternalFilesDirs(null);
|
||||
if (externalFilesDirs.length > 0) {
|
||||
strat.addRoot("external_file_files", buildPath(externalFilesDirs[0], "."));
|
||||
}
|
||||
{
|
||||
File[] externalCacheDirs = getExternalCacheDirs(context);
|
||||
if (externalCacheDirs.length > 0) {
|
||||
strat.addRoot("external_cache_files", buildPath(externalCacheDirs[0], "."));
|
||||
}
|
||||
File[] externalCacheDirs = context.getExternalCacheDirs();
|
||||
if (externalCacheDirs.length > 0) {
|
||||
strat.addRoot("external_cache_files", buildPath(externalCacheDirs[0], "."));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
File[] externalMediaDirs = context.getExternalMediaDirs();
|
||||
if (externalMediaDirs.length > 0) {
|
||||
strat.addRoot("external_media_files", buildPath(externalMediaDirs[0], "."));
|
||||
}
|
||||
File[] externalMediaDirs = context.getExternalMediaDirs();
|
||||
if (externalMediaDirs.length > 0) {
|
||||
strat.addRoot("external_media_files", buildPath(externalMediaDirs[0], "."));
|
||||
}
|
||||
|
||||
return strat;
|
||||
}
|
||||
|
||||
interface PathStrategy {
|
||||
|
||||
Uri getUriForFile(File file);
|
||||
|
||||
File getFileForUri(Uri uri);
|
||||
@@ -199,7 +179,6 @@ public class FileProvider extends ContentProvider {
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
root = root.getCanonicalFile();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(
|
||||
@@ -218,7 +197,6 @@ public class FileProvider extends ContentProvider {
|
||||
throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
|
||||
}
|
||||
|
||||
|
||||
Map.Entry<String, File> mostSpecific = null;
|
||||
for (Map.Entry<String, File> root : mRoots.entrySet()) {
|
||||
final String rootPath = root.getValue().getPath();
|
||||
@@ -233,7 +211,6 @@ public class FileProvider extends ContentProvider {
|
||||
"Failed to find configured root that contains " + path);
|
||||
}
|
||||
|
||||
|
||||
final String rootPath = mostSpecific.getValue().getPath();
|
||||
if (rootPath.endsWith("/")) {
|
||||
path = path.substring(rootPath.length());
|
||||
@@ -241,7 +218,6 @@ public class FileProvider extends ContentProvider {
|
||||
path = path.substring(rootPath.length() + 1);
|
||||
}
|
||||
|
||||
|
||||
path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
|
||||
return new Uri.Builder().scheme("content")
|
||||
.authority(mAuthority).encodedPath(path).build();
|
||||
@@ -275,7 +251,6 @@ public class FileProvider extends ContentProvider {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static int modeToMode(String mode) {
|
||||
int modeBits;
|
||||
if ("r".equals(mode)) {
|
||||
@@ -322,20 +297,4 @@ public class FileProvider extends ContentProvider {
|
||||
System.arraycopy(original, 0, result, 0, newLength);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static File[] getExternalFilesDirs(Context context, String type) {
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
return context.getExternalFilesDirs(type);
|
||||
} else {
|
||||
return new File[] { context.getExternalFilesDir(type) };
|
||||
}
|
||||
}
|
||||
|
||||
private static File[] getExternalCacheDirs(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
return context.getExternalCacheDirs();
|
||||
} else {
|
||||
return new File[] { context.getExternalCacheDir() };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,21 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class ProviderInstaller {
|
||||
|
||||
public static boolean install(Context context) {
|
||||
try {
|
||||
// Try installing new SSL provider from Google Play Service
|
||||
Context gms = context.createPackageContext("com.google.android.gms",
|
||||
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
|
||||
gms.getClassLoader()
|
||||
.loadClass("com.google.android.gms.common.security.ProviderInstallerImpl")
|
||||
.getMethod("insertProvider", Context.class)
|
||||
.invoke(null, gms);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,70 +0,0 @@
|
||||
package com.topjohnwu.magisk.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
public class NoSSLv3SocketFactory extends SSLSocketFactory {
|
||||
|
||||
private final static SSLSocketFactory delegate = HttpsURLConnection.getDefaultSSLSocketFactory();
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return delegate.getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return delegate.getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
private Socket createSafeSocket(Socket socket) {
|
||||
if (socket instanceof SSLSocket)
|
||||
return new SSLSocketWrapper((SSLSocket) socket) {
|
||||
@Override
|
||||
public void setEnabledProtocols(String[] protocols) {
|
||||
List<String> proto = new ArrayList<>(Arrays.asList(getSupportedProtocols()));
|
||||
proto.remove("SSLv3");
|
||||
super.setEnabledProtocols(proto.toArray(new String[0]));
|
||||
}
|
||||
};
|
||||
return socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
||||
return createSafeSocket(delegate.createSocket(s, host, port, autoClose));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket() throws IOException {
|
||||
return createSafeSocket(delegate.createSocket());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException {
|
||||
return createSafeSocket(delegate.createSocket(host, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
|
||||
return createSafeSocket(delegate.createSocket(host, port, localHost, localPort));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return createSafeSocket(delegate.createSocket(host, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
return createSafeSocket(delegate.createSocket(address, port, localAddress, localPort));
|
||||
}
|
||||
}
|
@@ -1,333 +0,0 @@
|
||||
package com.topjohnwu.magisk.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import javax.net.ssl.HandshakeCompletedListener;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
class SSLSocketWrapper extends SSLSocket {
|
||||
|
||||
private SSLSocket mBase;
|
||||
|
||||
SSLSocketWrapper(SSLSocket socket) {
|
||||
mBase = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return mBase.getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getEnabledCipherSuites() {
|
||||
return mBase.getEnabledCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabledCipherSuites(String[] suites) {
|
||||
mBase.setEnabledCipherSuites(suites);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedProtocols() {
|
||||
return mBase.getSupportedProtocols();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getEnabledProtocols() {
|
||||
return mBase.getEnabledProtocols();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabledProtocols(String[] protocols) {
|
||||
mBase.setEnabledProtocols(protocols);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLSession getSession() {
|
||||
return mBase.getSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLSession getHandshakeSession() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
|
||||
mBase.addHandshakeCompletedListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
|
||||
mBase.removeHandshakeCompletedListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startHandshake() throws IOException {
|
||||
mBase.startHandshake();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseClientMode(boolean mode) {
|
||||
mBase.setUseClientMode(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getUseClientMode() {
|
||||
return mBase.getUseClientMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNeedClientAuth(boolean need) {
|
||||
mBase.setNeedClientAuth(need);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getNeedClientAuth() {
|
||||
return mBase.getNeedClientAuth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWantClientAuth(boolean want) {
|
||||
mBase.setWantClientAuth(want);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getWantClientAuth() {
|
||||
return mBase.getWantClientAuth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnableSessionCreation(boolean flag) {
|
||||
mBase.setEnableSessionCreation(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getEnableSessionCreation() {
|
||||
return mBase.getEnableSessionCreation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLParameters getSSLParameters() {
|
||||
return mBase.getSSLParameters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSSLParameters(SSLParameters params) {
|
||||
mBase.setSSLParameters(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mBase.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(SocketAddress endpoint) throws IOException {
|
||||
mBase.connect(endpoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(SocketAddress endpoint, int timeout) throws IOException {
|
||||
mBase.connect(endpoint, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(SocketAddress bindpoint) throws IOException {
|
||||
mBase.bind(bindpoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetAddress getInetAddress() {
|
||||
return mBase.getInetAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetAddress getLocalAddress() {
|
||||
return mBase.getLocalAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
return mBase.getPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalPort() {
|
||||
return mBase.getLocalPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getRemoteSocketAddress() {
|
||||
return mBase.getRemoteSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getLocalSocketAddress() {
|
||||
return mBase.getLocalSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketChannel getChannel() {
|
||||
return mBase.getChannel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return mBase.getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
return mBase.getOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTcpNoDelay(boolean on) throws SocketException {
|
||||
mBase.setTcpNoDelay(on);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getTcpNoDelay() throws SocketException {
|
||||
return mBase.getTcpNoDelay();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSoLinger(boolean on, int linger) throws SocketException {
|
||||
mBase.setSoLinger(on, linger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSoLinger() throws SocketException {
|
||||
return mBase.getSoLinger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendUrgentData(int data) throws IOException {
|
||||
mBase.sendUrgentData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOOBInline(boolean on) throws SocketException {
|
||||
mBase.setOOBInline(on);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getOOBInline() throws SocketException {
|
||||
return mBase.getOOBInline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSoTimeout(int timeout) throws SocketException {
|
||||
mBase.setSoTimeout(timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSoTimeout() throws SocketException {
|
||||
return mBase.getSoTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSendBufferSize(int size) throws SocketException {
|
||||
mBase.setSendBufferSize(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSendBufferSize() throws SocketException {
|
||||
return mBase.getSendBufferSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReceiveBufferSize(int size) throws SocketException {
|
||||
mBase.setReceiveBufferSize(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReceiveBufferSize() throws SocketException {
|
||||
return mBase.getReceiveBufferSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKeepAlive(boolean on) throws SocketException {
|
||||
mBase.setKeepAlive(on);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getKeepAlive() throws SocketException {
|
||||
return mBase.getKeepAlive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrafficClass(int tc) throws SocketException {
|
||||
mBase.setTrafficClass(tc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTrafficClass() throws SocketException {
|
||||
return mBase.getTrafficClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReuseAddress(boolean on) throws SocketException {
|
||||
mBase.setReuseAddress(on);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getReuseAddress() throws SocketException {
|
||||
return mBase.getReuseAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mBase.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownInput() throws IOException {
|
||||
mBase.shutdownInput();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownOutput() throws IOException {
|
||||
mBase.shutdownOutput();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return mBase.isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBound() {
|
||||
return mBase.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return mBase.isClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInputShutdown() {
|
||||
return mBase.isInputShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOutputShutdown() {
|
||||
return mBase.isOutputShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
|
||||
mBase.setPerformancePreferences(connectionTime, latency, bandwidth);
|
||||
}
|
||||
}
|
@@ -9,7 +9,11 @@ import dalvik.system.DexClassLoader;
|
||||
|
||||
public class DynamicClassLoader extends DexClassLoader {
|
||||
|
||||
private ClassLoader base = Object.class.getClassLoader();
|
||||
private static final ClassLoader base = Object.class.getClassLoader();
|
||||
|
||||
public DynamicClassLoader(File apk) {
|
||||
super(apk.getPath(), apk.getParent(), null, base);
|
||||
}
|
||||
|
||||
public DynamicClassLoader(File apk, ClassLoader parent) {
|
||||
super(apk.getPath(), apk.getParent(), null, parent);
|
||||
@@ -18,7 +22,7 @@ public class DynamicClassLoader extends DexClassLoader {
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
// First check if already loaded
|
||||
Class cls = findLoadedClass(name);
|
||||
Class<?> cls = findLoadedClass(name);
|
||||
if (cls != null)
|
||||
return cls;
|
||||
|
||||
|
@@ -15,6 +15,7 @@
|
||||
<!-- Splash -->
|
||||
<activity
|
||||
android:name=".core.SplashActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/SplashTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@@ -49,10 +50,10 @@
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
<action android:name="android.intent.action.UID_REMOVED" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
|
||||
|
||||
<data android:scheme="package" />
|
||||
</intent-filter>
|
||||
|
@@ -10,7 +10,6 @@ import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.res.use
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDirections
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
@@ -28,7 +27,7 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
|
||||
protected open val themeRes: Int = Theme.selected.themeRes
|
||||
|
||||
private val navHostFragment by lazy {
|
||||
supportFragmentManager.findFragmentById(navHost) as? NavHostFragment
|
||||
supportFragmentManager.findFragmentById(navHostId) as? NavHostFragment
|
||||
}
|
||||
private val topFragment get() = navHostFragment?.childFragmentManager?.fragments?.getOrNull(0)
|
||||
protected val currentFragment get() = topFragment as? BaseUIFragment<*, *>
|
||||
@@ -36,7 +35,7 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
|
||||
override val viewRoot: View get() = binding.root
|
||||
open val navigation: NavController? get() = navHostFragment?.navController
|
||||
|
||||
open val navHost: Int = 0
|
||||
open val navHostId: Int = 0
|
||||
open val snackbarView get() = binding.root
|
||||
|
||||
init {
|
||||
@@ -58,34 +57,24 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
|
||||
.use { it.getDrawable(0) }
|
||||
.also { window.setBackgroundDrawable(it) }
|
||||
|
||||
directionsDispatcher.observe(this) {
|
||||
it?.navigate()
|
||||
// we don't want the directions to be re-dispatched, so we preemptively set them to null
|
||||
if (it != null) {
|
||||
directionsDispatcher.value = null
|
||||
}
|
||||
window?.decorView?.let {
|
||||
it.systemUiVisibility = (it.systemUiVisibility
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
window?.decorView?.let {
|
||||
it.systemUiVisibility = (it.systemUiVisibility
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
window?.decorView?.post {
|
||||
// If navigation bar is short enough (gesture navigation enabled), make it transparent
|
||||
if (window.decorView.rootWindowInsets?.systemWindowInsetBottom ?: 0 < Resources.getSystem().displayMetrics.density * 40) {
|
||||
window.navigationBarColor = Color.TRANSPARENT
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
window.navigationBarDividerColor = Color.TRANSPARENT
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.isNavigationBarContrastEnforced = false
|
||||
window.isStatusBarContrastEnforced = false
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
window?.decorView?.post {
|
||||
// If navigation bar is short enough (gesture navigation enabled), make it transparent
|
||||
if (window.decorView.rootWindowInsets?.systemWindowInsetBottom ?: 0 < Resources.getSystem().displayMetrics.density * 40) {
|
||||
window.navigationBarColor = Color.TRANSPARENT
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
window.navigationBarDividerColor = Color.TRANSPARENT
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.isNavigationBarContrastEnforced = false
|
||||
window.isStatusBarContrastEnforced = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,14 +116,4 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
|
||||
fun NavDirections.navigate() {
|
||||
navigation?.navigate(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val directionsDispatcher = MutableLiveData<NavDirections?>()
|
||||
|
||||
fun postDirections(navDirections: NavDirections) =
|
||||
directionsDispatcher.postValue(navDirections)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -5,7 +5,6 @@ import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.databinding.OnRebindCallback
|
||||
import androidx.databinding.ViewDataBinding
|
||||
|
@@ -1,9 +1,7 @@
|
||||
package com.topjohnwu.magisk.arch
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.databinding.Observable
|
||||
import androidx.databinding.PropertyChangeRegistry
|
||||
@@ -15,16 +13,17 @@ import androidx.navigation.NavDirections
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
||||
import com.topjohnwu.magisk.events.*
|
||||
import com.topjohnwu.magisk.events.BackPressEvent
|
||||
import com.topjohnwu.magisk.events.NavigationEvent
|
||||
import com.topjohnwu.magisk.events.PermissionEvent
|
||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.utils.ObservableHost
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import kotlinx.coroutines.Job
|
||||
import org.koin.core.KoinComponent
|
||||
|
||||
abstract class BaseViewModel(
|
||||
initialState: State = State.LOADING
|
||||
) : ViewModel(), ObservableHost, KoinComponent {
|
||||
) : ViewModel(), ObservableHost {
|
||||
|
||||
override var callbacks: PropertyChangeRegistry? = null
|
||||
|
||||
@@ -42,10 +41,6 @@ abstract class BaseViewModel(
|
||||
val isConnected get() = Info.isConnected
|
||||
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
|
||||
|
||||
@get:Bindable
|
||||
var insets = Insets.NONE
|
||||
set(value) = set(value, field, { field = it }, BR.insets)
|
||||
|
||||
var state= initialState
|
||||
set(value) = set(value, field, { field = it }, BR.loading, BR.loaded, BR.loadFailed)
|
||||
|
||||
@@ -78,10 +73,6 @@ abstract class BaseViewModel(
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
fun withView(action: BaseActivity.() -> Unit) {
|
||||
ViewActionEvent(action).publish()
|
||||
}
|
||||
|
||||
fun withPermission(permission: String, callback: (Boolean) -> Unit) {
|
||||
PermissionEvent(permission, callback).publish()
|
||||
}
|
||||
@@ -107,7 +98,7 @@ abstract class BaseViewModel(
|
||||
_viewEvents.postValue(this)
|
||||
}
|
||||
|
||||
fun NavDirections.publish() {
|
||||
fun NavDirections.navigate() {
|
||||
_viewEvents.postValue(NavigationEvent(this))
|
||||
}
|
||||
|
||||
|
@@ -1,24 +1,21 @@
|
||||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.multidex.MultiDex
|
||||
import androidx.work.WorkManager
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.DynAPK
|
||||
import com.topjohnwu.magisk.core.utils.AppShellInit
|
||||
import com.topjohnwu.magisk.core.utils.BusyBoxInit
|
||||
import com.topjohnwu.magisk.core.utils.IODispatcherExecutor
|
||||
import com.topjohnwu.magisk.core.utils.updateConfig
|
||||
import com.topjohnwu.magisk.di.koinModules
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ktx.unwrap
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.context.startKoin
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import kotlin.system.exitProcess
|
||||
@@ -46,10 +43,6 @@ open class App() : Application() {
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
// Basic setup
|
||||
if (BuildConfig.DEBUG)
|
||||
MultiDex.install(base)
|
||||
|
||||
// Some context magic
|
||||
val app: Application
|
||||
val impl: Context
|
||||
@@ -69,11 +62,7 @@ open class App() : Application() {
|
||||
}.getOrNull() ?: info.nativeLibraryDir
|
||||
Const.NATIVE_LIB_DIR = File(libDir)
|
||||
|
||||
// Normal startup
|
||||
startKoin {
|
||||
androidContext(wrapped)
|
||||
modules(koinModules)
|
||||
}
|
||||
ServiceLocator.context = wrapped
|
||||
AssetHack.init(impl)
|
||||
app.registerActivityLifecycleCallbacks(ForegroundTracker)
|
||||
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
|
||||
@@ -91,6 +80,7 @@ open class App() : Application() {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
object ForegroundTracker : Application.ActivityLifecycleCallbacks {
|
||||
|
||||
@Volatile
|
||||
|
@@ -1,20 +1,16 @@
|
||||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Xml
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.edit
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
|
||||
import com.topjohnwu.magisk.core.magiskdb.StringDao
|
||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||
import com.topjohnwu.magisk.core.utils.refreshLocale
|
||||
import com.topjohnwu.magisk.data.preference.PreferenceModel
|
||||
import com.topjohnwu.magisk.data.repository.DBConfig
|
||||
import com.topjohnwu.magisk.di.Protected
|
||||
import com.topjohnwu.magisk.ktx.inject
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ui.theme.Theme
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import java.io.File
|
||||
@@ -22,9 +18,9 @@ import java.io.InputStream
|
||||
|
||||
object Config : PreferenceModel, DBConfig {
|
||||
|
||||
override val stringDao: StringDao by inject()
|
||||
override val settingsDao: SettingsDao by inject()
|
||||
override val context: Context by inject(Protected)
|
||||
override val stringDB get() = ServiceLocator.stringDB
|
||||
override val settingsDB get() = ServiceLocator.settingsDB
|
||||
override val context get() = ServiceLocator.deContext
|
||||
|
||||
@get:SuppressLint("ApplySharedPref")
|
||||
val prefsFile: File get() {
|
||||
|
@@ -8,18 +8,8 @@ import java.io.File
|
||||
@Suppress("DEPRECATION")
|
||||
object Const {
|
||||
|
||||
val CPU_ABI: String
|
||||
val CPU_ABI_32: String
|
||||
|
||||
init {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
CPU_ABI = Build.SUPPORTED_ABIS[0]
|
||||
CPU_ABI_32 = Build.SUPPORTED_32_BIT_ABIS.firstOrNull() ?: CPU_ABI
|
||||
} else {
|
||||
CPU_ABI = Build.CPU_ABI
|
||||
CPU_ABI_32 = CPU_ABI
|
||||
}
|
||||
}
|
||||
val CPU_ABI: String = Build.SUPPORTED_ABIS[0]
|
||||
val CPU_ABI_32: String = Build.SUPPORTED_32_BIT_ABIS.firstOrNull() ?: CPU_ABI
|
||||
|
||||
// Paths
|
||||
lateinit var MAGISKTMP: String
|
||||
@@ -29,9 +19,9 @@ object Const {
|
||||
const val MAGISK_LOG = "/cache/magisk.log"
|
||||
|
||||
// Versions
|
||||
const val SNET_EXT_VER = 15
|
||||
const val SNET_REVISION = "18ab78817087c337ae0edd1ecac38aec49217880"
|
||||
const val BOOTCTL_REVISION = "18ab78817087c337ae0edd1ecac38aec49217880"
|
||||
const val SNET_EXT_VER = 17
|
||||
const val SNET_REVISION = "23.0"
|
||||
const val BOOTCTL_REVISION = "22.0"
|
||||
|
||||
// Misc
|
||||
val USER_ID = Process.myUid() / 100000
|
||||
@@ -62,7 +52,7 @@ object Const {
|
||||
|
||||
const val GITHUB_RAW_URL = "https://raw.githubusercontent.com/"
|
||||
const val GITHUB_API_URL = "https://api.github.com/"
|
||||
const val GITHUB_PAGE_URL = "https://topjohnwu.github.io/magisk_files/"
|
||||
const val GITHUB_PAGE_URL = "https://topjohnwu.github.io/magisk-files/"
|
||||
const val JS_DELIVR_URL = "https://cdn.jsdelivr.net/gh/"
|
||||
const val OFFICIAL_REPO = "https://magisk-modules-repo.github.io/submission/modules.json"
|
||||
}
|
||||
|
@@ -129,7 +129,6 @@ val shouldKeepResources = listOf(
|
||||
R.string.invalid_update_channel,
|
||||
R.string.update_available,
|
||||
R.string.safetynet_api_error,
|
||||
R.raw.changelog,
|
||||
R.drawable.ic_device,
|
||||
R.drawable.ic_hide_select_md2,
|
||||
R.drawable.ic_more,
|
||||
|
@@ -6,7 +6,7 @@ import com.topjohnwu.magisk.DynAPK
|
||||
import com.topjohnwu.magisk.core.model.UpdateInfo
|
||||
import com.topjohnwu.magisk.core.utils.net.NetworkObserver
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.ktx.get
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.ktx.getProperty
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.ShellUtils.fastCmd
|
||||
@@ -43,7 +43,7 @@ object Info {
|
||||
|
||||
val isConnected by lazy {
|
||||
ObservableBoolean(false).also { field ->
|
||||
NetworkObserver.observe(get()) {
|
||||
NetworkObserver.observe(AppContext) {
|
||||
UiThreadHandler.run { field.set(it) }
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ object Info {
|
||||
private fun loadState() = Env(
|
||||
fastCmd("magisk -v").split(":".toRegex())[0],
|
||||
runCatching { fastCmd("magisk -V").toInt() }.getOrDefault(-1),
|
||||
Shell.su("magiskhide --status").exec().isSuccess
|
||||
Shell.su("magiskhide status").exec().isSuccess
|
||||
)
|
||||
|
||||
class Env(
|
||||
|
@@ -1,44 +1,46 @@
|
||||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import com.topjohnwu.magisk.core.base.BaseReceiver
|
||||
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.view.Shortcuts
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.inject
|
||||
|
||||
open class Receiver : BaseReceiver() {
|
||||
|
||||
private val policyDB: PolicyDao by inject()
|
||||
private val policyDB get() = ServiceLocator.policyDB
|
||||
|
||||
private fun getPkg(intent: Intent): String {
|
||||
return intent.data?.encodedSchemeSpecificPart.orEmpty()
|
||||
@SuppressLint("InlinedApi")
|
||||
private fun getPkg(intent: Intent): String? {
|
||||
val pkg = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME)
|
||||
return pkg ?: intent.data?.schemeSpecificPart
|
||||
}
|
||||
|
||||
private fun getUid(intent: Intent): Int? {
|
||||
val uid = intent.getIntExtra(Intent.EXTRA_UID, -1)
|
||||
return if (uid == -1) null else uid
|
||||
}
|
||||
|
||||
override fun onReceive(context: ContextWrapper, intent: Intent?) {
|
||||
intent ?: return
|
||||
|
||||
fun rmPolicy(pkg: String) = GlobalScope.launch {
|
||||
policyDB.delete(pkg)
|
||||
fun rmPolicy(uid: Int) = GlobalScope.launch {
|
||||
policyDB.delete(uid)
|
||||
}
|
||||
|
||||
when (intent.action ?: return) {
|
||||
Intent.ACTION_REBOOT -> {
|
||||
SuCallbackHandler(context, intent.getStringExtra("action"), intent.extras)
|
||||
}
|
||||
Intent.ACTION_PACKAGE_REPLACED -> {
|
||||
// This will only work pre-O
|
||||
if (Config.suReAuth)
|
||||
rmPolicy(getPkg(intent))
|
||||
getUid(intent)?.let { rmPolicy(it) }
|
||||
}
|
||||
Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
|
||||
val pkg = getPkg(intent)
|
||||
rmPolicy(pkg)
|
||||
Shell.su("magiskhide --rm $pkg").submit()
|
||||
Intent.ACTION_UID_REMOVED -> {
|
||||
getUid(intent)?.let { rmPolicy(it) }
|
||||
getPkg(intent)?.let { Shell.su("magiskhide rm $it").submit() }
|
||||
}
|
||||
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setupDynamic(context)
|
||||
}
|
||||
|
@@ -7,8 +7,7 @@ import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.ktx.get
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ui.MainActivity
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
@@ -68,7 +67,7 @@ open class SplashActivity : BaseActivity() {
|
||||
Shortcuts.setupDynamic(this)
|
||||
|
||||
// Pre-fetch network services
|
||||
get<NetworkService>()
|
||||
ServiceLocator.networkService
|
||||
|
||||
DONE = true
|
||||
startActivity(redirect<MainActivity>())
|
||||
|
@@ -4,16 +4,14 @@ import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.work.*
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class UpdateCheckService(context: Context, workerParams: WorkerParameters)
|
||||
: CoroutineWorker(context, workerParams), KoinComponent {
|
||||
: CoroutineWorker(context, workerParams) {
|
||||
|
||||
private val svc: NetworkService by inject()
|
||||
private val svc get() = ServiceLocator.networkService
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
return svc.fetchUpdate()?.run {
|
||||
|
@@ -7,6 +7,7 @@ import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
@@ -16,6 +17,7 @@ import androidx.core.content.ContextCompat
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import com.topjohnwu.magisk.core.wrap
|
||||
import com.topjohnwu.magisk.ktx.reflectField
|
||||
import com.topjohnwu.magisk.ktx.set
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import kotlin.random.Random
|
||||
@@ -43,6 +45,15 @@ abstract class BaseActivity : AppCompatActivity() {
|
||||
super.attachBaseContext(base.wrap(true))
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// Overwrite private members to avoid nasty "false" stack traces being logged
|
||||
val delegate = delegate
|
||||
val clz = delegate.javaClass
|
||||
clz.reflectField("mActivityHandlesUiModeChecked").set(delegate, true)
|
||||
clz.reflectField("mActivityHandlesUiMode").set(delegate, false)
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
fun withPermission(permission: String, builder: PermissionRequestBuilder.() -> Unit) {
|
||||
val request = PermissionRequestBuilder().apply(builder).build()
|
||||
|
||||
@@ -72,6 +83,7 @@ abstract class BaseActivity : AppCompatActivity() {
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
var success = true
|
||||
for (res in grantResults) {
|
||||
if (res != PackageManager.PERMISSION_GRANTED) {
|
||||
|
@@ -5,9 +5,8 @@ import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import com.topjohnwu.magisk.core.wrap
|
||||
import org.koin.core.KoinComponent
|
||||
|
||||
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent {
|
||||
abstract class BaseReceiver : BroadcastReceiver() {
|
||||
|
||||
final override fun onReceive(context: Context, intent: Intent?) {
|
||||
onReceive(context.wrap() as ContextWrapper, intent)
|
||||
|
@@ -3,9 +3,8 @@ package com.topjohnwu.magisk.core.base
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import com.topjohnwu.magisk.core.wrap
|
||||
import org.koin.core.KoinComponent
|
||||
|
||||
abstract class BaseService : Service(), KoinComponent {
|
||||
abstract class BaseService : Service() {
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(base.wrap())
|
||||
}
|
||||
|
@@ -9,15 +9,13 @@ import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.ForegroundTracker
|
||||
import com.topjohnwu.magisk.core.base.BaseService
|
||||
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.ResponseBody
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.core.KoinComponent
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
@@ -25,13 +23,13 @@ import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.random.Random.Default.nextInt
|
||||
|
||||
abstract class BaseDownloader : BaseService(), KoinComponent {
|
||||
abstract class BaseDownloader : BaseService() {
|
||||
|
||||
private val hasNotifications get() = notifications.isNotEmpty()
|
||||
private val notifications = Collections.synchronizedMap(HashMap<Int, Notification.Builder>())
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
val service: NetworkService by inject()
|
||||
val service get() = ServiceLocator.networkService
|
||||
|
||||
// -- Service overrides
|
||||
|
||||
@@ -174,10 +172,10 @@ abstract class BaseDownloader : BaseService(), KoinComponent {
|
||||
|
||||
// ---
|
||||
|
||||
companion object : KoinComponent {
|
||||
companion object {
|
||||
const val ACTION_KEY = "download_action"
|
||||
|
||||
private val progressBroadcast = MutableLiveData<Pair<Float, Subject>>()
|
||||
private val progressBroadcast = MutableLiveData<Pair<Float, Subject>?>()
|
||||
|
||||
fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {
|
||||
progressBroadcast.value = null
|
||||
|
@@ -1,21 +1,22 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.net.toFile
|
||||
import com.topjohnwu.magisk.arch.BaseUIActivity
|
||||
import com.topjohnwu.magisk.core.ForegroundTracker
|
||||
import com.topjohnwu.magisk.core.download.Action.Flash
|
||||
import com.topjohnwu.magisk.core.download.Subject.Manager
|
||||
import com.topjohnwu.magisk.core.download.Subject.Module
|
||||
import com.topjohnwu.magisk.core.intent
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import kotlin.random.Random.Default.nextInt
|
||||
|
||||
@SuppressLint("Registered")
|
||||
open class DownloadService : BaseDownloader() {
|
||||
|
||||
private val context get() = this
|
||||
@@ -26,7 +27,12 @@ open class DownloadService : BaseDownloader() {
|
||||
}
|
||||
|
||||
private fun Module.onFinish(id: Int) = when (action) {
|
||||
Flash -> FlashFragment.install(file, id)
|
||||
Flash -> {
|
||||
UiThreadHandler.run {
|
||||
(ForegroundTracker.foreground as? BaseUIActivity<*, *>)
|
||||
?.navigation?.navigate(FlashFragment.install(file, id))
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import androidx.core.net.toUri
|
||||
@@ -9,12 +8,12 @@ import com.topjohnwu.magisk.core.model.MagiskJson
|
||||
import com.topjohnwu.magisk.core.model.StubJson
|
||||
import com.topjohnwu.magisk.core.model.module.OnlineModule
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.ktx.cachedFile
|
||||
import com.topjohnwu.magisk.ktx.get
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
private fun cachedFile(name: String) = get<Context>().cachedFile(name).apply { delete() }.toUri()
|
||||
private fun cachedFile(name: String) = AppContext.cachedFile(name).apply { delete() }.toUri()
|
||||
|
||||
sealed class Subject : Parcelable {
|
||||
|
||||
|
@@ -1,11 +1,11 @@
|
||||
package com.topjohnwu.magisk.core.magiskdb
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||
import com.topjohnwu.magisk.core.model.su.toMap
|
||||
import com.topjohnwu.magisk.core.model.su.toPolicy
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.ktx.now
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -13,9 +13,7 @@ import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class PolicyDao(
|
||||
private val context: Context
|
||||
) : BaseDao() {
|
||||
class PolicyDao : BaseDao() {
|
||||
|
||||
override val table: String = Table.POLICY
|
||||
|
||||
@@ -31,12 +29,6 @@ class PolicyDao(
|
||||
}
|
||||
}.commit()
|
||||
|
||||
suspend fun delete(packageName: String) = buildQuery<Delete> {
|
||||
condition {
|
||||
equals("package_name", packageName)
|
||||
}
|
||||
}.commit()
|
||||
|
||||
suspend fun delete(uid: Int) = buildQuery<Delete> {
|
||||
condition {
|
||||
equals("uid", uid)
|
||||
@@ -62,7 +54,7 @@ class PolicyDao(
|
||||
}
|
||||
|
||||
private fun Map<String, String>.toPolicyOrNull(): SuPolicy? {
|
||||
return runCatching { toPolicy(context.packageManager) }.getOrElse {
|
||||
return runCatching { toPolicy(AppContext.packageManager) }.getOrElse {
|
||||
Timber.e(it)
|
||||
if (it is PackageManager.NameNotFoundException) {
|
||||
val uid = getOrElse("uid") { null } ?: return null
|
||||
|
@@ -16,8 +16,7 @@ data class MagiskJson(
|
||||
val version: String = "",
|
||||
val versionCode: Int = -1,
|
||||
val link: String = "",
|
||||
val note: String = "",
|
||||
val md5: String = ""
|
||||
val note: String = ""
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
|
@@ -4,8 +4,7 @@ import android.os.Parcelable
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.topjohnwu.magisk.core.model.ModuleJson
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.ktx.get
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ktx.legalFilename
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.text.DateFormat
|
||||
@@ -26,7 +25,7 @@ data class OnlineModule(
|
||||
val notes_url: String
|
||||
) : Module(), Parcelable {
|
||||
|
||||
private val svc: NetworkService get() = get()
|
||||
private val svc get() = ServiceLocator.networkService
|
||||
|
||||
constructor(info: ModuleJson) : this(
|
||||
id = info.id,
|
||||
|
@@ -13,8 +13,7 @@ import com.topjohnwu.magisk.core.intent
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||
import com.topjohnwu.magisk.core.model.su.toLog
|
||||
import com.topjohnwu.magisk.core.model.su.toPolicy
|
||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||
import com.topjohnwu.magisk.ktx.get
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ktx.startActivity
|
||||
import com.topjohnwu.magisk.ktx.startActivityWithRoot
|
||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
|
||||
@@ -104,9 +103,8 @@ object SuCallbackHandler {
|
||||
command = command
|
||||
)
|
||||
|
||||
val logRepo = get<LogRepository>()
|
||||
GlobalScope.launch {
|
||||
logRepo.insert(log)
|
||||
ServiceLocator.logRepo.insert(log)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,18 +1,16 @@
|
||||
package com.topjohnwu.magisk.core.tasks
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toFile
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
|
||||
import com.topjohnwu.magisk.core.utils.unzip
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.ktx.writeTo
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
@@ -22,10 +20,9 @@ open class FlashZip(
|
||||
private val mUri: Uri,
|
||||
private val console: MutableList<String>,
|
||||
private val logs: MutableList<String>
|
||||
): KoinComponent {
|
||||
) {
|
||||
|
||||
private val context: Context by inject()
|
||||
private val installDir = File(context.cacheDir, "flash")
|
||||
private val installDir = File(AppContext.cacheDir, "flash")
|
||||
private lateinit var zipFile: File
|
||||
|
||||
@Throws(IOException::class)
|
||||
@@ -66,7 +63,7 @@ open class FlashZip(
|
||||
|
||||
console.add("- Installing ${mUri.displayName}")
|
||||
|
||||
return Shell.su("sh $installDir/update-binary dummy 1 \"$zipFile\"")
|
||||
return Shell.su("sh $installDir/update-binary dummy 1 \'$zipFile\'")
|
||||
.to(console, logs).exec().isSuccess
|
||||
}
|
||||
|
||||
|
@@ -14,8 +14,7 @@ import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.Provider
|
||||
import com.topjohnwu.magisk.core.utils.AXML
|
||||
import com.topjohnwu.magisk.core.utils.Keygen
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.ktx.inject
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ktx.writeTo
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
@@ -41,7 +40,7 @@ object HideAPK {
|
||||
// Some arbitrary limit
|
||||
const val MAX_LABEL_LENGTH = 32
|
||||
|
||||
private val svc: NetworkService by inject()
|
||||
private val svc get() = ServiceLocator.networkService
|
||||
private val Context.APK_URI get() = Provider.APK_URI(packageName)
|
||||
private val Context.PREFS_URI get() = Provider.PREFS_URI(packageName)
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package com.topjohnwu.magisk.core.tasks
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.system.Os
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.os.postDelayed
|
||||
@@ -12,10 +12,8 @@ import com.topjohnwu.magisk.core.*
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.di.Protected
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ktx.reboot
|
||||
import com.topjohnwu.magisk.ktx.symlink
|
||||
import com.topjohnwu.magisk.ktx.withStreams
|
||||
import com.topjohnwu.magisk.ktx.writeTo
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
@@ -34,8 +32,6 @@ import org.kamranzafar.jtar.TarEntry
|
||||
import org.kamranzafar.jtar.TarHeader
|
||||
import org.kamranzafar.jtar.TarInputStream
|
||||
import org.kamranzafar.jtar.TarOutputStream
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import timber.log.Timber
|
||||
import java.io.*
|
||||
import java.nio.ByteBuffer
|
||||
@@ -46,14 +42,14 @@ import java.util.zip.ZipFile
|
||||
abstract class MagiskInstallImpl protected constructor(
|
||||
protected val console: MutableList<String> = NOPList.getInstance(),
|
||||
private val logs: MutableList<String> = NOPList.getInstance()
|
||||
) : KoinComponent {
|
||||
) {
|
||||
|
||||
protected var installDir = File("xxx")
|
||||
private lateinit var srcBoot: File
|
||||
|
||||
private val shell = Shell.getShell()
|
||||
private val service: NetworkService by inject()
|
||||
protected val context: Context by inject(Protected)
|
||||
private val service get() = ServiceLocator.networkService
|
||||
protected val context get() = ServiceLocator.deContext
|
||||
private val useRootDir = shell.isRoot && Info.noDataExec
|
||||
|
||||
private fun findImage(): Boolean {
|
||||
@@ -111,7 +107,7 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
} ?: emptyArray()
|
||||
for (lib in libs) {
|
||||
val name = lib.name.substring(3, lib.name.length - 3)
|
||||
symlink(lib.path, "$installDir/$name")
|
||||
Os.symlink(lib.path, "$installDir/$name")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,6 +212,7 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
// Repack boot image to prevent auto restore
|
||||
arrayOf(
|
||||
"cd $installDir",
|
||||
"chmod -R 755 .",
|
||||
"./magiskboot unpack boot.img",
|
||||
"./magiskboot repack boot.img",
|
||||
"cat new-boot.img > boot.img",
|
||||
@@ -255,7 +252,7 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
val alpha = "abcdefghijklmnopqrstuvwxyz"
|
||||
val alphaNum = "$alpha${alpha.toUpperCase(Locale.ROOT)}0123456789"
|
||||
val random = SecureRandom()
|
||||
val filename = StringBuilder("magisk_patched_").run {
|
||||
val filename = StringBuilder("magisk_patched-${BuildConfig.VERSION_CODE}_").run {
|
||||
for (i in 1..5) {
|
||||
append(alphaNum[random.nextInt(alphaNum.length)])
|
||||
}
|
||||
@@ -415,7 +412,22 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
@WorkerThread
|
||||
protected abstract suspend fun operations(): Boolean
|
||||
|
||||
open suspend fun exec() = withContext(Dispatchers.IO) { operations() }
|
||||
open suspend fun exec(): Boolean {
|
||||
synchronized(Companion) {
|
||||
if (haveActiveSession)
|
||||
return false
|
||||
haveActiveSession = true
|
||||
}
|
||||
val result = withContext(Dispatchers.IO) { operations() }
|
||||
synchronized(Companion) {
|
||||
haveActiveSession = false
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var haveActiveSession = false
|
||||
}
|
||||
}
|
||||
|
||||
abstract class MagiskInstaller(
|
||||
|
@@ -6,12 +6,11 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.get
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
|
||||
object BiometricHelper: KoinComponent {
|
||||
object BiometricHelper {
|
||||
|
||||
private val mgr by lazy { BiometricManager.from(get()) }
|
||||
private val mgr by lazy { BiometricManager.from(AppContext) }
|
||||
|
||||
val isSupported get() = when (mgr.canAuthenticate()) {
|
||||
BiometricManager.BIOMETRIC_SUCCESS -> true
|
||||
|
@@ -8,8 +8,6 @@ import android.content.res.Resources
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.AssetHack
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.ktx.langTagToLocale
|
||||
import com.topjohnwu.magisk.ktx.toLangTag
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
@@ -40,7 +38,7 @@ withContext(Dispatchers.Default) {
|
||||
// Then add all supported locales
|
||||
addAll(Resources.getSystem().assets.locales)
|
||||
}.map {
|
||||
it.langTagToLocale()
|
||||
Locale.forLanguageTag(it)
|
||||
}.distinctBy {
|
||||
res.updateLocale(it)
|
||||
res.getString(compareId)
|
||||
@@ -59,7 +57,7 @@ withContext(Dispatchers.Default) {
|
||||
|
||||
locales.forEach { locale ->
|
||||
names.add(locale.getDisplayName(locale))
|
||||
values.add(locale.toLangTag())
|
||||
values.add(locale.toLanguageTag())
|
||||
}
|
||||
|
||||
(names.toTypedArray() to values.toTypedArray()).also { cachedLocales = it }
|
||||
@@ -79,7 +77,7 @@ fun refreshLocale() {
|
||||
val localeConfig = Config.locale
|
||||
currentLocale = when {
|
||||
localeConfig.isEmpty() -> defaultLocale
|
||||
else -> localeConfig.langTagToLocale()
|
||||
else -> Locale.forLanguageTag(localeConfig)
|
||||
}
|
||||
Locale.setDefault(currentLocale)
|
||||
AssetHack.resource.updateConfig()
|
||||
|
@@ -1,9 +1,7 @@
|
||||
package com.topjohnwu.magisk.core.utils
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.ContentUris
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
@@ -13,7 +11,7 @@ import androidx.annotation.RequiresApi
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.ktx.get
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
@@ -24,7 +22,7 @@ import kotlin.experimental.and
|
||||
@Suppress("DEPRECATION")
|
||||
object MediaStoreUtils {
|
||||
|
||||
private val cr: ContentResolver by lazy { get<Context>().contentResolver }
|
||||
private val cr get() = AppContext.contentResolver
|
||||
|
||||
@get:RequiresApi(api = 29)
|
||||
private val tableUri
|
||||
|
@@ -78,7 +78,6 @@ class AppShellInit : BaseShellInit() {
|
||||
fun getBool(name: String) = getVar(name).toBoolean()
|
||||
|
||||
shell.newJob().apply {
|
||||
add("export API=${Build.VERSION.SDK_INT}")
|
||||
add(context.rawResource(R.raw.manager))
|
||||
if (shell.isRoot) {
|
||||
add(context.assets.open("util_functions.sh"))
|
||||
|
@@ -20,11 +20,10 @@ abstract class NetworkObserver(
|
||||
|
||||
companion object {
|
||||
fun observe(context: Context, callback: ConnectionCallback): NetworkObserver {
|
||||
return when (Build.VERSION.SDK_INT) {
|
||||
in 23 until Int.MAX_VALUE -> MarshmallowNetworkObserver(context, callback)
|
||||
in 21 until 23 -> LollipopNetworkObserver(context, callback)
|
||||
else -> PreLollipopNetworkObserver(context, callback)
|
||||
}.apply { getCurrentState() }
|
||||
val observer: NetworkObserver = if (Build.VERSION.SDK_INT >= 23)
|
||||
MarshmallowNetworkObserver(context, callback)
|
||||
else LollipopNetworkObserver(context, callback)
|
||||
return observer.apply { getCurrentState() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,38 +0,0 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.topjohnwu.magisk.core.utils.net
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.ConnectivityManager
|
||||
import androidx.core.net.ConnectivityManagerCompat
|
||||
|
||||
class PreLollipopNetworkObserver(
|
||||
context: Context,
|
||||
callback: ConnectionCallback
|
||||
): NetworkObserver(context, callback) {
|
||||
|
||||
private val receiver = ConnectivityBroadcastReceiver()
|
||||
|
||||
init {
|
||||
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
|
||||
app.registerReceiver(receiver, filter)
|
||||
}
|
||||
|
||||
override fun stopObserving() {
|
||||
app.unregisterReceiver(receiver)
|
||||
}
|
||||
|
||||
override fun getCurrentState() {
|
||||
callback(manager.activeNetworkInfo?.isConnected ?: false)
|
||||
}
|
||||
|
||||
private inner class ConnectivityBroadcastReceiver: BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent) {
|
||||
val info = ConnectivityManagerCompat.getNetworkInfoFromBroadcast(manager, intent)
|
||||
callback(info?.isConnected ?: false)
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,17 +10,15 @@ import retrofit2.http.*
|
||||
private const val REVISION = "revision"
|
||||
private const val BRANCH = "branch"
|
||||
private const val REPO = "repo"
|
||||
private const val FILE = "file"
|
||||
|
||||
const val MAGISK_FILES = "topjohnwu/magisk_files"
|
||||
const val MAGISK_FILES = "topjohnwu/magisk-files"
|
||||
const val MAGISK_MAIN = "topjohnwu/Magisk"
|
||||
|
||||
interface GithubPageServices {
|
||||
|
||||
@GET("stable.json")
|
||||
suspend fun fetchStableUpdate(): UpdateInfo
|
||||
|
||||
@GET("beta.json")
|
||||
suspend fun fetchBetaUpdate(): UpdateInfo
|
||||
@GET("{$FILE}")
|
||||
suspend fun fetchUpdateJSON(@Path(FILE) file: String): UpdateInfo
|
||||
}
|
||||
|
||||
interface JSDelivrServices {
|
||||
@@ -33,9 +31,6 @@ interface JSDelivrServices {
|
||||
@Streaming
|
||||
suspend fun fetchBootctl(@Path(REVISION) revision: String = Const.BOOTCTL_REVISION): ResponseBody
|
||||
|
||||
@GET("$MAGISK_FILES@{$REVISION}/canary.json")
|
||||
suspend fun fetchCanaryUpdate(@Path(REVISION) revision: String): UpdateInfo
|
||||
|
||||
@GET("$MAGISK_MAIN@{$REVISION}/scripts/module_installer.sh")
|
||||
@Streaming
|
||||
suspend fun fetchInstaller(@Path(REVISION) revision: String): ResponseBody
|
||||
|
@@ -9,8 +9,8 @@ import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
interface DBConfig {
|
||||
val settingsDao: SettingsDao
|
||||
val stringDao: StringDao
|
||||
val settingsDB: SettingsDao
|
||||
val stringDB: StringDao
|
||||
|
||||
fun dbSettings(
|
||||
name: String,
|
||||
@@ -41,7 +41,7 @@ class DBSettingsValue(
|
||||
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Int {
|
||||
if (value == null)
|
||||
value = runBlocking {
|
||||
thisRef.settingsDao.fetch(name, default)
|
||||
thisRef.settingsDB.fetch(name, default)
|
||||
}
|
||||
return value as Int
|
||||
}
|
||||
@@ -51,7 +51,7 @@ class DBSettingsValue(
|
||||
this.value = value
|
||||
}
|
||||
GlobalScope.launch {
|
||||
thisRef.settingsDao.put(name, value)
|
||||
thisRef.settingsDB.put(name, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ class DBStringsValue(
|
||||
override fun getValue(thisRef: DBConfig, property: KProperty<*>): String {
|
||||
if (value == null)
|
||||
value = runBlocking {
|
||||
thisRef.stringDao.fetch(name, default)
|
||||
thisRef.stringDB.fetch(name, default)
|
||||
}
|
||||
return value!!
|
||||
}
|
||||
@@ -94,21 +94,21 @@ class DBStringsValue(
|
||||
if (value.isEmpty()) {
|
||||
if (sync) {
|
||||
runBlocking {
|
||||
thisRef.stringDao.delete(name)
|
||||
thisRef.stringDB.delete(name)
|
||||
}
|
||||
} else {
|
||||
GlobalScope.launch {
|
||||
thisRef.stringDao.delete(name)
|
||||
thisRef.stringDB.delete(name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (sync) {
|
||||
runBlocking {
|
||||
thisRef.stringDao.put(name, value)
|
||||
thisRef.stringDB.put(name, value)
|
||||
}
|
||||
} else {
|
||||
GlobalScope.launch {
|
||||
thisRef.stringDao.put(name, value)
|
||||
thisRef.stringDB.put(name, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,9 +8,6 @@ import com.topjohnwu.magisk.core.Config.Value.DEFAULT_CHANNEL
|
||||
import com.topjohnwu.magisk.core.Config.Value.STABLE_CHANNEL
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.model.MagiskJson
|
||||
import com.topjohnwu.magisk.core.model.StubJson
|
||||
import com.topjohnwu.magisk.core.model.UpdateInfo
|
||||
import com.topjohnwu.magisk.data.network.*
|
||||
import retrofit2.HttpException
|
||||
import timber.log.Timber
|
||||
@@ -40,22 +37,10 @@ class NetworkService(
|
||||
}
|
||||
|
||||
// UpdateInfo
|
||||
private suspend fun fetchStableUpdate() = pages.fetchStableUpdate()
|
||||
private suspend fun fetchBetaUpdate() = pages.fetchBetaUpdate()
|
||||
private suspend fun fetchStableUpdate() = pages.fetchUpdateJSON("stable.json")
|
||||
private suspend fun fetchBetaUpdate() = pages.fetchUpdateJSON("beta.json")
|
||||
private suspend fun fetchCanaryUpdate() = pages.fetchUpdateJSON("canary.json")
|
||||
private suspend fun fetchCustomUpdate(url: String) = raw.fetchCustomUpdate(url)
|
||||
private suspend fun fetchCanaryUpdate(): UpdateInfo {
|
||||
val sha = fetchCanaryVersion()
|
||||
val info = jsd.fetchCanaryUpdate(sha)
|
||||
|
||||
fun genCDNUrl(name: String) = "${Const.Url.JS_DELIVR_URL}${MAGISK_FILES}@${sha}/${name}"
|
||||
fun MagiskJson.updateCopy() = copy(link = genCDNUrl(link), note = genCDNUrl(note))
|
||||
fun StubJson.updateCopy() = copy(link = genCDNUrl(link))
|
||||
|
||||
return info.copy(
|
||||
magisk = info.magisk.updateCopy(),
|
||||
stub = info.stub.updateCopy()
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun <T> safe(factory: () -> T): T? {
|
||||
return try {
|
||||
@@ -89,6 +74,5 @@ class NetworkService(
|
||||
suspend fun fetchFile(url: String) = wrap { raw.fetchFile(url) }
|
||||
suspend fun fetchString(url: String) = wrap { raw.fetchString(url) }
|
||||
|
||||
private suspend fun fetchCanaryVersion() = api.fetchBranch(MAGISK_FILES, "canary").commit.sha
|
||||
private suspend fun fetchMainVersion() = api.fetchBranch(MAGISK_MAIN, "master").commit.sha
|
||||
}
|
||||
|
@@ -28,12 +28,11 @@ import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ktx.coroutineScope
|
||||
import com.topjohnwu.magisk.ktx.get
|
||||
import com.topjohnwu.magisk.ktx.replaceRandomWithSpecial
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import com.topjohnwu.widget.IndeterminateCheckBox
|
||||
import io.noties.markwon.Markwon
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@@ -60,8 +59,7 @@ fun setInvisibleUnless(view: View, invisibleUnless: Boolean) {
|
||||
@BindingAdapter("markdownText")
|
||||
fun setMarkdownText(tv: TextView, text: CharSequence) {
|
||||
tv.coroutineScope.launch(Dispatchers.IO) {
|
||||
val markwon = get<Markwon>()
|
||||
markwon.setMarkdown(tv, text.toString())
|
||||
ServiceLocator.markwon.setMarkdown(tv, text.toString())
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,19 +0,0 @@
|
||||
package com.topjohnwu.magisk.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.topjohnwu.magisk.core.AssetHack
|
||||
import com.topjohnwu.magisk.ktx.deviceProtectedContext
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
|
||||
val SUTimeout = named("su_timeout")
|
||||
val Protected = named("protected")
|
||||
|
||||
val applicationModule = module {
|
||||
factory { AssetHack.resource }
|
||||
factory { get<Context>().packageManager }
|
||||
factory(Protected) { get<Context>().deviceProtectedContext }
|
||||
single(SUTimeout) { get<Context>(Protected).getSharedPreferences("su_timeout", 0) }
|
||||
single { PreferenceManager.getDefaultSharedPreferences(get(Protected)) }
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
package com.topjohnwu.magisk.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
|
||||
import com.topjohnwu.magisk.core.magiskdb.StringDao
|
||||
import com.topjohnwu.magisk.core.tasks.RepoUpdater
|
||||
import com.topjohnwu.magisk.data.database.RepoDatabase
|
||||
import com.topjohnwu.magisk.data.database.SuLogDatabase
|
||||
import org.koin.dsl.module
|
||||
|
||||
|
||||
val databaseModule = module {
|
||||
single { PolicyDao(get()) }
|
||||
single { SettingsDao() }
|
||||
single { StringDao() }
|
||||
single { createRepoDatabase(get()) }
|
||||
single { get<RepoDatabase>().repoDao() }
|
||||
single { createSuLogDatabase(get(Protected)).suLogDao() }
|
||||
single { RepoUpdater(get(), get()) }
|
||||
}
|
||||
|
||||
fun createRepoDatabase(context: Context) =
|
||||
Room.databaseBuilder(context, RepoDatabase::class.java, "repo.db")
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
|
||||
fun createSuLogDatabase(context: Context) =
|
||||
Room.databaseBuilder(context, SuLogDatabase::class.java, "sulogs.db")
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
@@ -1,9 +0,0 @@
|
||||
package com.topjohnwu.magisk.di
|
||||
|
||||
val koinModules = listOf(
|
||||
applicationModule,
|
||||
networkingModule,
|
||||
databaseModule,
|
||||
repositoryModule,
|
||||
viewModelModules
|
||||
)
|
@@ -1,49 +1,31 @@
|
||||
package com.topjohnwu.magisk.di
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.ProviderInstaller
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.data.network.GithubApiServices
|
||||
import com.topjohnwu.magisk.data.network.GithubPageServices
|
||||
import com.topjohnwu.magisk.data.network.JSDelivrServices
|
||||
import com.topjohnwu.magisk.data.network.RawServices
|
||||
import com.topjohnwu.magisk.ktx.precomputedText
|
||||
import com.topjohnwu.magisk.net.Networking
|
||||
import com.topjohnwu.magisk.net.NoSSLv3SocketFactory
|
||||
import com.topjohnwu.magisk.utils.MarkwonImagePlugin
|
||||
import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.html.HtmlPlugin
|
||||
import okhttp3.Dns
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.dnsoverhttps.DnsOverHttps
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.koin.dsl.module
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import retrofit2.converter.scalars.ScalarsConverterFactory
|
||||
import java.net.InetAddress
|
||||
import java.net.UnknownHostException
|
||||
|
||||
val networkingModule = module {
|
||||
single { createOkHttpClient(get()) }
|
||||
single { createRetrofit(get()) }
|
||||
single { createApiService<RawServices>(get(), Const.Url.GITHUB_RAW_URL) }
|
||||
single { createApiService<GithubApiServices>(get(), Const.Url.GITHUB_API_URL) }
|
||||
single { createApiService<GithubPageServices>(get(), Const.Url.GITHUB_PAGE_URL) }
|
||||
single { createApiService<JSDelivrServices>(get(), Const.Url.JS_DELIVR_URL) }
|
||||
single { createMarkwon(get(), get()) }
|
||||
}
|
||||
|
||||
private class DnsResolver(client: OkHttpClient) : Dns {
|
||||
|
||||
private val doh by lazy {
|
||||
DnsOverHttps.Builder().client(client)
|
||||
.url(HttpUrl.get("https://cloudflare-dns.com/dns-query"))
|
||||
.url("https://cloudflare-dns.com/dns-query".toHttpUrl())
|
||||
.bootstrapDnsHosts(listOf(
|
||||
InetAddress.getByName("162.159.36.1"),
|
||||
InetAddress.getByName("162.159.46.1"),
|
||||
@@ -79,10 +61,8 @@ fun createOkHttpClient(context: Context): OkHttpClient {
|
||||
})
|
||||
}
|
||||
|
||||
if (!Networking.init(context)) {
|
||||
if (!ProviderInstaller.install(context)) {
|
||||
Info.hasGMS = false
|
||||
if (Build.VERSION.SDK_INT < 21)
|
||||
builder.sslSocketFactory(NoSSLv3SocketFactory())
|
||||
}
|
||||
builder.dns(DnsResolver(builder.build()))
|
||||
|
@@ -1,11 +0,0 @@
|
||||
package com.topjohnwu.magisk.di
|
||||
|
||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import org.koin.dsl.module
|
||||
|
||||
|
||||
val repositoryModule = module {
|
||||
single { LogRepository(get()) }
|
||||
single { NetworkService(get(), get(), get(), get()) }
|
||||
}
|
88
app/src/main/java/com/topjohnwu/magisk/di/ServiceLocator.kt
Normal file
88
app/src/main/java/com/topjohnwu/magisk/di/ServiceLocator.kt
Normal file
@@ -0,0 +1,88 @@
|
||||
package com.topjohnwu.magisk.di
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import androidx.room.Room
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
|
||||
import com.topjohnwu.magisk.core.magiskdb.StringDao
|
||||
import com.topjohnwu.magisk.core.tasks.RepoUpdater
|
||||
import com.topjohnwu.magisk.data.database.RepoDatabase
|
||||
import com.topjohnwu.magisk.data.database.SuLogDatabase
|
||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.ktx.deviceProtectedContext
|
||||
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
||||
import com.topjohnwu.magisk.ui.install.InstallViewModel
|
||||
import com.topjohnwu.magisk.ui.log.LogViewModel
|
||||
import com.topjohnwu.magisk.ui.module.ModuleViewModel
|
||||
import com.topjohnwu.magisk.ui.settings.SettingsViewModel
|
||||
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
|
||||
import com.topjohnwu.magisk.ui.surequest.SuRequestViewModel
|
||||
|
||||
val AppContext: Context inline get() = ServiceLocator.context
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
object ServiceLocator {
|
||||
|
||||
lateinit var context: Context
|
||||
val deContext by lazy { context.deviceProtectedContext }
|
||||
val timeoutPrefs by lazy { deContext.getSharedPreferences("su_timeout", 0) }
|
||||
|
||||
// Database
|
||||
val policyDB = PolicyDao()
|
||||
val settingsDB = SettingsDao()
|
||||
val stringDB = StringDao()
|
||||
val repoDB by lazy { createRepoDatabase(context).repoDao() }
|
||||
val sulogDB by lazy { createSuLogDatabase(deContext).suLogDao() }
|
||||
val repoUpdater by lazy { RepoUpdater(networkService, repoDB) }
|
||||
val logRepo by lazy { LogRepository(sulogDB) }
|
||||
|
||||
// Networking
|
||||
val okhttp by lazy { createOkHttpClient(context) }
|
||||
val retrofit by lazy { createRetrofit(okhttp) }
|
||||
val markwon by lazy { createMarkwon(context, okhttp) }
|
||||
val networkService by lazy {
|
||||
NetworkService(
|
||||
createApiService(retrofit, Const.Url.GITHUB_PAGE_URL),
|
||||
createApiService(retrofit, Const.Url.GITHUB_RAW_URL),
|
||||
createApiService(retrofit, Const.Url.JS_DELIVR_URL),
|
||||
createApiService(retrofit, Const.Url.GITHUB_API_URL)
|
||||
)
|
||||
}
|
||||
|
||||
object VMFactory : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(clz: Class<T>): T {
|
||||
return when (clz) {
|
||||
HomeViewModel::class.java -> HomeViewModel(networkService)
|
||||
LogViewModel::class.java -> LogViewModel(logRepo)
|
||||
ModuleViewModel::class.java -> ModuleViewModel(repoDB, repoUpdater)
|
||||
SettingsViewModel::class.java -> SettingsViewModel(repoDB)
|
||||
SuperuserViewModel::class.java -> SuperuserViewModel(policyDB)
|
||||
InstallViewModel::class.java -> InstallViewModel(networkService)
|
||||
SuRequestViewModel::class.java -> SuRequestViewModel(policyDB, timeoutPrefs)
|
||||
else -> clz.newInstance()
|
||||
} as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified VM : ViewModel> ViewModelStoreOwner.viewModel() =
|
||||
lazy(LazyThreadSafetyMode.NONE) {
|
||||
ViewModelProvider(this, ServiceLocator.VMFactory).get(VM::class.java)
|
||||
}
|
||||
|
||||
private fun createRepoDatabase(context: Context) =
|
||||
Room.databaseBuilder(context, RepoDatabase::class.java, "repo.db")
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
|
||||
private fun createSuLogDatabase(context: Context) =
|
||||
Room.databaseBuilder(context, SuLogDatabase::class.java, "sulogs.db")
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
@@ -1,34 +0,0 @@
|
||||
package com.topjohnwu.magisk.di
|
||||
|
||||
import com.topjohnwu.magisk.ui.MainViewModel
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragmentArgs
|
||||
import com.topjohnwu.magisk.ui.flash.FlashViewModel
|
||||
import com.topjohnwu.magisk.ui.hide.HideViewModel
|
||||
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
||||
import com.topjohnwu.magisk.ui.install.InstallViewModel
|
||||
import com.topjohnwu.magisk.ui.log.LogViewModel
|
||||
import com.topjohnwu.magisk.ui.module.ModuleViewModel
|
||||
import com.topjohnwu.magisk.ui.safetynet.SafetynetViewModel
|
||||
import com.topjohnwu.magisk.ui.settings.SettingsViewModel
|
||||
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
|
||||
import com.topjohnwu.magisk.ui.surequest.SuRequestViewModel
|
||||
import com.topjohnwu.magisk.ui.theme.ThemeViewModel
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
|
||||
val viewModelModules = module {
|
||||
viewModel { HideViewModel() }
|
||||
viewModel { HomeViewModel(get()) }
|
||||
viewModel { LogViewModel(get()) }
|
||||
viewModel { ModuleViewModel(get(), get()) }
|
||||
viewModel { SafetynetViewModel() }
|
||||
viewModel { SettingsViewModel(get()) }
|
||||
viewModel { SuperuserViewModel(get(), get()) }
|
||||
viewModel { ThemeViewModel() }
|
||||
viewModel { InstallViewModel(get()) }
|
||||
viewModel { MainViewModel() }
|
||||
|
||||
// Legacy
|
||||
viewModel { (args: FlashFragmentArgs) -> FlashViewModel(args) }
|
||||
viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) }
|
||||
}
|
@@ -29,10 +29,10 @@ object RebootEvent {
|
||||
fun inflateMenu(activity: BaseActivity): PopupMenu {
|
||||
val themeWrapper = ContextThemeWrapper(activity, R.style.Foundation_PopupMenu)
|
||||
val menu = PopupMenu(themeWrapper, activity.findViewById(R.id.action_reboot))
|
||||
activity.menuInflater.inflate(R.menu.menu_reboot, menu.menu)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
|
||||
activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true)
|
||||
menu.menu.getItem(R.id.action_reboot_userspace).isVisible = true
|
||||
activity.menuInflater.inflate(R.menu.menu_reboot, menu.menu)
|
||||
menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true
|
||||
menu.setOnMenuItemClickListener(::reboot)
|
||||
return menu
|
||||
}
|
||||
|
@@ -6,32 +6,33 @@ import com.google.android.material.snackbar.Snackbar
|
||||
import com.topjohnwu.magisk.arch.ActivityExecutor
|
||||
import com.topjohnwu.magisk.arch.BaseUIActivity
|
||||
import com.topjohnwu.magisk.arch.ViewEvent
|
||||
import com.topjohnwu.magisk.utils.TransitiveText
|
||||
import com.topjohnwu.magisk.utils.TextHolder
|
||||
import com.topjohnwu.magisk.utils.asText
|
||||
|
||||
class SnackbarEvent private constructor(
|
||||
private val msg: TransitiveText,
|
||||
private val length: Int,
|
||||
private val builder: Snackbar.() -> Unit
|
||||
class SnackbarEvent constructor(
|
||||
private val msg: TextHolder,
|
||||
private val length: Int = Snackbar.LENGTH_SHORT,
|
||||
private val builder: Snackbar.() -> Unit = {}
|
||||
) : ViewEvent(), ActivityExecutor {
|
||||
|
||||
constructor(
|
||||
@StringRes res: Int,
|
||||
length: Int = Snackbar.LENGTH_SHORT,
|
||||
builder: Snackbar.() -> Unit = {}
|
||||
) : this(TransitiveText.Res(res), length, builder)
|
||||
) : this(res.asText(), length, builder)
|
||||
|
||||
constructor(
|
||||
message: String,
|
||||
msg: String,
|
||||
length: Int = Snackbar.LENGTH_SHORT,
|
||||
builder: Snackbar.() -> Unit = {}
|
||||
) : this(TransitiveText.String(message), length, builder)
|
||||
) : this(msg.asText(), length, builder)
|
||||
|
||||
|
||||
private fun snackbar(
|
||||
view: View,
|
||||
message: String,
|
||||
length: Int = Snackbar.LENGTH_SHORT,
|
||||
builder: Snackbar.() -> Unit = {}
|
||||
length: Int,
|
||||
builder: Snackbar.() -> Unit
|
||||
) = Snackbar.make(view, message, length).apply(builder).show()
|
||||
|
||||
override fun invoke(activity: BaseUIActivity<*, *>) {
|
||||
@@ -39,5 +40,4 @@ class SnackbarEvent private constructor(
|
||||
msg.getText(activity.resources).toString(),
|
||||
length, builder)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,28 +1,25 @@
|
||||
package com.topjohnwu.magisk.events.dialog
|
||||
|
||||
import android.content.Context
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import org.koin.core.get
|
||||
import org.koin.core.inject
|
||||
import java.io.File
|
||||
|
||||
class ManagerInstallDialog : MarkDownDialog() {
|
||||
|
||||
private val svc: NetworkService by inject()
|
||||
private val svc get() = ServiceLocator.networkService
|
||||
|
||||
override suspend fun getMarkdownText(): String {
|
||||
val text = svc.fetchString(Info.remote.magisk.note)
|
||||
// Cache the changelog
|
||||
val context = get<Context>()
|
||||
context.cacheDir.listFiles { _, name -> name.endsWith(".md") }.orEmpty().forEach {
|
||||
AppContext.cacheDir.listFiles { _, name -> name.endsWith(".md") }.orEmpty().forEach {
|
||||
it.delete()
|
||||
}
|
||||
File(context.cacheDir, "${Info.remote.magisk.versionCode}.md").writeText(text)
|
||||
File(AppContext.cacheDir, "${Info.remote.magisk.versionCode}.md").writeText(text)
|
||||
return text
|
||||
}
|
||||
|
||||
|
@@ -6,19 +6,15 @@ import androidx.annotation.CallSuper
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import io.noties.markwon.Markwon
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
abstract class MarkDownDialog : DialogEvent(), KoinComponent {
|
||||
|
||||
private val markwon: Markwon by inject()
|
||||
abstract class MarkDownDialog : DialogEvent() {
|
||||
|
||||
abstract suspend fun getMarkdownText(): String
|
||||
|
||||
@@ -31,7 +27,7 @@ abstract class MarkDownDialog : DialogEvent(), KoinComponent {
|
||||
val tv = view.findViewById<TextView>(R.id.md_txt)
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
markwon.setMarkdown(tv, getMarkdownText())
|
||||
ServiceLocator.markwon.setMarkdown(tv, getMarkdownText())
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException)
|
||||
throw e
|
||||
|
@@ -3,6 +3,7 @@ package com.topjohnwu.magisk.events.dialog
|
||||
import android.app.ProgressDialog
|
||||
import android.widget.Toast
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseUIActivity
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
@@ -41,7 +42,8 @@ class UninstallDialog : DialogEvent() {
|
||||
}
|
||||
|
||||
private fun completeUninstall() {
|
||||
FlashFragment.uninstall()
|
||||
(dialog.ownerActivity as? BaseUIActivity<*, *>)
|
||||
?.navigation?.navigate(FlashFragment.uninstall())
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -22,7 +22,6 @@ import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.system.Os
|
||||
import android.text.PrecomputedText
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -57,26 +56,8 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Array as JArray
|
||||
|
||||
val packageName: String get() = get<Context>().packageName
|
||||
|
||||
private lateinit var osSymlink: Method
|
||||
private lateinit var os: Any
|
||||
|
||||
fun symlink(oldPath: String, newPath: String) {
|
||||
if (SDK_INT >= 21) {
|
||||
Os.symlink(oldPath, newPath)
|
||||
} else {
|
||||
if (!::osSymlink.isInitialized) {
|
||||
os = Class.forName("libcore.io.Libcore").getField("os").get(null)!!
|
||||
osSymlink = os.javaClass.getMethod("symlink", String::class.java, String::class.java)
|
||||
}
|
||||
osSymlink.invoke(os, oldPath, newPath)
|
||||
}
|
||||
}
|
||||
|
||||
val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0
|
||||
|
||||
@get:SuppressLint("InlinedApi")
|
||||
@@ -264,8 +245,7 @@ fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
|
||||
|
||||
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
|
||||
|
||||
@Suppress("FunctionName")
|
||||
inline fun <reified T> T.DynamicClassLoader(apk: File) =
|
||||
inline fun <reified T> T.createClassLoader(apk: File) =
|
||||
DynamicClassLoader(apk, T::class.java.classLoader)
|
||||
|
||||
fun Context.unwrap(): Context {
|
||||
@@ -339,16 +319,6 @@ var TextView.precomputedText: CharSequence
|
||||
set(value) {
|
||||
val callback = tag as? Runnable
|
||||
|
||||
// Don't even bother pre 21
|
||||
if (SDK_INT < 21) {
|
||||
post {
|
||||
text = value
|
||||
isGone = false
|
||||
callback?.run()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
if (SDK_INT >= 29) {
|
||||
// Internally PrecomputedTextCompat will use platform API on API 29+
|
||||
|
@@ -1,11 +1,11 @@
|
||||
package com.topjohnwu.magisk.ktx
|
||||
|
||||
import android.os.Build
|
||||
import androidx.collection.SparseArrayCompat
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.lang.reflect.Field
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.zip.ZipEntry
|
||||
@@ -47,64 +47,8 @@ fun <T> MutableList<T>.synchronized() = Collections.synchronizedList(this)
|
||||
fun <T> MutableSet<T>.synchronized() = Collections.synchronizedSet(this)
|
||||
fun <K, V> MutableMap<K, V>.synchronized() = Collections.synchronizedMap(this)
|
||||
|
||||
fun String.langTagToLocale(): Locale {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
return Locale.forLanguageTag(this)
|
||||
} else {
|
||||
val tok = split("[-_]".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
if (tok.isEmpty()) {
|
||||
return Locale("")
|
||||
}
|
||||
val language = when (tok[0]) {
|
||||
"und" -> "" // Undefined
|
||||
"fil" -> "tl" // Filipino
|
||||
else -> tok[0]
|
||||
}
|
||||
if (language.length != 2 && language.length != 3)
|
||||
return Locale("")
|
||||
if (tok.size == 1)
|
||||
return Locale(language)
|
||||
val country = tok[1]
|
||||
|
||||
return if (country.length != 2 && country.length != 3) Locale(language)
|
||||
else Locale(language, country)
|
||||
}
|
||||
}
|
||||
|
||||
fun Locale.toLangTag(): String {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
return toLanguageTag()
|
||||
} else {
|
||||
var language = language
|
||||
var country = country
|
||||
var variant = variant
|
||||
when {
|
||||
language.isEmpty() || !language.matches("\\p{Alpha}{2,8}".toRegex()) ->
|
||||
language = "und" // Follow the Locale#toLanguageTag() implementation
|
||||
language == "iw" -> language = "he" // correct deprecated "Hebrew"
|
||||
language == "in" -> language = "id" // correct deprecated "Indonesian"
|
||||
language == "ji" -> language = "yi" // correct deprecated "Yiddish"
|
||||
}
|
||||
// ensure valid country code, if not well formed, it's omitted
|
||||
|
||||
// variant subtags that begin with a letter must be at least 5 characters long
|
||||
// ensure valid country code, if not well formed, it's omitted
|
||||
if (!country.matches("\\p{Alpha}{2}|\\p{Digit}{3}".toRegex())) {
|
||||
country = ""
|
||||
}
|
||||
|
||||
// variant subtags that begin with a letter must be at least 5 characters long
|
||||
if (!variant.matches("\\p{Alnum}{5,8}|\\p{Digit}\\p{Alnum}{3}".toRegex())) {
|
||||
variant = ""
|
||||
}
|
||||
val tag = StringBuilder(language)
|
||||
if (country.isNotEmpty())
|
||||
tag.append('-').append(country)
|
||||
if (variant.isNotEmpty())
|
||||
tag.append('-').append(variant)
|
||||
return tag.toString()
|
||||
}
|
||||
}
|
||||
|
||||
fun SimpleDateFormat.parseOrNull(date: String) =
|
||||
runCatching { parse(date) }.onFailure { Timber.e(it) }.getOrNull()
|
||||
|
||||
fun Class<*>.reflectField(name: String): Field =
|
||||
getDeclaredField(name).apply { isAccessible = true }
|
||||
|
@@ -1,17 +0,0 @@
|
||||
package com.topjohnwu.magisk.ktx
|
||||
|
||||
import org.koin.core.context.KoinContextHandler
|
||||
import org.koin.core.parameter.ParametersDefinition
|
||||
import org.koin.core.qualifier.Qualifier
|
||||
|
||||
fun getKoin() = KoinContextHandler.get()
|
||||
|
||||
inline fun <reified T> inject(
|
||||
qualifier: Qualifier? = null,
|
||||
noinline parameters: ParametersDefinition? = null
|
||||
) = lazy { get<T>(qualifier, parameters) }
|
||||
|
||||
inline fun <reified T> get(
|
||||
qualifier: Qualifier? = null,
|
||||
noinline parameters: ParametersDefinition? = null
|
||||
): T = getKoin().get(qualifier, parameters)
|
@@ -1,8 +1,5 @@
|
||||
package com.topjohnwu.magisk.ktx
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
|
||||
val specialChars = arrayOf('!', '@', '#', '$', '%', '&', '?')
|
||||
val fullSpecialChars = arrayOf('!', '@', '#', '$', '%', '&', '?')
|
||||
|
||||
@@ -13,10 +10,7 @@ fun String.isCJK(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
fun isCJK(codepoint: Int): Boolean {
|
||||
return if (Build.VERSION.SDK_INT < 19) false /* Pre 5.0 don't need to be pretty.. */
|
||||
else Character.isIdeographic(codepoint)
|
||||
}
|
||||
fun isCJK(codepoint: Int) = Character.isIdeographic(codepoint)
|
||||
|
||||
fun String.replaceRandomWithSpecial(passes: Int): String {
|
||||
var string = this
|
||||
@@ -38,11 +32,6 @@ fun String.replaceRandomWithSpecial(): String {
|
||||
fun StringBuilder.appendIf(condition: Boolean, builder: StringBuilder.() -> Unit) =
|
||||
if (condition) apply(builder) else this
|
||||
|
||||
fun Int.res(vararg args: Any): String {
|
||||
val resources: Resources by inject()
|
||||
return resources.getString(this, *args)
|
||||
}
|
||||
|
||||
fun String.trimEmptyToNull(): String? = if (isBlank()) null else this
|
||||
|
||||
fun String.legalFilename() = replace(" ", "_").replace("'", "").replace("\"", "")
|
||||
|
@@ -2,7 +2,6 @@ package com.topjohnwu.magisk.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
@@ -19,13 +18,13 @@ import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.arch.ReselectionTarget
|
||||
import com.topjohnwu.magisk.core.*
|
||||
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
|
||||
import com.topjohnwu.magisk.di.viewModel
|
||||
import com.topjohnwu.magisk.ktx.startAnimations
|
||||
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
|
||||
import com.topjohnwu.magisk.utils.HideableBehavior
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import com.topjohnwu.magisk.view.Shortcuts
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import java.io.File
|
||||
|
||||
class MainViewModel : BaseViewModel()
|
||||
@@ -34,7 +33,7 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
|
||||
|
||||
override val layoutRes = R.layout.activity_main_md2
|
||||
override val viewModel by viewModel<MainViewModel>()
|
||||
override val navHost: Int = R.id.main_nav_host
|
||||
override val navHostId: Int = R.id.main_nav_host
|
||||
|
||||
private var isRootFragment = true
|
||||
|
||||
@@ -123,10 +122,7 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
|
||||
val topView = binding.mainToolbarWrapper
|
||||
val bottomView = binding.mainBottomBar
|
||||
|
||||
if (
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
|
||||
!binding.mainBottomBar.isAttachedToWindow
|
||||
) {
|
||||
if (!binding.mainBottomBar.isAttachedToWindow) {
|
||||
binding.mainBottomBar.viewTreeObserver.addOnWindowAttachListener(object :
|
||||
ViewTreeObserver.OnWindowAttachListener {
|
||||
|
||||
@@ -166,9 +162,9 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
|
||||
|
||||
private fun getScreen(name: String?): NavDirections? {
|
||||
return when (name) {
|
||||
Const.Nav.SUPERUSER -> HomeFragmentDirections.actionSuperuserFragment()
|
||||
Const.Nav.HIDE -> HomeFragmentDirections.actionHideFragment()
|
||||
Const.Nav.MODULES -> HomeFragmentDirections.actionModuleFragment()
|
||||
Const.Nav.SUPERUSER -> MainDirections.actionSuperuserFragment()
|
||||
Const.Nav.HIDE -> MainDirections.actionHideFragment()
|
||||
Const.Nav.MODULES -> MainDirections.actionModuleFragment()
|
||||
Const.Nav.SETTINGS -> HomeFragmentDirections.actionHomeFragmentToSettingsFragment()
|
||||
else -> null
|
||||
}
|
||||
|
@@ -7,26 +7,27 @@ import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import androidx.navigation.NavDeepLinkBuilder
|
||||
import com.topjohnwu.magisk.MainDirections
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseUIActivity
|
||||
import com.topjohnwu.magisk.arch.BaseUIFragment
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.cmp
|
||||
import com.topjohnwu.magisk.databinding.FragmentFlashMd2Binding
|
||||
import com.topjohnwu.magisk.di.viewModel
|
||||
import com.topjohnwu.magisk.ui.MainActivity
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import com.topjohnwu.magisk.MainDirections.Companion.actionFlashFragment as toFlash
|
||||
|
||||
class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>() {
|
||||
|
||||
override val layoutRes = R.layout.fragment_flash_md2
|
||||
override val viewModel by viewModel<FlashViewModel> {
|
||||
parametersOf(FlashFragmentArgs.fromBundle(requireArguments()))
|
||||
}
|
||||
override val viewModel by viewModel<FlashViewModel>()
|
||||
|
||||
private var defaultOrientation = -1
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
viewModel.args = FlashFragmentArgs.fromBundle(requireArguments())
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setHasOptionsMenu(true)
|
||||
@@ -50,6 +51,7 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
|
||||
|
||||
defaultOrientation = activity.requestedOrientation
|
||||
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||
viewModel.startFlashing()
|
||||
}
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
@@ -90,22 +92,22 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
|
||||
|
||||
/* Flashing is understood as installing / flashing magisk itself */
|
||||
|
||||
fun flash(isSecondSlot: Boolean) = toFlash(
|
||||
fun flash(isSecondSlot: Boolean) = MainDirections.actionFlashFragment(
|
||||
action = flashType(isSecondSlot)
|
||||
).let { BaseUIActivity.postDirections(it) }
|
||||
)
|
||||
|
||||
/* Patching is understood as injecting img files with magisk */
|
||||
|
||||
fun patch(uri: Uri) = toFlash(
|
||||
fun patch(uri: Uri) = MainDirections.actionFlashFragment(
|
||||
action = Const.Value.PATCH_FILE,
|
||||
additionalData = uri
|
||||
).let { BaseUIActivity.postDirections(it) }
|
||||
)
|
||||
|
||||
/* Uninstalling is understood as removing magisk entirely */
|
||||
|
||||
fun uninstall() = toFlash(
|
||||
fun uninstall() = MainDirections.actionFlashFragment(
|
||||
action = Const.Value.UNINSTALL
|
||||
).let { BaseUIActivity.postDirections(it) }
|
||||
)
|
||||
|
||||
/* Installing is understood as flashing modules / zips */
|
||||
|
||||
@@ -115,11 +117,11 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
|
||||
dismissId = id
|
||||
).let { createIntent(context, it) }
|
||||
|
||||
fun install(file: Uri, id: Int) = toFlash(
|
||||
fun install(file: Uri, id: Int) = MainDirections.actionFlashFragment(
|
||||
action = Const.Value.FLASH_ZIP,
|
||||
additionalData = file,
|
||||
dismissId = id
|
||||
).let { BaseUIActivity.postDirections(it) }
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package com.topjohnwu.magisk.ui.flash
|
||||
|
||||
import android.net.Uri
|
||||
import android.view.MenuItem
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.lifecycle.LiveData
|
||||
@@ -27,7 +26,7 @@ import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class FlashViewModel(args: FlashFragmentArgs) : BaseViewModel() {
|
||||
class FlashViewModel : BaseViewModel() {
|
||||
|
||||
@get:Bindable
|
||||
var showReboot = Shell.rootAccess()
|
||||
@@ -39,6 +38,7 @@ class FlashViewModel(args: FlashFragmentArgs) : BaseViewModel() {
|
||||
val adapter = RvBindingAdapter<ConsoleItem>()
|
||||
val items = diffListOf<ConsoleItem>()
|
||||
val itemBinding = itemBindingOf<ConsoleItem>()
|
||||
lateinit var args: FlashFragmentArgs
|
||||
|
||||
private val logItems = mutableListOf<String>().synchronized()
|
||||
private val outItems = object : CallbackList<String>() {
|
||||
@@ -49,14 +49,11 @@ class FlashViewModel(args: FlashFragmentArgs) : BaseViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
fun startFlashing() {
|
||||
val (action, uri, id) = args
|
||||
if (id != -1)
|
||||
Notifications.mgr.cancel(id)
|
||||
startFlashing(action, uri)
|
||||
}
|
||||
|
||||
private fun startFlashing(action: String, uri: Uri?) {
|
||||
viewModelScope.launch {
|
||||
val result = when (action) {
|
||||
Const.Value.FLASH_ZIP -> {
|
||||
|
@@ -12,12 +12,12 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseUIFragment
|
||||
import com.topjohnwu.magisk.databinding.FragmentHideMd2Binding
|
||||
import com.topjohnwu.magisk.di.viewModel
|
||||
import com.topjohnwu.magisk.ktx.addSimpleItemDecoration
|
||||
import com.topjohnwu.magisk.ktx.addVerticalPadding
|
||||
import com.topjohnwu.magisk.ktx.fixEdgeEffect
|
||||
import com.topjohnwu.magisk.ktx.hideKeyboard
|
||||
import com.topjohnwu.magisk.utils.MotionRevealHelper
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class HideFragment : BaseUIFragment<HideViewModel, FragmentHideMd2Binding>() {
|
||||
|
||||
|
@@ -7,6 +7,7 @@ import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.*
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import com.topjohnwu.magisk.ktx.getLabel
|
||||
import com.topjohnwu.magisk.ktx.isIsolated
|
||||
@@ -59,7 +60,7 @@ class HideAppInfo(info: ApplicationInfo, pm: PackageManager, hideList: List<Cmdl
|
||||
|
||||
val hidden = hideList.filter { it.packageName == packageName || it.packageName == ISOLATED_MAGIC }
|
||||
fun createProcess(name: String, pkg: String = packageName): HideProcessInfo {
|
||||
return HideProcessInfo(name, pkg, hidden.any { it.process == name })
|
||||
return HideProcessInfo(name, pkg, hidden.any { it.process == name && it.packageName == pkg })
|
||||
}
|
||||
|
||||
var haveAppZygote = false
|
||||
@@ -71,7 +72,8 @@ class HideAppInfo(info: ApplicationInfo, pm: PackageManager, hideList: List<Cmdl
|
||||
// Using app zygote, don't need to track the process
|
||||
null
|
||||
} else {
|
||||
createProcess("${it.processName}:${it.name}", ISOLATED_MAGIC)
|
||||
val proc = if (SDK_INT >= 29) "${it.processName}:${it.name}" else it.processName
|
||||
createProcess(proc, ISOLATED_MAGIC)
|
||||
}
|
||||
} else {
|
||||
createProcess(it.processName)
|
||||
@@ -84,7 +86,7 @@ class HideAppInfo(info: ApplicationInfo, pm: PackageManager, hideList: List<Cmdl
|
||||
receivers?.processes().orEmpty() +
|
||||
providers?.processes().orEmpty() +
|
||||
listOf(if (haveAppZygote) createProcess("${processName}_zygote") else null)
|
||||
}.filterNotNull().distinctBy { it.name }.sortedBy { it.name }
|
||||
}.filterNotNull().distinct().sortedBy { it.name }
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -100,6 +102,6 @@ data class HideProcessInfo(
|
||||
val packageName: String,
|
||||
var isHidden: Boolean
|
||||
) {
|
||||
val isIsolated get() = name == ISOLATED_MAGIC
|
||||
val isIsolated get() = packageName == ISOLATED_MAGIC
|
||||
val isAppZygote get() = name.endsWith("_zygote")
|
||||
}
|
||||
|
@@ -93,13 +93,15 @@ class HideProcessRvItem(
|
||||
|
||||
override val layoutRes get() = R.layout.item_hide_process_md2
|
||||
|
||||
val displayName = if (process.isIsolated) "(isolated) ${process.name}" else process.name
|
||||
|
||||
@get:Bindable
|
||||
var isHidden
|
||||
get() = process.isHidden
|
||||
set(value) = set(value, process.isHidden, { process.isHidden = it }, BR.hidden) {
|
||||
val arg = if (it) "add" else "rm"
|
||||
val (name, pkg) = process
|
||||
Shell.su("magiskhide --$arg $pkg $name").submit()
|
||||
Shell.su("magiskhide $arg $pkg \'$name\'").submit()
|
||||
}
|
||||
|
||||
fun toggle() {
|
||||
@@ -109,7 +111,10 @@ class HideProcessRvItem(
|
||||
val defaultSelection get() =
|
||||
process.isIsolated || process.isAppZygote || process.name == process.packageName
|
||||
|
||||
override fun contentSameAs(other: HideProcessRvItem) = process == other.process
|
||||
override fun itemSameAs(other: HideProcessRvItem) = process.name == other.process.name
|
||||
override fun contentSameAs(other: HideProcessRvItem) =
|
||||
process.isHidden == other.process.isHidden
|
||||
|
||||
override fun itemSameAs(other: HideProcessRvItem) =
|
||||
process.name == other.process.name && process.packageName == other.process.packageName
|
||||
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ package com.topjohnwu.magisk.ui.hide
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
|
||||
import android.os.Process
|
||||
import androidx.databinding.Bindable
|
||||
@@ -13,8 +12,7 @@ import com.topjohnwu.magisk.arch.Queryable
|
||||
import com.topjohnwu.magisk.arch.filterableListOf
|
||||
import com.topjohnwu.magisk.arch.itemBindingOf
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.ktx.get
|
||||
import com.topjohnwu.magisk.ktx.packageName
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import com.topjohnwu.superuser.Shell
|
||||
@@ -61,13 +59,14 @@ class HideViewModel : BaseViewModel(), Queryable {
|
||||
}
|
||||
state = State.LOADING
|
||||
val (apps, diff) = withContext(Dispatchers.Default) {
|
||||
val pm = get<PackageManager>()
|
||||
val hideList = Shell.su("magiskhide --ls").exec().out.map { CmdlineHiddenItem(it) }
|
||||
val pm = AppContext.packageManager
|
||||
val hideList = Shell.su("magiskhide ls").exec().out.map { CmdlineHiddenItem(it) }
|
||||
val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES)
|
||||
.asSequence()
|
||||
.filter { it.enabled && !blacklist.contains(it.packageName) }
|
||||
.filterNot { blacklist.contains(it.packageName) }
|
||||
.map { HideAppInfo(it, pm, hideList) }
|
||||
.filter { it.processes.isNotEmpty() }
|
||||
.filter { info -> info.enabled || info.processes.any { it.isHidden } }
|
||||
.map { HideRvItem(it) }
|
||||
.toList()
|
||||
.sorted()
|
||||
@@ -112,7 +111,7 @@ class HideViewModel : BaseViewModel(), Queryable {
|
||||
|
||||
companion object {
|
||||
private val blacklist by lazy { listOf(
|
||||
packageName,
|
||||
AppContext.packageName,
|
||||
"com.android.chrome",
|
||||
"com.chrome.beta",
|
||||
"com.chrome.dev",
|
||||
|
@@ -8,9 +8,9 @@ import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseUIFragment
|
||||
import com.topjohnwu.magisk.core.download.BaseDownloader
|
||||
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
|
||||
import com.topjohnwu.magisk.di.viewModel
|
||||
import com.topjohnwu.magisk.events.RebootEvent
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class HomeFragment : BaseUIFragment<HomeViewModel, FragmentHomeMd2Binding>() {
|
||||
|
||||
|
@@ -16,7 +16,7 @@ import com.topjohnwu.magisk.events.dialog.EnvFixDialog
|
||||
import com.topjohnwu.magisk.events.dialog.ManagerInstallDialog
|
||||
import com.topjohnwu.magisk.events.dialog.UninstallDialog
|
||||
import com.topjohnwu.magisk.ktx.await
|
||||
import com.topjohnwu.magisk.utils.asTransitive
|
||||
import com.topjohnwu.magisk.utils.asText
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -54,13 +54,13 @@ class HomeViewModel(
|
||||
|
||||
val magiskInstalledVersion get() = Info.env.run {
|
||||
if (isActive)
|
||||
"$magiskVersionString ($magiskVersionCode)".asTransitive()
|
||||
"$magiskVersionString ($magiskVersionCode)".asText()
|
||||
else
|
||||
R.string.not_available.asTransitive()
|
||||
R.string.not_available.asText()
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var managerRemoteVersion = R.string.loading.asTransitive()
|
||||
var managerRemoteVersion = R.string.loading.asText()
|
||||
set(value) = set(value, field, { field = it }, BR.managerRemoteVersion)
|
||||
|
||||
val managerInstalledVersion = Info.stub?.let {
|
||||
@@ -92,14 +92,14 @@ class HomeViewModel(
|
||||
}
|
||||
|
||||
managerRemoteVersion =
|
||||
"${magisk.version} (${magisk.versionCode}) (${stub.versionCode})".asTransitive()
|
||||
"${magisk.version} (${magisk.versionCode}) (${stub.versionCode})".asText()
|
||||
|
||||
launch {
|
||||
ensureEnv()
|
||||
}
|
||||
} ?: {
|
||||
state = State.LOADING_FAILED
|
||||
managerRemoteVersion = R.string.not_available.asTransitive()
|
||||
managerRemoteVersion = R.string.not_available.asText()
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -127,11 +127,11 @@ class HomeViewModel(
|
||||
}
|
||||
|
||||
fun onMagiskPressed() = withExternalRW {
|
||||
HomeFragmentDirections.actionHomeFragmentToInstallFragment().publish()
|
||||
HomeFragmentDirections.actionHomeFragmentToInstallFragment().navigate()
|
||||
}
|
||||
|
||||
fun onSafetyNetPressed() =
|
||||
HomeFragmentDirections.actionHomeFragmentToSafetynetFragment().publish()
|
||||
HomeFragmentDirections.actionHomeFragmentToSafetynetFragment().navigate()
|
||||
|
||||
fun hideNotice() {
|
||||
Config.safetyNotice = false
|
||||
|
@@ -26,9 +26,7 @@ open class LayoutInflaterFactory(private val delegate: AppCompatDelegate) : Layo
|
||||
open fun onViewCreated(view: View?, parent: View?, name: String, context: Context, attrs: AttributeSet) {
|
||||
if (view == null) return
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
WindowInsetsHelper.attach(view, attrs)
|
||||
}
|
||||
WindowInsetsHelper.attach(view, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,4 +83,4 @@ private object LayoutInflaterFactoryDefaultImpl {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,8 +8,8 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseUIFragment
|
||||
import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding
|
||||
import com.topjohnwu.magisk.di.viewModel
|
||||
import com.topjohnwu.magisk.ktx.coroutineScope
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class InstallFragment : BaseUIFragment<InstallViewModel, FragmentInstallMd2Binding>() {
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
package com.topjohnwu.magisk.ui.install
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@@ -12,13 +11,13 @@ import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.events.MagiskInstallFileEvent
|
||||
import com.topjohnwu.magisk.events.dialog.SecondSlotWarningDialog
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.get
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
@@ -65,8 +64,7 @@ class InstallViewModel(
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val context = get<Context>()
|
||||
File(context.cacheDir, "${BuildConfig.VERSION_CODE}.md").run {
|
||||
File(AppContext.cacheDir, "${BuildConfig.VERSION_CODE}.md").run {
|
||||
notes = when {
|
||||
exists() -> readText()
|
||||
Const.Url.CHANGELOG_URL.isEmpty() -> ""
|
||||
@@ -89,9 +87,9 @@ class InstallViewModel(
|
||||
|
||||
fun install() {
|
||||
when (method) {
|
||||
R.id.method_patch -> FlashFragment.patch(data!!)
|
||||
R.id.method_direct -> FlashFragment.flash(false)
|
||||
R.id.method_inactive_slot -> FlashFragment.flash(true)
|
||||
R.id.method_patch -> FlashFragment.patch(data!!).navigate()
|
||||
R.id.method_direct -> FlashFragment.flash(false).navigate()
|
||||
R.id.method_inactive_slot -> FlashFragment.flash(true).navigate()
|
||||
else -> error("Unknown value")
|
||||
}
|
||||
state = State.LOADING
|
||||
|
@@ -9,12 +9,12 @@ import androidx.core.view.isVisible
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseUIFragment
|
||||
import com.topjohnwu.magisk.databinding.FragmentLogMd2Binding
|
||||
import com.topjohnwu.magisk.di.viewModel
|
||||
import com.topjohnwu.magisk.ktx.addSimpleItemDecoration
|
||||
import com.topjohnwu.magisk.ktx.addVerticalPadding
|
||||
import com.topjohnwu.magisk.ktx.fixEdgeEffect
|
||||
import com.topjohnwu.magisk.ui.MainActivity
|
||||
import com.topjohnwu.magisk.utils.MotionRevealHelper
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class LogFragment : BaseUIFragment<LogViewModel, FragmentLogMd2Binding>() {
|
||||
|
||||
|
@@ -13,11 +13,11 @@ import com.topjohnwu.magisk.arch.ReselectionTarget
|
||||
import com.topjohnwu.magisk.arch.ViewEvent
|
||||
import com.topjohnwu.magisk.core.download.BaseDownloader
|
||||
import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding
|
||||
import com.topjohnwu.magisk.di.viewModel
|
||||
import com.topjohnwu.magisk.ktx.*
|
||||
import com.topjohnwu.magisk.ui.MainActivity
|
||||
import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener
|
||||
import com.topjohnwu.magisk.utils.MotionRevealHelper
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class ModuleFragment : BaseUIFragment<ModuleViewModel, FragmentModuleMd2Binding>(),
|
||||
ReselectionTarget {
|
||||
|
@@ -1,123 +1,130 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.topjohnwu.magisk.ui.safetynet
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.ContextExecutor
|
||||
import com.topjohnwu.magisk.arch.ViewEventWithScope
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.ktx.DynamicClassLoader
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ktx.createClassLoader
|
||||
import com.topjohnwu.magisk.ktx.reflectField
|
||||
import com.topjohnwu.magisk.ktx.writeTo
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import com.topjohnwu.signing.CryptoUtils
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import dalvik.system.BaseDexClassLoader
|
||||
import dalvik.system.DexFile
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import org.bouncycastle.asn1.ASN1Encoding
|
||||
import org.bouncycastle.asn1.ASN1Primitive
|
||||
import org.bouncycastle.est.jcajce.JsseDefaultHostnameAuthorizer
|
||||
import timber.log.Timber
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.lang.reflect.InvocationHandler
|
||||
import java.lang.reflect.Proxy
|
||||
import java.security.SecureRandom
|
||||
import java.security.Signature
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class CheckSafetyNetEvent(
|
||||
private val callback: (SafetyNetResult) -> Unit = {}
|
||||
) : ViewEventWithScope(), ContextExecutor, KoinComponent, SafetyNetHelper.Callback {
|
||||
) : ViewEventWithScope(), ContextExecutor, SafetyNetHelper.Callback {
|
||||
|
||||
private val svc by inject<NetworkService>()
|
||||
private val svc get() = ServiceLocator.networkService
|
||||
|
||||
private lateinit var apk: File
|
||||
private lateinit var dex: File
|
||||
private lateinit var jar: File
|
||||
private lateinit var nonce: ByteArray
|
||||
|
||||
override fun invoke(context: Context) {
|
||||
apk = File("${context.filesDir.parent}/snet", "snet.jar")
|
||||
dex = File(apk.parent, "snet.dex")
|
||||
jar = File("${context.filesDir.parent}/snet", "snet.jar")
|
||||
|
||||
scope.launch {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
attest(context) {
|
||||
// Download and retry
|
||||
withContext(Dispatchers.IO) {
|
||||
Shell.sh("rm -rf " + apk.parent).exec()
|
||||
apk.parentFile?.mkdir()
|
||||
Shell.sh("rm -rf " + jar.parent).exec()
|
||||
jar.parentFile?.mkdir()
|
||||
withContext(Dispatchers.Main) {
|
||||
showDialog(context)
|
||||
}
|
||||
download(context, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun attest(context: Context, onError: suspend (Exception) -> Unit) {
|
||||
val helper: SafetyNetHelper
|
||||
try {
|
||||
val helper = withContext(Dispatchers.IO) {
|
||||
val loader = DynamicClassLoader(apk)
|
||||
val dex = DexFile.loadDex(apk.path, dex.path, 0)
|
||||
val loader = createClassLoader(jar)
|
||||
|
||||
// Scan through the dex and find our helper class
|
||||
var helperClass: Class<*>? = null
|
||||
for (className in dex.entries()) {
|
||||
if (className.startsWith("x.")) {
|
||||
val cls = loader.loadClass(className)
|
||||
if (InvocationHandler::class.java.isAssignableFrom(cls)) {
|
||||
helperClass = cls
|
||||
break
|
||||
}
|
||||
// Scan through the dex and find our helper class
|
||||
var clazz: Class<*>? = null
|
||||
loop@for (dex in loader.getDexFiles()) {
|
||||
for (name in dex.entries()) {
|
||||
val cls = loader.loadClass(name)
|
||||
if (InvocationHandler::class.java.isAssignableFrom(cls)) {
|
||||
clazz = cls
|
||||
break@loop
|
||||
}
|
||||
}
|
||||
helperClass ?: throw Exception()
|
||||
|
||||
val helper = helperClass
|
||||
.getMethod("get", Class::class.java, Context::class.java, Any::class.java)
|
||||
.invoke(
|
||||
null, SafetyNetHelper::class.java,
|
||||
context, this@CheckSafetyNetEvent
|
||||
) as SafetyNetHelper
|
||||
|
||||
if (helper.version < Const.SNET_EXT_VER)
|
||||
throw Exception()
|
||||
helper
|
||||
}
|
||||
helper.attest()
|
||||
clazz ?: throw Exception("Cannot find SafetyNetHelper implementation")
|
||||
|
||||
helper = Proxy.newProxyInstance(
|
||||
loader, arrayOf(SafetyNetHelper::class.java),
|
||||
clazz.newInstance() as InvocationHandler) as SafetyNetHelper
|
||||
|
||||
if (helper.version != Const.SNET_EXT_VER)
|
||||
throw Exception("snet extension version mismatch")
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException)
|
||||
throw e
|
||||
onError(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun download(context: Context, askUser: Boolean) {
|
||||
fun downloadInternal() = scope.launch {
|
||||
val abort: suspend (Exception) -> Unit = {
|
||||
Timber.e(it)
|
||||
callback(SafetyNetResult())
|
||||
}
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
svc.fetchSafetynet().byteStream().writeTo(apk)
|
||||
}
|
||||
attest(context, abort)
|
||||
} catch (e: IOException) {
|
||||
if (e is CancellationException)
|
||||
throw e
|
||||
abort(e)
|
||||
}
|
||||
}
|
||||
|
||||
if (!askUser) {
|
||||
downloadInternal()
|
||||
return
|
||||
}
|
||||
|
||||
val random = SecureRandom()
|
||||
nonce = ByteArray(24)
|
||||
random.nextBytes(nonce)
|
||||
helper.attest(context, nonce, this)
|
||||
}
|
||||
|
||||
// All of these fields are whitelisted
|
||||
private fun BaseDexClassLoader.getDexFiles(): List<DexFile> {
|
||||
val pathList = BaseDexClassLoader::class.java.reflectField("pathList").get(this)
|
||||
val dexElements = pathList.javaClass.reflectField("dexElements").get(pathList) as Array<*>
|
||||
val fileField = dexElements.javaClass.componentType.reflectField("dexFile")
|
||||
return dexElements.map { fileField.get(it) as DexFile }
|
||||
}
|
||||
|
||||
private fun download(context: Context) = scope.launch(Dispatchers.IO) {
|
||||
val abort: suspend (Exception) -> Unit = {
|
||||
Timber.e(it)
|
||||
withContext(Dispatchers.Main) {
|
||||
callback(SafetyNetResult())
|
||||
}
|
||||
}
|
||||
try {
|
||||
svc.fetchSafetynet().byteStream().writeTo(jar)
|
||||
attest(context, abort)
|
||||
} catch (e: IOException) {
|
||||
abort(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDialog(context: Context) {
|
||||
MagiskDialog(context)
|
||||
.applyTitle(R.string.proprietary_title)
|
||||
.applyMessage(R.string.proprietary_notice)
|
||||
.cancellable(false)
|
||||
.applyButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||
titleRes = android.R.string.ok
|
||||
onClick { downloadInternal() }
|
||||
onClick { download(context) }
|
||||
}
|
||||
.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||
titleRes = android.R.string.cancel
|
||||
@@ -129,7 +136,90 @@ class CheckSafetyNetEvent(
|
||||
.reveal()
|
||||
}
|
||||
|
||||
override fun onResponse(response: JSONObject?) {
|
||||
callback(SafetyNetResult(response))
|
||||
private fun String.decode(): ByteArray {
|
||||
return if (contains("[+/]".toRegex()))
|
||||
Base64.decode(this, Base64.DEFAULT)
|
||||
else
|
||||
Base64.decode(this, Base64.URL_SAFE)
|
||||
}
|
||||
|
||||
private fun String.parseJws(): SafetyNetResponse {
|
||||
val jws = split('.')
|
||||
val secondDot = lastIndexOf('.')
|
||||
val rawHeader = String(jws[0].decode())
|
||||
val payload = String(jws[1].decode())
|
||||
var signature = jws[2].decode()
|
||||
val signedBytes = substring(0, secondDot).toByteArray()
|
||||
|
||||
val moshi = Moshi.Builder().build()
|
||||
val header = moshi.adapter(JwsHeader::class.java).fromJson(rawHeader)
|
||||
?: error("Invalid JWS header")
|
||||
|
||||
val alg = when (header.algorithm) {
|
||||
"RS256" -> "SHA256withRSA"
|
||||
"ES256" -> {
|
||||
// Convert to DER encoding
|
||||
signature = ASN1Primitive.fromByteArray(signature).getEncoded(ASN1Encoding.DER)
|
||||
"SHA256withECDSA"
|
||||
}
|
||||
else -> error("Unsupported algorithm: ${header.algorithm}")
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
val certB64 = header.certificates?.first() ?: error("Cannot find certificate in JWS")
|
||||
val bis = ByteArrayInputStream(certB64.decode())
|
||||
val cert = CryptoUtils.readCertificate(bis)
|
||||
val verifier = Signature.getInstance(alg)
|
||||
verifier.initVerify(cert.publicKey)
|
||||
verifier.update(signedBytes)
|
||||
if (!verifier.verify(signature))
|
||||
error("Signature mismatch")
|
||||
|
||||
// Verify hostname
|
||||
val hostnameVerifier = JsseDefaultHostnameAuthorizer(setOf())
|
||||
if (!hostnameVerifier.verify("attest.android.com", cert))
|
||||
error("Hostname mismatch")
|
||||
|
||||
val response = moshi.adapter(SafetyNetResponse::class.java).fromJson(payload)
|
||||
?: error("Invalid SafetyNet response")
|
||||
|
||||
// Verify results
|
||||
if (!response.nonce.decode().contentEquals(nonce))
|
||||
error("nonce mismatch")
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
override fun onResponse(response: String?) {
|
||||
if (response != null) {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
val res = runCatching { response.parseJws() }.getOrElse {
|
||||
Timber.e(it)
|
||||
INVALID_RESPONSE
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
callback(SafetyNetResult(res))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
callback(SafetyNetResult())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class JwsHeader(
|
||||
@Json(name = "alg") val algorithm: String,
|
||||
@Json(name = "x5c") val certificates: List<String>?
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SafetyNetResponse(
|
||||
val nonce: String,
|
||||
val ctsProfileMatch: Boolean,
|
||||
val basicIntegrity: Boolean,
|
||||
val evaluationType: String = ""
|
||||
)
|
||||
|
||||
// Special instance to indicate invalid SafetyNet response
|
||||
val INVALID_RESPONSE = SafetyNetResponse("", ctsProfileMatch = false, basicIntegrity = false)
|
||||
|
@@ -1,14 +1,14 @@
|
||||
package com.topjohnwu.magisk.ui.safetynet
|
||||
|
||||
import org.json.JSONObject
|
||||
import android.content.Context
|
||||
|
||||
interface SafetyNetHelper {
|
||||
|
||||
val version: Int
|
||||
|
||||
fun attest()
|
||||
fun attest(context: Context, nonce: ByteArray, callback: Callback)
|
||||
|
||||
interface Callback {
|
||||
fun onResponse(response: JSONObject?)
|
||||
fun onResponse(response: String?)
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import android.view.ViewGroup
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseUIFragment
|
||||
import com.topjohnwu.magisk.databinding.FragmentSafetynetMd2Binding
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import com.topjohnwu.magisk.di.viewModel
|
||||
|
||||
class SafetynetFragment : BaseUIFragment<SafetynetViewModel, FragmentSafetynetMd2Binding>() {
|
||||
|
||||
|
@@ -5,10 +5,9 @@ import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import org.json.JSONObject
|
||||
|
||||
data class SafetyNetResult(
|
||||
val response: JSONObject? = null,
|
||||
class SafetyNetResult(
|
||||
val response: SafetyNetResponse? = null,
|
||||
val dismiss: Boolean = false
|
||||
)
|
||||
|
||||
@@ -43,59 +42,54 @@ class SafetynetViewModel : BaseViewModel() {
|
||||
|
||||
init {
|
||||
cachedResult?.also {
|
||||
resolveResponse(SafetyNetResult(it))
|
||||
handleResult(SafetyNetResult(it))
|
||||
} ?: attest()
|
||||
}
|
||||
|
||||
private fun attest() {
|
||||
isChecking = true
|
||||
CheckSafetyNetEvent {
|
||||
resolveResponse(it)
|
||||
}.publish()
|
||||
CheckSafetyNetEvent(::handleResult).publish()
|
||||
}
|
||||
|
||||
fun reset() = attest()
|
||||
|
||||
private fun resolveResponse(response: SafetyNetResult) {
|
||||
private fun handleResult(result: SafetyNetResult) {
|
||||
isChecking = false
|
||||
|
||||
if (response.dismiss) {
|
||||
if (result.dismiss) {
|
||||
back()
|
||||
return
|
||||
}
|
||||
|
||||
response.response?.apply {
|
||||
runCatching {
|
||||
val cts = getBoolean("ctsProfileMatch")
|
||||
val basic = getBoolean("basicIntegrity")
|
||||
val eval = optString("evaluationType")
|
||||
val result = cts && basic
|
||||
cachedResult = this
|
||||
ctsState = cts
|
||||
basicIntegrityState = basic
|
||||
evalType = if (eval.contains("HARDWARE")) "HARDWARE" else "BASIC"
|
||||
isSuccess = result
|
||||
safetyNetTitle =
|
||||
if (result) R.string.safetynet_attest_success
|
||||
else R.string.safetynet_attest_failure
|
||||
}.onFailure {
|
||||
result.response?.apply {
|
||||
cachedResult = this
|
||||
if (this === INVALID_RESPONSE) {
|
||||
isSuccess = false
|
||||
ctsState = false
|
||||
basicIntegrityState = false
|
||||
evalType = "N/A"
|
||||
safetyNetTitle = R.string.safetynet_res_invalid
|
||||
} else {
|
||||
val success = ctsProfileMatch && basicIntegrity
|
||||
isSuccess = success
|
||||
ctsState = ctsProfileMatch
|
||||
basicIntegrityState = basicIntegrity
|
||||
evalType = if (evaluationType.contains("HARDWARE")) "HARDWARE" else "BASIC"
|
||||
safetyNetTitle =
|
||||
if (success) R.string.safetynet_attest_success
|
||||
else R.string.safetynet_attest_failure
|
||||
}
|
||||
} ?: {
|
||||
} ?: run {
|
||||
isSuccess = false
|
||||
ctsState = false
|
||||
basicIntegrityState = false
|
||||
evalType = "N/A"
|
||||
safetyNetTitle = R.string.safetynet_api_error
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var cachedResult: JSONObject? = null
|
||||
private var cachedResult: SafetyNetResponse? = null
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -3,27 +3,23 @@ package com.topjohnwu.magisk.ui.settings
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.view.View
|
||||
import androidx.annotation.ArrayRes
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.databinding.Bindable
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.ObservableItem
|
||||
import com.topjohnwu.magisk.utils.TransitiveText
|
||||
import com.topjohnwu.magisk.utils.asTransitive
|
||||
import com.topjohnwu.magisk.utils.TextHolder
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.get
|
||||
|
||||
sealed class BaseSettingsItem : ObservableItem<BaseSettingsItem>() {
|
||||
|
||||
override val layoutRes get() = R.layout.item_settings
|
||||
|
||||
open val icon: Int get() = 0
|
||||
open val title: TransitiveText get() = TransitiveText.EMPTY
|
||||
open val title: TextHolder get() = TextHolder.EMPTY
|
||||
@get:Bindable
|
||||
open val description: TransitiveText get() = TransitiveText.EMPTY
|
||||
open val description: TextHolder get() = TextHolder.EMPTY
|
||||
|
||||
// ---
|
||||
|
||||
@@ -141,34 +137,29 @@ sealed class BaseSettingsItem : ObservableItem<BaseSettingsItem>() {
|
||||
abstract fun getView(context: Context): View
|
||||
}
|
||||
|
||||
abstract class Selector : Value<Int>(), KoinComponent {
|
||||
abstract class Selector : Value<Int>() {
|
||||
|
||||
protected val resources get() = get<Resources>()
|
||||
open val entryRes get() = -1
|
||||
open val descriptionRes get() = entryRes
|
||||
open fun entries(res: Resources) = res.getArrayOrEmpty(entryRes)
|
||||
open fun descriptions(res: Resources) = res.getArrayOrEmpty(descriptionRes)
|
||||
|
||||
@ArrayRes open val entryRes = -1
|
||||
@ArrayRes open val entryValRes = -1
|
||||
|
||||
open val entries get() = resources.getArrayOrEmpty(entryRes)
|
||||
open val entryValues get() = resources.getArrayOrEmpty(entryValRes)
|
||||
|
||||
override val description: TransitiveText
|
||||
get() = entries.getOrNull(value)?.asTransitive() ?: TransitiveText.EMPTY
|
||||
override val description = object : TextHolder() {
|
||||
override fun getText(resources: Resources): CharSequence {
|
||||
return descriptions(resources).getOrElse(value) { "" }
|
||||
}
|
||||
}
|
||||
|
||||
private fun Resources.getArrayOrEmpty(id: Int): Array<String> =
|
||||
runCatching { getStringArray(id) }.getOrDefault(emptyArray())
|
||||
|
||||
override fun onPressed(view: View, callback: Callback) {
|
||||
if (entries.isEmpty()) return
|
||||
super.onPressed(view, callback)
|
||||
}
|
||||
|
||||
override fun onPressed(view: View) {
|
||||
MagiskDialog(view.context)
|
||||
.applyTitle(title.getText(resources))
|
||||
.applyTitle(title.getText(view.resources))
|
||||
.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||
titleRes = android.R.string.cancel
|
||||
}
|
||||
.applyAdapter(entries) {
|
||||
.applyAdapter(entries(view.resources)) {
|
||||
value = it
|
||||
}
|
||||
.reveal()
|
||||
|
@@ -5,11 +5,11 @@ import android.view.View
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseUIFragment
|
||||
import com.topjohnwu.magisk.databinding.FragmentSettingsMd2Binding
|
||||
import com.topjohnwu.magisk.di.viewModel
|
||||
import com.topjohnwu.magisk.ktx.addSimpleItemDecoration
|
||||
import com.topjohnwu.magisk.ktx.addVerticalPadding
|
||||
import com.topjohnwu.magisk.ktx.fixEdgeEffect
|
||||
import com.topjohnwu.magisk.ktx.setOnViewReadyListener
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class SettingsFragment : BaseUIFragment<SettingsViewModel, FragmentSettingsMd2Binding>() {
|
||||
|
||||
|
@@ -1,8 +1,10 @@
|
||||
package com.topjohnwu.magisk.ui.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.databinding.Bindable
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
@@ -19,10 +21,9 @@ import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding
|
||||
import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding
|
||||
import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding
|
||||
import com.topjohnwu.magisk.ktx.get
|
||||
import com.topjohnwu.magisk.utils.TransitiveText
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.utils.asTransitive
|
||||
import com.topjohnwu.magisk.utils.asText
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -31,7 +32,7 @@ import kotlinx.coroutines.launch
|
||||
// --- Customization
|
||||
|
||||
object Customization : BaseSettingsItem.Section() {
|
||||
override val title = R.string.settings_customization.asTransitive()
|
||||
override val title = R.string.settings_customization.asText()
|
||||
}
|
||||
|
||||
object Language : BaseSettingsItem.Selector() {
|
||||
@@ -40,9 +41,18 @@ object Language : BaseSettingsItem.Selector() {
|
||||
Config.locale = entryValues[it]
|
||||
}
|
||||
|
||||
override val title = R.string.language.asTransitive()
|
||||
override var entries = emptyArray<String>()
|
||||
override var entryValues = emptyArray<String>()
|
||||
override val title = R.string.language.asText()
|
||||
|
||||
private var entries = emptyArray<String>()
|
||||
private var entryValues = emptyArray<String>()
|
||||
|
||||
override fun entries(res: Resources) = entries
|
||||
override fun descriptions(res: Resources) = entries
|
||||
|
||||
override fun onPressed(view: View, callback: Callback) {
|
||||
if (entries.isEmpty()) return
|
||||
super.onPressed(view, callback)
|
||||
}
|
||||
|
||||
suspend fun loadLanguages(scope: CoroutineScope) {
|
||||
scope.launch {
|
||||
@@ -58,18 +68,18 @@ object Language : BaseSettingsItem.Selector() {
|
||||
|
||||
object Theme : BaseSettingsItem.Blank() {
|
||||
override val icon = R.drawable.ic_paint
|
||||
override val title = R.string.section_theme.asTransitive()
|
||||
override val title = R.string.section_theme.asText()
|
||||
}
|
||||
|
||||
// --- App
|
||||
|
||||
object AppSettings : BaseSettingsItem.Section() {
|
||||
override val title = R.string.home_app_title.asTransitive()
|
||||
override val title = R.string.home_app_title.asText()
|
||||
}
|
||||
|
||||
object ClearRepoCache : BaseSettingsItem.Blank() {
|
||||
override val title = R.string.settings_clear_cache_title.asTransitive()
|
||||
override val description = R.string.settings_clear_cache_summary.asTransitive()
|
||||
override val title = R.string.settings_clear_cache_title.asText()
|
||||
override val description = R.string.settings_clear_cache_summary.asText()
|
||||
|
||||
override fun refresh() {
|
||||
isEnabled = Info.env.isActive
|
||||
@@ -77,8 +87,8 @@ object ClearRepoCache : BaseSettingsItem.Blank() {
|
||||
}
|
||||
|
||||
object Hide : BaseSettingsItem.Input() {
|
||||
override val title = R.string.settings_hide_app_title.asTransitive()
|
||||
override val description = R.string.settings_hide_app_summary.asTransitive()
|
||||
override val title = R.string.settings_hide_app_title.asText()
|
||||
override val description = R.string.settings_hide_app_summary.asText()
|
||||
|
||||
override var value = ""
|
||||
set(value) = setV(value, field, { field = it })
|
||||
@@ -87,7 +97,7 @@ object Hide : BaseSettingsItem.Input() {
|
||||
get() = if (isError) null else result
|
||||
|
||||
@get:Bindable
|
||||
var result = "Manager"
|
||||
var result = "Settings"
|
||||
set(value) = set(value, field, { field = it }, BR.result, BR.error)
|
||||
|
||||
val maxLength
|
||||
@@ -106,21 +116,21 @@ object Hide : BaseSettingsItem.Input() {
|
||||
}
|
||||
|
||||
object Restore : BaseSettingsItem.Blank() {
|
||||
override val title = R.string.settings_restore_app_title.asTransitive()
|
||||
override val description = R.string.settings_restore_app_summary.asTransitive()
|
||||
override val title = R.string.settings_restore_app_title.asText()
|
||||
override val description = R.string.settings_restore_app_summary.asText()
|
||||
}
|
||||
|
||||
object AddShortcut : BaseSettingsItem.Blank() {
|
||||
override val title = R.string.add_shortcut_title.asTransitive()
|
||||
override val description = R.string.setting_add_shortcut_summary.asTransitive()
|
||||
override val title = R.string.add_shortcut_title.asText()
|
||||
override val description = R.string.setting_add_shortcut_summary.asText()
|
||||
}
|
||||
|
||||
object DownloadPath : BaseSettingsItem.Input() {
|
||||
override var value = Config.downloadDir
|
||||
set(value) = setV(value, field, { field = it }) { Config.downloadDir = it }
|
||||
|
||||
override val title = R.string.settings_download_path_title.asTransitive()
|
||||
override val description get() = path.asTransitive()
|
||||
override val title = R.string.settings_download_path_title.asText()
|
||||
override val description get() = path.asText()
|
||||
|
||||
override val inputResult: String get() = result
|
||||
|
||||
@@ -137,29 +147,32 @@ object DownloadPath : BaseSettingsItem.Input() {
|
||||
}
|
||||
|
||||
object UpdateChannel : BaseSettingsItem.Selector() {
|
||||
override var value = Config.updateChannel
|
||||
override var value = Config.updateChannel.let { if (it < 0) 0 else it }
|
||||
set(value) = setV(value, field, { field = it }) {
|
||||
Config.updateChannel = it
|
||||
Info.remote = Info.EMPTY_REMOTE
|
||||
}
|
||||
|
||||
override val title = R.string.settings_update_channel_title.asTransitive()
|
||||
override val entries: Array<String> = resources.getStringArray(R.array.update_channel).apply {
|
||||
if (BuildConfig.VERSION_CODE % 100 == 0)
|
||||
toMutableList().apply { removeAt(Config.Value.CANARY_CHANNEL) }.toTypedArray()
|
||||
override val title = R.string.settings_update_channel_title.asText()
|
||||
|
||||
override val entryRes = R.array.update_channel
|
||||
override fun entries(res: Resources): Array<String> {
|
||||
return super.entries(res).let {
|
||||
if (!BuildConfig.DEBUG)
|
||||
it.copyOfRange(0, Config.Value.CANARY_CHANNEL)
|
||||
else it
|
||||
}
|
||||
}
|
||||
override val description
|
||||
get() = entries.getOrNull(value)?.asTransitive() ?: TransitiveText.String(entries[0])
|
||||
}
|
||||
|
||||
object UpdateChannelUrl : BaseSettingsItem.Input() {
|
||||
override val title = R.string.settings_update_custom.asTransitive()
|
||||
override val title = R.string.settings_update_custom.asText()
|
||||
override var value = Config.customChannelUrl
|
||||
set(value) = setV(value, field, { field = it }) {
|
||||
Config.customChannelUrl = it
|
||||
Info.remote = Info.EMPTY_REMOTE
|
||||
}
|
||||
override val description get() = value.asTransitive()
|
||||
override val description get() = value.asText()
|
||||
|
||||
override val inputResult get() = result
|
||||
|
||||
@@ -176,18 +189,18 @@ object UpdateChannelUrl : BaseSettingsItem.Input() {
|
||||
}
|
||||
|
||||
object UpdateChecker : BaseSettingsItem.Toggle() {
|
||||
override val title = R.string.settings_check_update_title.asTransitive()
|
||||
override val description = R.string.settings_check_update_summary.asTransitive()
|
||||
override val title = R.string.settings_check_update_title.asText()
|
||||
override val description = R.string.settings_check_update_summary.asText()
|
||||
override var value = Config.checkUpdate
|
||||
set(value) = setV(value, field, { field = it }) {
|
||||
Config.checkUpdate = it
|
||||
UpdateCheckService.schedule(get())
|
||||
UpdateCheckService.schedule(AppContext)
|
||||
}
|
||||
}
|
||||
|
||||
object DoHToggle : BaseSettingsItem.Toggle() {
|
||||
override val title = R.string.settings_doh_title.asTransitive()
|
||||
override val description = R.string.settings_doh_description.asTransitive()
|
||||
override val title = R.string.settings_doh_title.asText()
|
||||
override val description = R.string.settings_doh_description.asText()
|
||||
override var value = Config.doh
|
||||
set(value) = setV(value, field, { field = it }) {
|
||||
Config.doh = it
|
||||
@@ -196,35 +209,35 @@ object DoHToggle : BaseSettingsItem.Toggle() {
|
||||
|
||||
// check whether is module already installed beforehand?
|
||||
object SystemlessHosts : BaseSettingsItem.Blank() {
|
||||
override val title = R.string.settings_hosts_title.asTransitive()
|
||||
override val description = R.string.settings_hosts_summary.asTransitive()
|
||||
override val title = R.string.settings_hosts_title.asText()
|
||||
override val description = R.string.settings_hosts_summary.asText()
|
||||
}
|
||||
|
||||
object Tapjack : BaseSettingsItem.Toggle() {
|
||||
override val title = R.string.settings_su_tapjack_title.asTransitive()
|
||||
override var description = R.string.settings_su_tapjack_summary.asTransitive()
|
||||
override val title = R.string.settings_su_tapjack_title.asText()
|
||||
override var description = R.string.settings_su_tapjack_summary.asText()
|
||||
override var value = Config.suTapjack
|
||||
set(value) = setV(value, field, { field = it }) { Config.suTapjack = it }
|
||||
}
|
||||
|
||||
object Biometrics : BaseSettingsItem.Toggle() {
|
||||
override val title = R.string.settings_su_biometric_title.asTransitive()
|
||||
override val title = R.string.settings_su_biometric_title.asText()
|
||||
override var value = Config.suBiometric
|
||||
set(value) = setV(value, field, { field = it }) { Config.suBiometric = it }
|
||||
override var description = R.string.settings_su_biometric_summary.asTransitive()
|
||||
override var description = R.string.settings_su_biometric_summary.asText()
|
||||
|
||||
override fun refresh() {
|
||||
isEnabled = BiometricHelper.isSupported
|
||||
if (!isEnabled) {
|
||||
value = false
|
||||
description = R.string.no_biometric.asTransitive()
|
||||
description = R.string.no_biometric.asText()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Reauthenticate : BaseSettingsItem.Toggle() {
|
||||
override val title = R.string.settings_su_reauth_title.asTransitive()
|
||||
override val description = R.string.settings_su_reauth_summary.asTransitive()
|
||||
override val title = R.string.settings_su_reauth_title.asText()
|
||||
override val description = R.string.settings_su_reauth_summary.asText()
|
||||
override var value = Config.suReAuth
|
||||
set(value) = setV(value, field, { field = it }) { Config.suReAuth = it }
|
||||
|
||||
@@ -236,16 +249,16 @@ object Reauthenticate : BaseSettingsItem.Toggle() {
|
||||
// --- Magisk
|
||||
|
||||
object Magisk : BaseSettingsItem.Section() {
|
||||
override val title = R.string.magisk.asTransitive()
|
||||
override val title = R.string.magisk.asText()
|
||||
}
|
||||
|
||||
object MagiskHide : BaseSettingsItem.Toggle() {
|
||||
override val title = R.string.magiskhide.asTransitive()
|
||||
override val description = R.string.settings_magiskhide_summary.asTransitive()
|
||||
override val title = R.string.magiskhide.asText()
|
||||
override val description = R.string.settings_magiskhide_summary.asText()
|
||||
override var value = Config.magiskHide
|
||||
set(value) = setV(value, field, { field = it }) {
|
||||
val cmd = if (it) "enable" else "disable"
|
||||
Shell.su("magiskhide --$cmd").submit { cb ->
|
||||
Shell.su("magiskhide $cmd").submit { cb ->
|
||||
if (cb.isSuccess) Config.magiskHide = it
|
||||
else field = !it
|
||||
}
|
||||
@@ -255,11 +268,11 @@ object MagiskHide : BaseSettingsItem.Toggle() {
|
||||
// --- Superuser
|
||||
|
||||
object Superuser : BaseSettingsItem.Section() {
|
||||
override val title = R.string.superuser.asTransitive()
|
||||
override val title = R.string.superuser.asText()
|
||||
}
|
||||
|
||||
object AccessMode : BaseSettingsItem.Selector() {
|
||||
override val title = R.string.superuser_access.asTransitive()
|
||||
override val title = R.string.superuser_access.asText()
|
||||
override val entryRes = R.array.su_access
|
||||
|
||||
override var value = Config.rootMode
|
||||
@@ -269,37 +282,33 @@ object AccessMode : BaseSettingsItem.Selector() {
|
||||
}
|
||||
|
||||
object MultiuserMode : BaseSettingsItem.Selector() {
|
||||
override val title = R.string.multiuser_mode.asTransitive()
|
||||
override val title = R.string.multiuser_mode.asText()
|
||||
override val entryRes = R.array.multiuser_mode
|
||||
override val descriptionRes = R.array.multiuser_summary
|
||||
|
||||
override var value = Config.suMultiuserMode
|
||||
set(value) = setV(value, field, { field = it }) {
|
||||
Config.suMultiuserMode = it
|
||||
}
|
||||
|
||||
override val description
|
||||
get() = resources.getStringArray(R.array.multiuser_summary)[value].asTransitive()
|
||||
|
||||
override fun refresh() {
|
||||
isEnabled = Const.USER_ID == 0
|
||||
}
|
||||
}
|
||||
|
||||
object MountNamespaceMode : BaseSettingsItem.Selector() {
|
||||
override val title = R.string.mount_namespace_mode.asTransitive()
|
||||
override val title = R.string.mount_namespace_mode.asText()
|
||||
override val entryRes = R.array.namespace
|
||||
override val descriptionRes = R.array.namespace_summary
|
||||
|
||||
override var value = Config.suMntNamespaceMode
|
||||
set(value) = setV(value, field, { field = it }) {
|
||||
Config.suMntNamespaceMode = it
|
||||
}
|
||||
|
||||
override val description
|
||||
get() = resources.getStringArray(R.array.namespace_summary)[value].asTransitive()
|
||||
}
|
||||
|
||||
object AutomaticResponse : BaseSettingsItem.Selector() {
|
||||
override val title = R.string.auto_response.asTransitive()
|
||||
override val title = R.string.auto_response.asText()
|
||||
override val entryRes = R.array.auto_response
|
||||
|
||||
override var value = Config.suAutoResponse
|
||||
@@ -309,21 +318,21 @@ object AutomaticResponse : BaseSettingsItem.Selector() {
|
||||
}
|
||||
|
||||
object RequestTimeout : BaseSettingsItem.Selector() {
|
||||
override val title = R.string.request_timeout.asTransitive()
|
||||
override val title = R.string.request_timeout.asText()
|
||||
override val entryRes = R.array.request_timeout
|
||||
override val entryValRes = R.array.request_timeout_value
|
||||
|
||||
private val entryValues = listOf(10, 15, 20, 30, 45, 60)
|
||||
override var value = selected
|
||||
set(value) = setV(value, field, { field = it }) {
|
||||
Config.suDefaultTimeout = entryValues[it].toInt()
|
||||
Config.suDefaultTimeout = entryValues[it]
|
||||
}
|
||||
|
||||
private val selected: Int
|
||||
get() = entryValues.indexOfFirst { it.toInt() == Config.suDefaultTimeout }
|
||||
get() = entryValues.indexOfFirst { it == Config.suDefaultTimeout }
|
||||
}
|
||||
|
||||
object SUNotification : BaseSettingsItem.Selector() {
|
||||
override val title = R.string.superuser_notification.asTransitive()
|
||||
override val title = R.string.superuser_notification.asText()
|
||||
override val entryRes = R.array.su_notification
|
||||
|
||||
override var value = Config.suNotification
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package com.topjohnwu.magisk.ui.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
@@ -18,6 +17,7 @@ import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||
import com.topjohnwu.magisk.data.database.RepoDao
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.events.AddHomeIconEvent
|
||||
import com.topjohnwu.magisk.events.RecreateEvent
|
||||
import com.topjohnwu.magisk.events.dialog.BiometricEvent
|
||||
@@ -25,7 +25,6 @@ import com.topjohnwu.magisk.ktx.activity
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.get
|
||||
|
||||
class SettingsViewModel(
|
||||
private val repositoryDao: RepoDao
|
||||
@@ -42,7 +41,7 @@ class SettingsViewModel(
|
||||
}
|
||||
|
||||
private fun createItems(): List<BaseSettingsItem> {
|
||||
val context = get<Context>()
|
||||
val context = AppContext
|
||||
val hidden = context.packageName != BuildConfig.APPLICATION_ID
|
||||
|
||||
// Customization
|
||||
@@ -60,7 +59,7 @@ class SettingsViewModel(
|
||||
))
|
||||
if (Info.env.isActive) {
|
||||
list.add(ClearRepoCache)
|
||||
if (Build.VERSION.SDK_INT >= 21 && Const.USER_ID == 0) {
|
||||
if (Const.USER_ID == 0) {
|
||||
if (hidden)
|
||||
list.add(Restore)
|
||||
else if (Info.isConnected.get())
|
||||
@@ -74,10 +73,6 @@ class SettingsViewModel(
|
||||
Magisk,
|
||||
MagiskHide, SystemlessHosts
|
||||
))
|
||||
if (Build.VERSION.SDK_INT < 19) {
|
||||
// MagiskHide is only available on 4.4+
|
||||
list.remove(MagiskHide)
|
||||
}
|
||||
}
|
||||
|
||||
// Superuser
|
||||
@@ -103,7 +98,7 @@ class SettingsViewModel(
|
||||
override fun onItemPressed(view: View, item: BaseSettingsItem, callback: () -> Unit) = when (item) {
|
||||
is DownloadPath -> withExternalRW(callback)
|
||||
is Biometrics -> authenticate(callback)
|
||||
is Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().publish()
|
||||
is Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate()
|
||||
is ClearRepoCache -> clearRepoCache()
|
||||
is SystemlessHosts -> createHosts()
|
||||
is Restore -> HideAPK.restore(view.activity)
|
||||
|
@@ -1,13 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.superuser
|
||||
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||
|
||||
class SpinnerRvItem(val item: String) : ComparableRvItem<SpinnerRvItem>() {
|
||||
|
||||
override val layoutRes: Int = R.layout.item_spinner
|
||||
|
||||
override fun contentSameAs(other: SpinnerRvItem) = itemSameAs(other)
|
||||
override fun itemSameAs(other: SpinnerRvItem) = item == other.item
|
||||
|
||||
}
|
@@ -5,10 +5,10 @@ import android.view.View
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseUIFragment
|
||||
import com.topjohnwu.magisk.databinding.FragmentSuperuserMd2Binding
|
||||
import com.topjohnwu.magisk.di.viewModel
|
||||
import com.topjohnwu.magisk.ktx.addSimpleItemDecoration
|
||||
import com.topjohnwu.magisk.ktx.addVerticalPadding
|
||||
import com.topjohnwu.magisk.ktx.fixEdgeEffect
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class SuperuserFragment : BaseUIFragment<SuperuserViewModel, FragmentSuperuserMd2Binding>() {
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package com.topjohnwu.magisk.ui.superuser
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.databinding.ObservableArrayList
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
@@ -19,6 +18,7 @@ import com.topjohnwu.magisk.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.events.dialog.BiometricEvent
|
||||
import com.topjohnwu.magisk.events.dialog.SuperuserRevokeDialog
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.utils.asText
|
||||
import com.topjohnwu.magisk.view.TappableHeadlineItem
|
||||
import com.topjohnwu.magisk.view.TextItem
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -27,8 +27,7 @@ import kotlinx.coroutines.withContext
|
||||
import me.tatarka.bindingcollectionadapter2.collections.MergeObservableList
|
||||
|
||||
class SuperuserViewModel(
|
||||
private val db: PolicyDao,
|
||||
private val resources: Resources
|
||||
private val db: PolicyDao
|
||||
) : BaseViewModel(), TappableHeadlineItem.Listener {
|
||||
|
||||
private val itemNoData = TextItem(R.string.superuser_policy_none)
|
||||
@@ -55,6 +54,7 @@ class SuperuserViewModel(
|
||||
}
|
||||
state = State.LOADING
|
||||
val (policies, diff) = withContext(Dispatchers.Default) {
|
||||
db.deleteOutdated()
|
||||
val policies = db.fetchAll {
|
||||
PolicyRvItem(it, it.icon, this@SuperuserViewModel)
|
||||
}.sortedWith(compareBy(
|
||||
@@ -79,7 +79,7 @@ class SuperuserViewModel(
|
||||
}
|
||||
|
||||
private fun hidePressed() =
|
||||
SuperuserFragmentDirections.actionSuperuserFragmentToHideFragment().publish()
|
||||
SuperuserFragmentDirections.actionSuperuserFragmentToHideFragment().navigate()
|
||||
|
||||
fun deletePressed(item: PolicyRvItem) {
|
||||
fun updateState() = viewModelScope.launch {
|
||||
@@ -106,7 +106,7 @@ class SuperuserViewModel(
|
||||
|
||||
fun updatePolicy(policy: SuPolicy, isLogging: Boolean) = viewModelScope.launch {
|
||||
db.update(policy)
|
||||
val str = when {
|
||||
val res = when {
|
||||
isLogging -> when {
|
||||
policy.logging -> R.string.su_snack_log_on
|
||||
else -> R.string.su_snack_log_off
|
||||
@@ -116,7 +116,7 @@ class SuperuserViewModel(
|
||||
else -> R.string.su_snack_notif_off
|
||||
}
|
||||
}
|
||||
SnackbarEvent(resources.getString(str, policy.appName)).publish()
|
||||
SnackbarEvent(res.asText(policy.appName)).publish()
|
||||
}
|
||||
|
||||
fun togglePolicy(item: PolicyRvItem, enable: Boolean) {
|
||||
@@ -130,7 +130,7 @@ class SuperuserViewModel(
|
||||
db.update(app)
|
||||
val res = if (app.policy == SuPolicy.ALLOW) R.string.su_snack_grant
|
||||
else R.string.su_snack_deny
|
||||
SnackbarEvent(resources.getString(res).format(item.item.appName)).publish()
|
||||
SnackbarEvent(res.asText(item.item.appName)).publish()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,6 @@ package com.topjohnwu.magisk.ui.surequest
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
@@ -13,7 +12,7 @@ import com.topjohnwu.magisk.arch.BaseUIActivity
|
||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST
|
||||
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import com.topjohnwu.magisk.di.viewModel
|
||||
|
||||
open class SuRequestActivity : BaseUIActivity<SuRequestViewModel, ActivityRequestBinding>() {
|
||||
|
||||
@@ -62,9 +61,6 @@ open class SuRequestActivity : BaseUIActivity<SuRequestViewModel, ActivityReques
|
||||
}
|
||||
|
||||
private fun lockOrientation() {
|
||||
requestedOrientation = if (Build.VERSION.SDK_INT < 18)
|
||||
resources.configuration.orientation
|
||||
else
|
||||
ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,6 @@ package com.topjohnwu.magisk.ui.surequest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
@@ -27,22 +26,20 @@ import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.ALLOW
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.DENY
|
||||
import com.topjohnwu.magisk.core.su.SuRequestHandler
|
||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.events.DieEvent
|
||||
import com.topjohnwu.magisk.events.ShowUIEvent
|
||||
import com.topjohnwu.magisk.events.dialog.BiometricEvent
|
||||
import com.topjohnwu.magisk.ui.superuser.SpinnerRvItem
|
||||
import com.topjohnwu.magisk.utils.TextHolder
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import kotlinx.coroutines.launch
|
||||
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
|
||||
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
|
||||
class SuRequestViewModel(
|
||||
pm: PackageManager,
|
||||
policyDB: PolicyDao,
|
||||
private val timeoutPrefs: SharedPreferences,
|
||||
private val res: Resources
|
||||
private val timeoutPrefs: SharedPreferences
|
||||
) : BaseViewModel() {
|
||||
|
||||
lateinit var icon: Drawable
|
||||
@@ -50,8 +47,7 @@ class SuRequestViewModel(
|
||||
lateinit var packageName: String
|
||||
|
||||
@get:Bindable
|
||||
var denyText = res.getString(R.string.deny)
|
||||
set(value) = set(value, field, { field = it }, BR.denyText)
|
||||
val denyText = DenyText()
|
||||
|
||||
@get:Bindable
|
||||
var selectedItemPosition = 0
|
||||
@@ -74,15 +70,9 @@ class SuRequestViewModel(
|
||||
false
|
||||
}
|
||||
|
||||
private val items = res.getStringArray(R.array.allow_timeout).map { SpinnerRvItem(it) }
|
||||
val adapter = BindingListViewAdapter<SpinnerRvItem>(1).apply {
|
||||
itemBinding = ItemBinding.of { binding, _, item ->
|
||||
item.bind(binding)
|
||||
}
|
||||
setItems(items)
|
||||
}
|
||||
val itemBinding = ItemBinding.of<String>(BR.item, R.layout.item_spinner)
|
||||
|
||||
private val handler = SuRequestHandler(pm, policyDB)
|
||||
private val handler = SuRequestHandler(AppContext.packageManager, policyDB)
|
||||
private lateinit var timer: CountDownTimer
|
||||
|
||||
fun grantPressed() {
|
||||
@@ -143,7 +133,7 @@ class SuRequestViewModel(
|
||||
|
||||
private fun cancelTimer() {
|
||||
timer.cancel()
|
||||
denyText = res.getString(R.string.deny)
|
||||
denyText.seconds = 0
|
||||
}
|
||||
|
||||
private inner class SuTimer(
|
||||
@@ -155,16 +145,28 @@ class SuRequestViewModel(
|
||||
if (!grantEnabled && remains <= millis - 1000) {
|
||||
grantEnabled = true
|
||||
}
|
||||
denyText = "${res.getString(R.string.deny)} (${(remains / 1000) + 1})"
|
||||
denyText.seconds = (remains / 1000).toInt() + 1
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
denyText = res.getString(R.string.deny)
|
||||
denyText.seconds = 0
|
||||
respond(DENY)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class DenyText : TextHolder() {
|
||||
var seconds = 0
|
||||
set(value) = set(value, field, { field = it }, BR.denyText)
|
||||
|
||||
override fun getText(resources: Resources): CharSequence {
|
||||
return if (seconds != 0)
|
||||
"${resources.getString(R.string.deny)} ($seconds)"
|
||||
else
|
||||
resources.getString(R.string.deny)
|
||||
}
|
||||
}
|
||||
|
||||
// Invisible for accessibility services
|
||||
object EmptyAccessibilityDelegate : View.AccessibilityDelegate() {
|
||||
override fun sendAccessibilityEvent(host: View?, eventType: Int) {}
|
||||
|
@@ -11,7 +11,7 @@ import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseUIFragment
|
||||
import com.topjohnwu.magisk.databinding.FragmentThemeMd2Binding
|
||||
import com.topjohnwu.magisk.databinding.ItemThemeBindingImpl
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import com.topjohnwu.magisk.di.viewModel
|
||||
|
||||
class ThemeFragment : BaseUIFragment<ThemeViewModel, FragmentThemeMd2Binding>() {
|
||||
|
||||
|
47
app/src/main/java/com/topjohnwu/magisk/utils/TextHolder.kt
Normal file
47
app/src/main/java/com/topjohnwu/magisk/utils/TextHolder.kt
Normal file
@@ -0,0 +1,47 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.widget.TextView
|
||||
import androidx.databinding.BindingAdapter
|
||||
|
||||
abstract class TextHolder {
|
||||
|
||||
open val isEmpty: Boolean get() = false
|
||||
abstract fun getText(resources: Resources): CharSequence
|
||||
|
||||
// ---
|
||||
|
||||
class String(
|
||||
private val value: CharSequence
|
||||
) : TextHolder() {
|
||||
|
||||
override val isEmpty get() = value.isEmpty()
|
||||
override fun getText(resources: Resources) = value
|
||||
|
||||
}
|
||||
|
||||
class Resource(
|
||||
private val value: Int,
|
||||
private vararg val params: Any
|
||||
) : TextHolder() {
|
||||
|
||||
override val isEmpty get() = value == 0
|
||||
override fun getText(resources: Resources) = resources.getString(value, *params)
|
||||
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
companion object {
|
||||
val EMPTY = String("")
|
||||
}
|
||||
}
|
||||
|
||||
fun Int.asText(vararg params: Any): TextHolder = TextHolder.Resource(this, *params)
|
||||
fun CharSequence.asText(): TextHolder = TextHolder.String(this)
|
||||
|
||||
|
||||
@BindingAdapter("android:text")
|
||||
fun TextView.setText(text: TextHolder) {
|
||||
this.text = text.getText(context.resources)
|
||||
}
|
@@ -1,54 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.widget.TextView
|
||||
import androidx.databinding.BindingAdapter
|
||||
import androidx.databinding.InverseBindingAdapter
|
||||
|
||||
sealed class TransitiveText {
|
||||
|
||||
abstract val isEmpty: Boolean
|
||||
abstract fun getText(resources: Resources): CharSequence
|
||||
|
||||
// ---
|
||||
|
||||
class String(
|
||||
private val value: CharSequence
|
||||
) : TransitiveText() {
|
||||
|
||||
override val isEmpty = value.isEmpty()
|
||||
override fun getText(resources: Resources) = value
|
||||
|
||||
}
|
||||
|
||||
class Res(
|
||||
private val value: Int,
|
||||
private vararg val params: Any
|
||||
) : TransitiveText() {
|
||||
|
||||
override val isEmpty = value == 0
|
||||
override fun getText(resources: Resources) =
|
||||
resources.getString(value, *params)
|
||||
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
companion object {
|
||||
val EMPTY = String("")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun Int.asTransitive(vararg params: Any): TransitiveText = TransitiveText.Res(this, *params)
|
||||
fun CharSequence.asTransitive(): TransitiveText = TransitiveText.String(this)
|
||||
|
||||
|
||||
@BindingAdapter("android:text")
|
||||
fun TextView.setText(text: TransitiveText) {
|
||||
this.text = text.getText(context.resources)
|
||||
}
|
||||
|
||||
@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
|
||||
fun TextView.getTransitiveText() = text.asTransitive()
|
@@ -8,17 +8,17 @@ import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.ktx.get
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
|
||||
object Utils {
|
||||
|
||||
fun toast(msg: CharSequence, duration: Int) {
|
||||
UiThreadHandler.run { Toast.makeText(get(), msg, duration).show() }
|
||||
UiThreadHandler.run { Toast.makeText(AppContext, msg, duration).show() }
|
||||
}
|
||||
|
||||
fun toast(resId: Int, duration: Int) {
|
||||
UiThreadHandler.run { Toast.makeText(get(), resId, duration).show() }
|
||||
UiThreadHandler.run { Toast.makeText(AppContext, resId, duration).show() }
|
||||
}
|
||||
|
||||
fun showSuperUser(): Boolean {
|
||||
|
@@ -13,13 +13,13 @@ import com.topjohnwu.magisk.core.Const.ID.PROGRESS_NOTIFICATION_CHANNEL
|
||||
import com.topjohnwu.magisk.core.Const.ID.UPDATE_NOTIFICATION_CHANNEL
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.ktx.get
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.ktx.getBitmap
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
object Notifications {
|
||||
|
||||
val mgr by lazy { get<Context>().getSystemService<NotificationManager>()!! }
|
||||
val mgr by lazy { AppContext.getSystemService<NotificationManager>()!! }
|
||||
|
||||
fun setup(context: Context) {
|
||||
if (SDK_INT >= 26) {
|
||||
|
@@ -10,7 +10,6 @@ import org.bouncycastle.cms.CMSSignedData;
|
||||
import org.bouncycastle.cms.CMSSignedDataGenerator;
|
||||
import org.bouncycastle.cms.CMSTypedData;
|
||||
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
@@ -31,7 +30,6 @@ import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
|
@@ -196,6 +196,9 @@ public class SignBoot {
|
||||
int secondSize = image.getInt();
|
||||
image.getLong(); // second_addr + tags_addr
|
||||
int pageSize = image.getInt();
|
||||
if (pageSize >= 0x02000000) {
|
||||
throw new IllegalArgumentException("Invalid image header: PXA header detected");
|
||||
}
|
||||
int length = pageSize // include the page aligned image header
|
||||
+ ((kernelSize + pageSize - 1) / pageSize) * pageSize
|
||||
+ ((ramdskSize + pageSize - 1) / pageSize) * pageSize
|
||||
@@ -214,8 +217,7 @@ public class SignBoot {
|
||||
image.getLong(); // dtb address
|
||||
}
|
||||
if (image.position() != headerSize) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid image header: invalid header length");
|
||||
throw new IllegalArgumentException("Invalid image header: invalid header length");
|
||||
}
|
||||
} else {
|
||||
// headerVersion is 0 or actually dt/extra size in this case
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user