mirror of
				https://github.com/topjohnwu/Magisk
				synced 2025-10-30 09:00:52 +01:00 
			
		
		
		
	Compare commits
	
		
			144 Commits
		
	
	
		
			manager-v8
			...
			v21.3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 7f8257152f | ||
|   | 0cd80f2556 | ||
|   | 1717387876 | ||
|   | 109363ebf6 | ||
|   | 716c4fa386 | ||
|   | 9a09b4eb20 | ||
|   | 95a5b57265 | ||
|   | 13fbf397d1 | ||
|   | 20be99ec8a | ||
|   | 04c53c3578 | ||
|   | 51bc27a869 | ||
|   | 71b083794c | ||
|   | b100d0c503 | ||
|   | 76061296c9 | ||
|   | bb303d2da1 | ||
|   | c91c070343 | ||
|   | aec06a6f61 | ||
|   | e8ba671fc2 | ||
|   | 1860e5d133 | ||
|   | f2cb3c38fe | ||
|   | 9a28dd4f6e | ||
|   | d2acd59ea8 | ||
|   | 79dfdb29e7 | ||
|   | eb21c8b42e | ||
|   | 541bb53553 | ||
|   | fe8997efae | ||
|   | 23455c722c | ||
|   | 5ce29c30d2 | ||
|   | 70d67728fd | ||
|   | e546884b08 | ||
|   | b36e6d987d | ||
|   | 53c3dd5e8b | ||
|   | da723b207a | ||
|   | e050f77198 | ||
|   | 540b4b7ea9 | ||
|   | bbef22daf7 | ||
|   | 9ed110c91b | ||
|   | a30d510eb1 | ||
|   | ef98eaed8f | ||
|   | 2a257f327c | ||
|   | 4060c2107c | ||
|   | cd23d27048 | ||
|   | 18b86e4fd2 | ||
|   | 5f2e22a259 | ||
|   | 4e97b18977 | ||
|   | f9bde347bc | ||
|   | 947a7d6a2f | ||
|   | 872ab2e99b | ||
|   | 90b8813bb7 | ||
|   | 88d0f63294 | ||
|   | 79fa0d3a90 | ||
|   | 8e61080a4a | ||
|   | 3f9a64417b | ||
|   | eb959379e8 | ||
|   | 41a644afb9 | ||
|   | 6b42db943d | ||
|   | 1c325459eb | ||
|   | 6d88d8ad95 | ||
|   | 246997f273 | ||
|   | b6144ae582 | ||
|   | afe17c73b4 | ||
|   | b51b884fc7 | ||
|   | d3e4b29e62 | ||
|   | 24059e7403 | ||
|   | 107a2a6682 | ||
|   | 56b4ab6672 | ||
|   | 4662454938 | ||
|   | db4f78d463 | ||
|   | 880de21596 | ||
|   | 622dd84c9e | ||
|   | f983bfc883 | ||
|   | 45cdb3fdb0 | ||
|   | 9a707236b8 | ||
|   | e9e6ad3bb0 | ||
|   | ab78a81d15 | ||
|   | 18340099b7 | ||
|   | a013696a41 | ||
|   | 8a2a6d9232 | ||
|   | 12aa6d86e4 | ||
|   | 7d08969d28 | ||
|   | dda4aa8488 | ||
|   | cdaef3d801 | ||
|   | 9159166128 | ||
|   | dc0882e043 | ||
|   | c811f015ef | ||
|   | d8f0b66fe1 | ||
|   | dc3d57deba | ||
|   | d089698475 | ||
|   | 8ed2dd6687 | ||
|   | 50305ca1fe | ||
|   | 3e91567636 | ||
|   | 0b4dd63d36 | ||
|   | 38d0f85deb | ||
|   | c5b452f369 | ||
|   | 6ce9225f52 | ||
|   | 13a8820603 | ||
|   | 503997a09a | ||
|   | 17efdff134 | ||
|   | 984f32f994 | ||
|   | eee7f097e3 | ||
|   | 086059ec30 | ||
|   | 7ff22c68c7 | ||
|   | 1232113772 | ||
|   | 039d4936cb | ||
|   | 784dd80965 | ||
|   | 1ffe9bd83b | ||
|   | 0c28b23224 | ||
|   | ec1af9dc1e | ||
|   | ff4cea229a | ||
|   | 3f81f9371f | ||
|   | 60e89a7d22 | ||
|   | c50daa5c9e | ||
|   | 58d00ab863 | ||
|   | ce916459c5 | ||
|   | 4094d560ab | ||
|   | 4dbf7eb04b | ||
|   | a39577c44d | ||
|   | 125ee46685 | ||
|   | ce84f1762c | ||
|   | a687d1347b | ||
|   | 6d9db20614 | ||
|   | c62dfc1bcc | ||
|   | aabe2696fe | ||
|   | ae0d605310 | ||
|   | 2a694596b5 | ||
|   | ff0a76606e | ||
|   | dead74801d | ||
|   | ab207a1bb3 | ||
|   | f152e8c33d | ||
|   | 797ba4fbf4 | ||
|   | a848f10bba | ||
|   | 552ec1eb35 | ||
|   | 1385d2a4f4 | ||
|   | 3b5c9abf7a | ||
|   | e0fa032bd3 | ||
|   | 7b69650fcd | ||
|   | 08a8df489f | ||
|   | 9f35a8a520 | ||
|   | 0df891b336 | ||
|   | 385853a290 | ||
|   | fa3ef8a1c1 | ||
|   | c93ada03c7 | ||
|   | 0064b01ae0 | ||
|   | 1469b82aa2 | 
							
								
								
									
										28
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| --- | ||||
| name: Bug report | ||||
| about: Create a report to help us improve | ||||
| title: '' | ||||
| labels: '' | ||||
| 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 a crash of Magisk Manager, dump the full `logcat` **when the crash happens**. **DO NOT** upload `magisk.log`. | ||||
|  | ||||
| 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) | ||||
|  | ||||
| **DO NOT** open issues regarding root detection. | ||||
|  | ||||
| **DO NOT** ask for instructions. | ||||
|  | ||||
| **DO NOT** report issues if you have any modules installed. | ||||
|  | ||||
| Without following the rules above, your issue will be closed without explanation. | ||||
							
								
								
									
										94
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| name: Magisk Build | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: [ master ] | ||||
|     paths: | ||||
|       - 'app/**' | ||||
|       - 'native/**' | ||||
|       - 'stub/**' | ||||
|       - 'buildSrc/**' | ||||
|       - 'build.py' | ||||
|       - 'gradle.properties' | ||||
|   pull_request: | ||||
|     branches: [ master ] | ||||
|   workflow_dispatch: | ||||
|  | ||||
| jobs: | ||||
|   build: | ||||
|     name: Build on ${{ matrix.os }} | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         os: [ ubuntu-latest, windows-latest, macOS-latest ] | ||||
|  | ||||
|     steps: | ||||
|       - name: Check out | ||||
|         uses: actions/checkout@v2 | ||||
|         with: | ||||
|           submodules: 'recursive' | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - name: Set up JDK 11 | ||||
|         uses: actions/setup-java@v1 | ||||
|         with: | ||||
|           java-version: '11' | ||||
|  | ||||
|       - name: Set up Python 3 | ||||
|         uses: actions/setup-python@v2 | ||||
|         with: | ||||
|           python-version: '3.x' | ||||
|  | ||||
|       - name: Set up GitHub env (Windows) | ||||
|         if: runner.os == 'Windows' | ||||
|         run: | | ||||
|           $oldAndroidPath = $env:ANDROID_SDK_ROOT | ||||
|           $sdk_root = "C:\Android" | ||||
|           New-Item -Path $sdk_root -ItemType SymbolicLink -Value $oldAndroidPath | ||||
|           $ndk_ver = Select-String -Path "gradle.properties" -Pattern "^magisk.fullNdkVersion=" | % { $_ -replace ".*=" } | ||||
|           echo "ANDROID_SDK_ROOT=$sdk_root" >> $env:GITHUB_ENV | ||||
|           echo "ANDROID_HOME=$sdk_root" >> $env:GITHUB_ENV | ||||
|           echo "MAGISK_NDK_VERSION=$ndk_ver" >> $env:GITHUB_ENV | ||||
|           echo "GRADLE_OPTS=-Dorg.gradle.daemon=false" >> $env:GITHUB_ENV | ||||
|  | ||||
|       - name: Set up GitHub env (Unix) | ||||
|         if: runner.os != 'Windows' | ||||
|         run: | | ||||
|           ndk_ver=$(sed -n 's/^magisk.fullNdkVersion=//p' gradle.properties) | ||||
|           echo ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT >> $GITHUB_ENV | ||||
|           echo MAGISK_NDK_VERSION=$ndk_ver >> $GITHUB_ENV | ||||
|  | ||||
|       - name: Cache Gradle | ||||
|         uses: actions/cache@v2 | ||||
|         with: | ||||
|           path: | | ||||
|             ~/.gradle/caches | ||||
|             ~/.gradle/wrapper | ||||
|           key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }} | ||||
|           restore-keys: ${{ runner.os }}-gradle- | ||||
|  | ||||
|       - name: Cache NDK | ||||
|         id: ndk-cache | ||||
|         uses: actions/cache@v2 | ||||
|         with: | ||||
|           path: ${{ env.ANDROID_SDK_ROOT }}/ndk/magisk | ||||
|           key: ${{ runner.os }}-ndk-${{ env.MAGISK_NDK_VERSION }} | ||||
|  | ||||
|       - name: Set up NDK | ||||
|         if: steps.ndk-cache.outputs.cache-hit != 'true' | ||||
|         run: python build.py ndk | ||||
|  | ||||
|       - name: Build release | ||||
|         run: python build.py -vr all | ||||
|  | ||||
|       - name: Build debug | ||||
|         run: python build.py -v all | ||||
|  | ||||
|       # Only upload artifacts built on Linux | ||||
|       - name: Upload build artifact | ||||
|         if: runner.os == 'Linux' | ||||
|         uses: actions/upload-artifact@v2 | ||||
|         with: | ||||
|           name: ${{ github.sha }} | ||||
|           path: out | ||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -25,6 +25,9 @@ | ||||
| [submodule "pcre"] | ||||
| 	path = native/jni/external/pcre | ||||
| 	url = https://android.googlesource.com/platform/external/pcre | ||||
| [submodule "xhook"] | ||||
| 	path = native/jni/external/xhook | ||||
| 	url = https://github.com/iqiyi/xHook.git | ||||
| [submodule "termux-elf-cleaner"] | ||||
| 	path = tools/termux-elf-cleaner | ||||
| 	url = https://github.com/termux/termux-elf-cleaner.git | ||||
|   | ||||
							
								
								
									
										10
									
								
								README.MD
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.MD
									
									
									
									
									
								
							| @@ -15,11 +15,11 @@ Here are some feature highlights: | ||||
|  | ||||
| ## Downloads | ||||
|  | ||||
| [](https://github.com/topjohnwu/Magisk/releases/download/manager-v8.0.2/MagiskManager-v8.0.2.apk) | ||||
| [](https://github.com/topjohnwu/Magisk/releases/download/manager-v8.0.5/MagiskManager-v8.0.5.apk) | ||||
| [](https://raw.githubusercontent.com/topjohnwu/magisk_files/canary/app-debug.apk) | ||||
| <br> | ||||
| [](https://github.com/topjohnwu/Magisk/releases/tag/v20.4) | ||||
| [](https://github.com/topjohnwu/Magisk/releases/tag/v21.0) | ||||
| [](https://github.com/topjohnwu/Magisk/releases/tag/v21.2) | ||||
| [](https://github.com/topjohnwu/Magisk/releases/tag/v21.2) | ||||
|  | ||||
| ## Useful Links | ||||
|  | ||||
| @@ -58,11 +58,11 @@ For Magisk Manager crashes, record and upload the logcat when the crash occurs. | ||||
| 	- Windows: Add `C:\Path\To\Android Studio\jre\bin` to environment variable `PATH` | ||||
| - Set environment variable `ANDROID_SDK_ROOT` to the Android SDK folder (can be found in Android Studio settings) | ||||
| - Run `./build.py ndk` to let the script download and install NDK for you | ||||
| - Set configurations in `config.prop`. A sample `config.prop.sample` is provided. | ||||
| - To start building, run `build.py` to see your options. \ | ||||
| For each action, use `-h` to access help (e.g. `./build.py all -h`) | ||||
| - To start development, open the project in Android Studio. Both app (Kotlin/Java) and native (C++/C) source code can be properly developed using the IDE, but *always* use `build.py` for building. | ||||
| - `build.py` builds in debug mode by default. If you want release builds (with `-r, --release`), you need a Java Keystore to sign APKs and zips. For more information, check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key). | ||||
| - Optionally, set custom configs with `config.prop`. A sample `config.prop.sample` is provided. | ||||
| - To sign APKs and zips with your own private keys, set signing configs in `config.prop`. For more info, check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key). | ||||
|  | ||||
| ## Translation Contributions | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,9 @@ | ||||
| import java.io.PrintStream | ||||
|  | ||||
| plugins { | ||||
|     id("com.android.application") | ||||
|     kotlin("android") | ||||
|     kotlin("android.extensions") | ||||
|     kotlin("plugin.parcelize") | ||||
|     kotlin("kapt") | ||||
|     id("androidx.navigation.safeargs.kotlin") | ||||
| } | ||||
| @@ -20,12 +22,11 @@ android { | ||||
|         applicationId = "com.topjohnwu.magisk" | ||||
|         vectorDrawables.useSupportLibrary = true | ||||
|         multiDexEnabled = true | ||||
|         versionName = Config["appVersion"] | ||||
|         versionCode = Config["appVersionCode"]?.toInt() | ||||
|         buildConfigField("int", "LATEST_MAGISK", Config["versionCode"] ?: "Integer.MAX_VALUE") | ||||
|         versionName = Config.appVersion | ||||
|         versionCode = Config.appVersionCode | ||||
|  | ||||
|         javaCompileOptions.annotationProcessorOptions.arguments( | ||||
|                 mapOf("room.incremental" to "true") | ||||
|             mapOf("room.incremental" to "true") | ||||
|         ) | ||||
|     } | ||||
|  | ||||
| @@ -64,28 +65,61 @@ android { | ||||
|     } | ||||
| } | ||||
|  | ||||
| androidExtensions { | ||||
|     isExperimental = true | ||||
| } | ||||
|  | ||||
| val copyUtils = tasks.register("copyUtils", Copy::class) { | ||||
| tasks["preBuild"]?.dependsOn(tasks.register("copyUtils", Copy::class) { | ||||
|     from(rootProject.file("scripts/util_functions.sh")) | ||||
|     into("src/main/res/raw") | ||||
| } | ||||
| }) | ||||
|  | ||||
| tasks["preBuild"]?.dependsOn(copyUtils) | ||||
| android.applicationVariants.all { | ||||
|     val keysDir = rootProject.file("tools/keys") | ||||
|     val outSrcDir = File(buildDir, "generated/source/keydata/$name") | ||||
|     val outSrc = File(outSrcDir, "com/topjohnwu/signing/KeyData.java") | ||||
|  | ||||
|     fun PrintStream.newField(name: String, file: File) { | ||||
|         println("public static byte[] $name() {") | ||||
|         print("byte[] buf = {") | ||||
|         val bytes = file.readBytes() | ||||
|         print(bytes.joinToString(",") { "(byte)(${it.toInt() and 0xff})" }) | ||||
|         println("};") | ||||
|         println("return buf;") | ||||
|         println("}") | ||||
|     } | ||||
|  | ||||
|     val genSrcTask = tasks.register("generate${name.capitalize()}KeyData") { | ||||
|         inputs.dir(keysDir) | ||||
|         outputs.file(outSrc) | ||||
|         doLast { | ||||
|             outSrc.parentFile.mkdirs() | ||||
|             PrintStream(outSrc).use { | ||||
|                 it.println("package com.topjohnwu.signing;") | ||||
|                 it.println("public final class KeyData {") | ||||
|  | ||||
|                 it.newField("testCert", File(keysDir, "testkey.x509.pem")) | ||||
|                 it.newField("testKey", File(keysDir, "testkey.pk8")) | ||||
|                 it.newField("verityCert", File(keysDir, "verity.x509.pem")) | ||||
|                 it.newField("verityKey", File(keysDir, "verity.pk8")) | ||||
|  | ||||
|                 it.println("}") | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     registerJavaGeneratingTask(genSrcTask.get(), outSrcDir) | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) | ||||
|     implementation(kotlin("stdlib")) | ||||
|     implementation(project(":app:shared")) | ||||
|     implementation(project(":app:signing")) | ||||
|  | ||||
|     implementation("com.github.topjohnwu:jtar:1.0.0") | ||||
|     implementation("com.github.topjohnwu:indeterminate-checkbox:1.0.7") | ||||
|     implementation("com.github.topjohnwu:lz4-java:1.7.1") | ||||
|     implementation("com.jakewharton.timber:timber:4.7.1") | ||||
|  | ||||
|     val vBC = "1.68" | ||||
|     implementation("org.bouncycastle:bcprov-jdk15on:${vBC}") | ||||
|     implementation("org.bouncycastle:bcpkix-jdk15on:${vBC}") | ||||
|  | ||||
|     val vBAdapt = "4.0.0" | ||||
|     val bindingAdapter = "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter" | ||||
|     implementation("${bindingAdapter}:${vBAdapt}") | ||||
| @@ -124,7 +158,7 @@ dependencies { | ||||
|     implementation("com.squareup.moshi:moshi:${vMoshi}") | ||||
|     kapt("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}") | ||||
|  | ||||
|     val vRoom = "2.3.0-alpha03" | ||||
|     val vRoom = "2.3.0-alpha04" | ||||
|     implementation("androidx.room:room-runtime:${vRoom}") | ||||
|     implementation("androidx.room:room-ktx:${vRoom}") | ||||
|     kapt("androidx.room:room-compiler:${vRoom}") | ||||
| @@ -136,7 +170,7 @@ dependencies { | ||||
|     implementation("androidx.biometric:biometric:1.0.1") | ||||
|     implementation("androidx.constraintlayout:constraintlayout:2.0.4") | ||||
|     implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") | ||||
|     implementation("androidx.browser:browser:1.2.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") | ||||
|   | ||||
							
								
								
									
										1
									
								
								app/signing/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								app/signing/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /build | ||||
| @@ -1,35 +0,0 @@ | ||||
| import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar | ||||
|  | ||||
| plugins { | ||||
|     id("java-library") | ||||
|     id("java") | ||||
|     id("com.github.johnrengelman.shadow") version "6.0.0" | ||||
| } | ||||
|  | ||||
| java { | ||||
|     sourceCompatibility = JavaVersion.VERSION_1_8 | ||||
|     targetCompatibility = JavaVersion.VERSION_1_8 | ||||
| } | ||||
|  | ||||
| val jar by tasks.getting(Jar::class) { | ||||
|     manifest { | ||||
|         attributes["Main-Class"] = "com.topjohnwu.signing.ZipSigner" | ||||
|     } | ||||
| } | ||||
|  | ||||
| val shadowJar by tasks.getting(ShadowJar::class) { | ||||
|     archiveBaseName.set("zipsigner") | ||||
|     archiveClassifier.set(null as String?) | ||||
|     archiveVersion.set("4.0") | ||||
| } | ||||
|  | ||||
| repositories { | ||||
|     jcenter() | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) | ||||
|  | ||||
|     api("org.bouncycastle:bcprov-jdk15on:1.67") | ||||
|     api("org.bouncycastle:bcpkix-jdk15on:1.67") | ||||
| } | ||||
| @@ -1,49 +0,0 @@ | ||||
| package com.topjohnwu.signing; | ||||
|  | ||||
| import java.io.FileInputStream; | ||||
| import java.io.InputStream; | ||||
|  | ||||
| public class BootSigner { | ||||
|  | ||||
|     public static void main(String[] args) throws Exception { | ||||
|         if (args.length > 0 && "-verify".equals(args[0])) { | ||||
|             String certPath = ""; | ||||
|             if (args.length >= 2) { | ||||
|                 /* args[1] is the path to a public key certificate */ | ||||
|                 certPath = args[1]; | ||||
|             } | ||||
|             boolean signed = SignBoot.verifySignature(System.in, | ||||
|                     certPath.isEmpty() ? null : new FileInputStream(certPath)); | ||||
|             System.exit(signed ? 0 : 1); | ||||
|         } else if (args.length > 0 && "-sign".equals(args[0])) { | ||||
|             InputStream cert = null; | ||||
|             InputStream key = null; | ||||
|             String name = "/boot"; | ||||
|  | ||||
|             if (args.length >= 3) { | ||||
|                 cert = new FileInputStream(args[1]); | ||||
|                 key = new FileInputStream(args[2]); | ||||
|             } | ||||
|             if (args.length == 2) { | ||||
|                 name = args[1]; | ||||
|             } else if (args.length >= 4) { | ||||
|                 name = args[3]; | ||||
|             } | ||||
|  | ||||
|             boolean success = SignBoot.doSignature(name, System.in, System.out, cert, key); | ||||
|             System.exit(success ? 0 : 1); | ||||
|         } else { | ||||
|             System.err.println( | ||||
|                     "BootSigner <actions> [args]\n" + | ||||
|                     "Input from stdin, outputs to stdout\n" + | ||||
|                     "\n" + | ||||
|                     "Actions:\n" + | ||||
|                     "   -verify [x509.pem]\n" + | ||||
|                     "      verify image, cert is optional\n" + | ||||
|                     "   -sign [x509.pem] [pk8] [name]\n" + | ||||
|                     "      sign image, name, cert and key pair are optional\n" + | ||||
|                     "      name should be /boot (default) or /recovery\n" | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,81 +0,0 @@ | ||||
| package com.topjohnwu.signing; | ||||
|  | ||||
| import org.bouncycastle.jce.provider.BouncyCastleProvider; | ||||
|  | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.security.KeyStore; | ||||
| import java.security.KeyStoreException; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.security.PrivateKey; | ||||
| import java.security.Security; | ||||
| import java.security.cert.CertificateException; | ||||
| import java.security.cert.X509Certificate; | ||||
|  | ||||
| public class ZipSigner { | ||||
|  | ||||
|     private static void usage() { | ||||
|         System.err.println("ZipSigner usage:"); | ||||
|         System.err.println("  zipsigner.jar input.jar output.jar"); | ||||
|         System.err.println("    sign jar with AOSP test keys"); | ||||
|         System.err.println("  zipsigner.jar x509.pem pk8 input.jar output.jar"); | ||||
|         System.err.println("    sign jar with certificate / private key pair"); | ||||
|         System.err.println("  zipsigner.jar keyStore keyStorePass alias keyPass input.jar output.jar"); | ||||
|         System.err.println("    sign jar with Java KeyStore"); | ||||
|         System.exit(2); | ||||
|     } | ||||
|  | ||||
|     private static void sign(JarMap input, FileOutputStream output) throws Exception { | ||||
|         sign(SignApk.class.getResourceAsStream("/keys/testkey.x509.pem"), | ||||
|                 SignApk.class.getResourceAsStream("/keys/testkey.pk8"), input, output); | ||||
|     } | ||||
|  | ||||
|     private static void sign(InputStream certIs, InputStream keyIs, | ||||
|                              JarMap input, FileOutputStream output) throws Exception { | ||||
|         X509Certificate cert = CryptoUtils.readCertificate(certIs); | ||||
|         PrivateKey key = CryptoUtils.readPrivateKey(keyIs); | ||||
|         SignApk.sign(cert, key, input, output); | ||||
|     } | ||||
|  | ||||
|     private static void sign(String keyStore, String keyStorePass, String alias, String keyPass, | ||||
|                              JarMap in, FileOutputStream out) throws Exception { | ||||
|         KeyStore ks; | ||||
|         try { | ||||
|             ks = KeyStore.getInstance("JKS"); | ||||
|             try (InputStream is = new FileInputStream(keyStore)) { | ||||
|                 ks.load(is, keyStorePass.toCharArray()); | ||||
|             } | ||||
|         } catch (KeyStoreException|IOException|CertificateException|NoSuchAlgorithmException e) { | ||||
|             ks = KeyStore.getInstance("PKCS12"); | ||||
|             try (InputStream is = new FileInputStream(keyStore)) { | ||||
|                 ks.load(is, keyStorePass.toCharArray()); | ||||
|             } | ||||
|         } | ||||
|         X509Certificate cert = (X509Certificate) ks.getCertificate(alias); | ||||
|         PrivateKey key = (PrivateKey) ks.getKey(alias, keyPass.toCharArray()); | ||||
|         SignApk.sign(cert, key, in, out); | ||||
|     } | ||||
|  | ||||
|     public static void main(String[] args) throws Exception { | ||||
|         if (args.length != 2 && args.length != 4 && args.length != 6) | ||||
|             usage(); | ||||
|  | ||||
|         Security.insertProviderAt(new BouncyCastleProvider(), 1); | ||||
|  | ||||
|         try (JarMap in = JarMap.open(args[args.length - 2], false); | ||||
|              FileOutputStream out = new FileOutputStream(args[args.length - 1])) { | ||||
|             if (args.length == 2) { | ||||
|                 sign(in, out); | ||||
|             } else if (args.length == 4) { | ||||
|                 try (InputStream cert = new FileInputStream(args[0]); | ||||
|                      InputStream key = new FileInputStream(args[1])) { | ||||
|                     sign(cert, key, in, out); | ||||
|                 } | ||||
|             } else if (args.length == 6) { | ||||
|                 sign(args[0], args[1], args[2], args[3], in, out); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -12,7 +12,7 @@ | ||||
|     <application | ||||
|         android:icon="@drawable/ic_launcher" | ||||
|         android:name="a.e" | ||||
|         android:allowBackup="true" | ||||
|         android:allowBackup="false" | ||||
|         tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"> | ||||
|  | ||||
|         <!-- Splash --> | ||||
|   | ||||
| @@ -8,10 +8,10 @@ import com.topjohnwu.magisk.core.SplashActivity | ||||
| import com.topjohnwu.magisk.core.download.DownloadService | ||||
| import com.topjohnwu.magisk.ui.MainActivity | ||||
| import com.topjohnwu.magisk.ui.surequest.SuRequestActivity | ||||
| import com.topjohnwu.signing.BootSigner | ||||
| import com.topjohnwu.signing.SignBoot | ||||
|  | ||||
| fun main(args: Array<String>) { | ||||
|     BootSigner.main(args) | ||||
|     SignBoot.main(args) | ||||
| } | ||||
|  | ||||
| class b : MainActivity() | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| package com.topjohnwu.magisk.arch | ||||
|  | ||||
| import android.content.res.Resources | ||||
| import android.graphics.Color | ||||
| import android.os.Build | ||||
| import android.os.Bundle | ||||
| import android.view.KeyEvent | ||||
| import android.view.View | ||||
| @@ -14,6 +17,7 @@ import androidx.navigation.fragment.NavHostFragment | ||||
| import com.topjohnwu.magisk.BR | ||||
| import com.topjohnwu.magisk.core.Config | ||||
| import com.topjohnwu.magisk.core.base.BaseActivity | ||||
| import com.topjohnwu.magisk.ui.inflater.LayoutInflaterFactory | ||||
| import com.topjohnwu.magisk.ui.theme.Theme | ||||
|  | ||||
| abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> : | ||||
| @@ -41,6 +45,8 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> : | ||||
|     } | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         layoutInflater.factory2 = LayoutInflaterFactory(delegate) | ||||
|  | ||||
|         setTheme(themeRes) | ||||
|         super.onCreate(savedInstanceState) | ||||
|  | ||||
| @@ -59,6 +65,31 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> : | ||||
|                 directionsDispatcher.value = null | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         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 | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun setContentView() { | ||||
| @@ -66,8 +97,6 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> : | ||||
|             it.setVariable(BR.viewModel, viewModel) | ||||
|             it.lifecycleOwner = this | ||||
|         } | ||||
|  | ||||
|         ensureInsets() | ||||
|     } | ||||
|  | ||||
|     fun setAccessibilityDelegate(delegate: View.AccessibilityDelegate?) { | ||||
|   | ||||
| @@ -1,12 +1,9 @@ | ||||
| package com.topjohnwu.magisk.arch | ||||
|  | ||||
| import android.view.View | ||||
| import androidx.core.graphics.Insets | ||||
| import androidx.core.view.ViewCompat | ||||
| import androidx.core.view.WindowInsetsCompat | ||||
| import androidx.lifecycle.LifecycleOwner | ||||
|  | ||||
| interface BaseUIComponent<VM : BaseViewModel>: LifecycleOwner { | ||||
| interface BaseUIComponent<VM : BaseViewModel> : LifecycleOwner { | ||||
|  | ||||
|     val viewRoot: View | ||||
|     val viewModel: VM | ||||
| @@ -17,47 +14,8 @@ interface BaseUIComponent<VM : BaseViewModel>: LifecycleOwner { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun consumeSystemWindowInsets(insets: Insets): Insets? = null | ||||
|  | ||||
|     /** | ||||
|      * Called for all [ViewEvent]s published by associated viewModel. | ||||
|      */ | ||||
|     fun onEventDispatched(event: ViewEvent) {} | ||||
|  | ||||
|     fun ensureInsets() { | ||||
|         ViewCompat.setOnApplyWindowInsetsListener(viewRoot) { _, insets -> | ||||
|             insets.asInsets() | ||||
|                 .also { viewModel.insets = it } | ||||
|                 .let { consumeSystemWindowInsets(it) } | ||||
|                 ?.subtractBy(insets) ?: insets | ||||
|         } | ||||
|         if (ViewCompat.isAttachedToWindow(viewRoot)) { | ||||
|             ViewCompat.requestApplyInsets(viewRoot) | ||||
|         } else { | ||||
|             viewRoot.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { | ||||
|                 override fun onViewDetachedFromWindow(v: View) = Unit | ||||
|                 override fun onViewAttachedToWindow(v: View) { | ||||
|                     ViewCompat.requestApplyInsets(v) | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun WindowInsetsCompat.asInsets() = Insets.of( | ||||
|         systemWindowInsetLeft, | ||||
|         systemWindowInsetTop, | ||||
|         systemWindowInsetRight, | ||||
|         systemWindowInsetBottom | ||||
|     ) | ||||
|  | ||||
|     private fun Insets.subtractBy(insets: WindowInsetsCompat) = | ||||
|         WindowInsetsCompat.Builder(insets).setSystemWindowInsets( | ||||
|             Insets.of( | ||||
|                 insets.systemWindowInsetLeft - left, | ||||
|                 insets.systemWindowInsetTop - top, | ||||
|                 insets.systemWindowInsetRight - right, | ||||
|                 insets.systemWindowInsetBottom - bottom | ||||
|             ) | ||||
|         ).build() | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -24,8 +24,6 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> : | ||||
|     override val viewRoot: View get() = binding.root | ||||
|     private val navigation get() = activity.navigation | ||||
|  | ||||
|     override fun consumeSystemWindowInsets(insets: Insets) = insets | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         startObserveEvents() | ||||
| @@ -43,6 +41,11 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> : | ||||
|         return binding.root | ||||
|     } | ||||
|  | ||||
|     override fun onStart() { | ||||
|         super.onStart() | ||||
|         activity.supportActionBar?.subtitle = null | ||||
|     } | ||||
|  | ||||
|     override fun onEventDispatched(event: ViewEvent) = when(event) { | ||||
|         is ContextExecutor -> event(requireContext()) | ||||
|         is ActivityExecutor -> event(activity) | ||||
| @@ -65,7 +68,6 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> : | ||||
|                 return true | ||||
|             } | ||||
|         }) | ||||
|         ensureInsets() | ||||
|     } | ||||
|  | ||||
|     override fun onResume() { | ||||
|   | ||||
| @@ -18,7 +18,6 @@ import com.topjohnwu.magisk.ktx.inject | ||||
| import com.topjohnwu.magisk.ui.theme.Theme | ||||
| import org.xmlpull.v1.XmlPullParser | ||||
| import java.io.File | ||||
| import java.io.IOException | ||||
| import java.io.InputStream | ||||
|  | ||||
| object Config : PreferenceModel, DBConfig { | ||||
| @@ -159,12 +158,13 @@ object Config : PreferenceModel, DBConfig { | ||||
|  | ||||
|     private const val SU_FINGERPRINT = "su_fingerprint" | ||||
|  | ||||
|     fun load(pkg: String) { | ||||
|         try { | ||||
|     fun load(pkg: String?) { | ||||
|         // Only try to load prefs when fresh install and a previous package name is set | ||||
|         if (pkg != null && prefs.all.isEmpty()) runCatching { | ||||
|             context.contentResolver.openInputStream(Provider.PREFS_URI(pkg))?.use { | ||||
|                 prefs.edit { parsePrefs(it) } | ||||
|             } | ||||
|         } catch (e: IOException) {} | ||||
|         } | ||||
|  | ||||
|         prefs.edit { | ||||
|             // Settings migration | ||||
|   | ||||
| @@ -16,8 +16,6 @@ object Const { | ||||
|     const val BOOTCTL_REVISION = "18ab78817087c337ae0edd1ecac38aec49217880" | ||||
|  | ||||
|     // Misc | ||||
|     const val ANDROID_MANIFEST = "AndroidManifest.xml" | ||||
|     const val MAGISK_INSTALL_LOG_FILENAME = "magisk_install_log_%s.log" | ||||
|     val USER_ID = Process.myUid() / 100000 | ||||
|  | ||||
|     object Version { | ||||
| @@ -27,6 +25,7 @@ object Const { | ||||
|         fun atLeast_20_2() = Info.env.magiskVersionCode >= 20200 || isCanary() | ||||
|         fun atLeast_20_4() = Info.env.magiskVersionCode >= 20400 || isCanary() | ||||
|         fun atLeast_21_0() = Info.env.magiskVersionCode >= 21000 || isCanary() | ||||
|         fun atLeast_21_2() = Info.env.magiskVersionCode >= 21200 || isCanary() | ||||
|         fun isCanary() = Info.env.magiskVersionCode % 100 != 0 | ||||
|     } | ||||
|  | ||||
| @@ -38,7 +37,6 @@ object Const { | ||||
|         // notifications | ||||
|         const val MAGISK_UPDATE_NOTIFICATION_ID = 4 | ||||
|         const val APK_UPDATE_NOTIFICATION_ID = 5 | ||||
|         const val HIDE_MANAGER_NOTIFICATION_ID = 8 | ||||
|         const val UPDATE_NOTIFICATION_CHANNEL = "update" | ||||
|         const val PROGRESS_NOTIFICATION_CHANNEL = "progress" | ||||
|         const val CHECK_MAGISK_UPDATE_WORKER_ID = "magisk_update" | ||||
| @@ -58,7 +56,7 @@ object Const { | ||||
|     object Key { | ||||
|         // intents | ||||
|         const val OPEN_SECTION = "section" | ||||
|         const val HIDDEN_PKG = "hidden_pkg" | ||||
|         const val PREV_PKG = "prev_pkg" | ||||
|     } | ||||
|  | ||||
|     object Value { | ||||
|   | ||||
| @@ -48,10 +48,10 @@ open class SplashActivity : Activity() { | ||||
|         // Pre-initialize root shell | ||||
|         Shell.getShell() | ||||
|  | ||||
|         val hiddenPackage = intent.getStringExtra(Const.Key.HIDDEN_PKG) | ||||
|         val prevPkg = intent.getStringExtra(Const.Key.PREV_PKG) | ||||
|  | ||||
|         Config.load(hiddenPackage ?: APPLICATION_ID) | ||||
|         handleRepackage(hiddenPackage) | ||||
|         Config.load(prevPkg) | ||||
|         handleRepackage(prevPkg) | ||||
|         Notifications.setup(this) | ||||
|         UpdateCheckService.schedule(this) | ||||
|         Shortcuts.setupDynamic(this) | ||||
|   | ||||
| @@ -2,7 +2,7 @@ package com.topjohnwu.magisk.core.download | ||||
|  | ||||
| import android.net.Uri | ||||
| import android.os.Parcelable | ||||
| import kotlinx.android.parcel.Parcelize | ||||
| import kotlinx.parcelize.Parcelize | ||||
|  | ||||
| sealed class Action : Parcelable { | ||||
|  | ||||
|   | ||||
| @@ -12,8 +12,8 @@ import com.topjohnwu.magisk.core.model.module.OnlineModule | ||||
| import com.topjohnwu.magisk.core.utils.MediaStoreUtils | ||||
| import com.topjohnwu.magisk.ktx.cachedFile | ||||
| import com.topjohnwu.magisk.ktx.get | ||||
| import kotlinx.android.parcel.IgnoredOnParcel | ||||
| import kotlinx.android.parcel.Parcelize | ||||
| import kotlinx.parcelize.IgnoredOnParcel | ||||
| import kotlinx.parcelize.Parcelize | ||||
|  | ||||
| private fun cachedFile(name: String) = get<Context>().cachedFile(name).apply { delete() }.toUri() | ||||
|  | ||||
| @@ -101,7 +101,6 @@ sealed class Subject : Parcelable { | ||||
|                 Action.Download -> Download() | ||||
|                 Action.Uninstall -> Uninstall() | ||||
|                 Action.EnvFix, is Action.Flash, is Action.Patch -> Internal(config) | ||||
|                 else -> throw IllegalArgumentException() | ||||
|             } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ package com.topjohnwu.magisk.core.model | ||||
|  | ||||
| import android.os.Parcelable | ||||
| import com.squareup.moshi.JsonClass | ||||
| import kotlinx.android.parcel.Parcelize | ||||
| import kotlinx.parcelize.Parcelize | ||||
|  | ||||
| @JsonClass(generateAdapter = true) | ||||
| data class UpdateInfo( | ||||
|   | ||||
| @@ -27,13 +27,13 @@ class LocalModule(path: String) : Module() { | ||||
|             val dir = "$PERSIST/$id" | ||||
|             if (enable) { | ||||
|                 disableFile.delete() | ||||
|                 if (Const.Version.isCanary()) | ||||
|                 if (Const.Version.atLeast_21_2()) | ||||
|                     Shell.su("copy_sepolicy_rules").submit() | ||||
|                 else | ||||
|                     Shell.su("mkdir -p $dir", "cp -af $ruleFile $dir").submit() | ||||
|             } else { | ||||
|                 !disableFile.createNewFile() | ||||
|                 if (Const.Version.isCanary()) | ||||
|                 if (Const.Version.atLeast_21_2()) | ||||
|                     Shell.su("copy_sepolicy_rules").submit() | ||||
|                 else | ||||
|                     Shell.su("rm -rf $dir").submit() | ||||
| @@ -45,13 +45,13 @@ class LocalModule(path: String) : Module() { | ||||
|         set(remove) { | ||||
|             if (remove) { | ||||
|                 removeFile.createNewFile() | ||||
|                 if (Const.Version.isCanary()) | ||||
|                 if (Const.Version.atLeast_21_2()) | ||||
|                     Shell.su("copy_sepolicy_rules").submit() | ||||
|                 else | ||||
|                     Shell.su("rm -rf $PERSIST/$id").submit() | ||||
|             } else { | ||||
|                 !removeFile.delete() | ||||
|                 if (Const.Version.isCanary()) | ||||
|                 if (Const.Version.atLeast_21_2()) | ||||
|                     Shell.su("copy_sepolicy_rules").submit() | ||||
|                 else | ||||
|                     Shell.su("cp -af $ruleFile $PERSIST/$id").submit() | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import com.topjohnwu.magisk.core.model.ModuleJson | ||||
| import com.topjohnwu.magisk.data.repository.NetworkService | ||||
| import com.topjohnwu.magisk.ktx.get | ||||
| import com.topjohnwu.magisk.ktx.legalFilename | ||||
| import kotlinx.android.parcel.Parcelize | ||||
| import kotlinx.parcelize.Parcelize | ||||
| import java.text.DateFormat | ||||
| import java.util.* | ||||
|  | ||||
|   | ||||
| @@ -33,6 +33,7 @@ object HideAPK { | ||||
|     private const val ALPHA = "abcdefghijklmnopqrstuvwxyz" | ||||
|     private const val ALPHADOTS = "$ALPHA....." | ||||
|     private const val APP_NAME = "Magisk Manager" | ||||
|     private const val ANDROID_MANIFEST = "AndroidManifest.xml" | ||||
|  | ||||
|     // Some arbitrary limit | ||||
|     const val MAX_LABEL_LENGTH = 32 | ||||
| @@ -71,7 +72,7 @@ object HideAPK { | ||||
|     ): Boolean { | ||||
|         try { | ||||
|             val jar = JarMap.open(apk) | ||||
|             val je = jar.getJarEntry(Const.ANDROID_MANIFEST) | ||||
|             val je = jar.getJarEntry(ANDROID_MANIFEST) | ||||
|             val xml = AXML(jar.getRawData(je)) | ||||
|  | ||||
|             if (!xml.findAndPatch(APPLICATION_ID to pkg, APP_NAME to label.toString())) | ||||
| @@ -123,6 +124,7 @@ object HideAPK { | ||||
|             Config.suManager = pkg | ||||
|             grantUriPermission(pkg, APK_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION) | ||||
|             grantUriPermission(pkg, PREFS_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION) | ||||
|             intent.putExtra(Const.Key.PREV_PKG, packageName) | ||||
|             startActivity(intent) | ||||
|         } | ||||
|  | ||||
| @@ -167,7 +169,7 @@ object HideAPK { | ||||
|             Config.suManager = "" | ||||
|             grantUriPermission(APPLICATION_ID, APK_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION) | ||||
|             grantUriPermission(APPLICATION_ID, PREFS_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION) | ||||
|             intent.putExtra(Const.Key.HIDDEN_PKG, packageName) | ||||
|             intent.putExtra(Const.Key.PREV_PKG, packageName) | ||||
|             startActivity(intent) | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -8,8 +8,10 @@ import android.widget.Toast | ||||
| import androidx.annotation.WorkerThread | ||||
| import androidx.core.os.postDelayed | ||||
| import androidx.localbroadcastmanager.content.LocalBroadcastManager | ||||
| import com.topjohnwu.magisk.BuildConfig | ||||
| import com.topjohnwu.magisk.R | ||||
| import com.topjohnwu.magisk.core.Config | ||||
| import com.topjohnwu.magisk.core.Info | ||||
| import com.topjohnwu.magisk.core.utils.MediaStoreUtils | ||||
| import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream | ||||
| import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream | ||||
| @@ -40,6 +42,8 @@ import org.koin.core.inject | ||||
| import timber.log.Timber | ||||
| import java.io.* | ||||
| import java.nio.ByteBuffer | ||||
| import java.security.SecureRandom | ||||
| import java.util.* | ||||
| import java.util.zip.ZipEntry | ||||
| import java.util.zip.ZipInputStream | ||||
|  | ||||
| @@ -107,6 +111,8 @@ abstract class MagiskInstallImpl : KoinComponent { | ||||
|         } | ||||
|  | ||||
|         console.add("- Device platform: " + Build.CPU_ABI) | ||||
|         console.add("- Magisk Manager: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") | ||||
|         console.add("- Install target: ${Info.remote.magisk.version} (${Info.remote.magisk.versionCode})") | ||||
|  | ||||
|         try { | ||||
|             ZipInputStream(zipUri.inputStream().buffered()).use { zi -> | ||||
| @@ -185,9 +191,10 @@ abstract class MagiskInstallImpl : KoinComponent { | ||||
|                     if (rawData.size < 256) | ||||
|                         continue | ||||
|  | ||||
|                     // Patch flags to AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED | ||||
|                     // Patch flags to AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED | | ||||
|                     // AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED | ||||
|                     console.add("-- Patching: vbmeta.img") | ||||
|                     ByteBuffer.wrap(rawData).putInt(120, 2) | ||||
|                     ByteBuffer.wrap(rawData).putInt(120, 3) | ||||
|                     tarOut.putNextEntry(newEntry("vbmeta.img", rawData.size.toLong())) | ||||
|                     tarOut.write(rawData) | ||||
|                 } else { | ||||
| @@ -198,7 +205,7 @@ abstract class MagiskInstallImpl : KoinComponent { | ||||
|             } | ||||
|             val boot = SuFile.open(installDir, "boot.img") | ||||
|             val recovery = SuFile.open(installDir, "recovery.img") | ||||
|             if (recovery.exists() && boot.exists()) { | ||||
|             if (Config.recovery && recovery.exists() && boot.exists()) { | ||||
|                 // Install Magisk to recovery | ||||
|                 srcBoot = recovery.path | ||||
|                 // Repack boot image to prevent restore | ||||
| @@ -225,7 +232,7 @@ abstract class MagiskInstallImpl : KoinComponent { | ||||
|  | ||||
|     private fun handleFile(uri: Uri): Boolean { | ||||
|         val outStream: OutputStream | ||||
|         val outFile: MediaStoreUtils.UriFile | ||||
|         var outFile: MediaStoreUtils.UriFile? = null | ||||
|  | ||||
|         // Process input file | ||||
|         try { | ||||
| @@ -237,27 +244,40 @@ abstract class MagiskInstallImpl : KoinComponent { | ||||
|                     return false | ||||
|                 } | ||||
|                 src.reset() | ||||
|  | ||||
|                 val alpha = "abcdefghijklmnopqrstuvwxyz" | ||||
|                 val alphaNum = "$alpha${alpha.toUpperCase(Locale.ROOT)}0123456789" | ||||
|                 val random = SecureRandom() | ||||
|                 val suffix = StringBuilder() | ||||
|                 for (i in 1..5) { | ||||
|                     suffix.append(alphaNum[random.nextInt(alphaNum.length)]) | ||||
|                 } | ||||
|  | ||||
|                 val filename = "magisk_patched_$suffix" | ||||
|                 outStream = if (magic.contentEquals("ustar".toByteArray())) { | ||||
|                     outFile = MediaStoreUtils.getFile("magisk_patched.tar") | ||||
|                     handleTar(src, outFile.uri.outputStream()) | ||||
|                     outFile = MediaStoreUtils.getFile("$filename.tar", true) | ||||
|                     handleTar(src, outFile!!.uri.outputStream()) | ||||
|                 } else { | ||||
|                     // Raw image | ||||
|                     srcBoot = File(installDir, "boot.img").path | ||||
|                     console.add("- Copying image to cache") | ||||
|                     FileOutputStream(srcBoot).use { src.copyTo(it) } | ||||
|                     outFile = MediaStoreUtils.getFile("magisk_patched.img") | ||||
|                     outFile.uri.outputStream() | ||||
|                     outFile = MediaStoreUtils.getFile("$filename.img", true) | ||||
|                     outFile!!.uri.outputStream() | ||||
|                 } | ||||
|             } | ||||
|         } catch (e: IOException) { | ||||
|             console.add("! Process error") | ||||
|             outFile?.delete() | ||||
|             Timber.e(e) | ||||
|             return false | ||||
|         } | ||||
|  | ||||
|         // Patch file | ||||
|         if (!patchBoot()) | ||||
|         if (!patchBoot()) { | ||||
|             outFile!!.delete() | ||||
|             return false | ||||
|         } | ||||
|  | ||||
|         // Output file | ||||
|         try { | ||||
| @@ -276,6 +296,7 @@ abstract class MagiskInstallImpl : KoinComponent { | ||||
|             console.add("****************************") | ||||
|         } catch (e: IOException) { | ||||
|             console.add("! Failed to output to $outFile") | ||||
|             outFile!!.delete() | ||||
|             Timber.e(e) | ||||
|             return false | ||||
|         } | ||||
| @@ -325,7 +346,7 @@ abstract class MagiskInstallImpl : KoinComponent { | ||||
|             val signed = File(installDir, "signed.img") | ||||
|             try { | ||||
|                 withStreams(SuFileInputStream(patched), signed.outputStream().buffered()) { | ||||
|                     input, out -> SignBoot.doSignature("/boot", input, out, null, null) | ||||
|                     input, out -> SignBoot.doSignature(null, null, input, out, "/boot") | ||||
|                 } | ||||
|             } catch (e: IOException) { | ||||
|                 console.add("! Unable to sign image") | ||||
| @@ -339,6 +360,13 @@ abstract class MagiskInstallImpl : KoinComponent { | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     private fun copySepolicyRules(): Boolean { | ||||
|         if (Info.remote.magisk.versionCode >= 21100) return true | ||||
|         // Copy existing rules for migration | ||||
|         "copy_sepolicy_rules".sh() | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     private fun flashBoot(): Boolean { | ||||
|         if (!"direct_install $installDir $srcBoot".sh().isSuccess) | ||||
|             return false | ||||
| @@ -373,10 +401,11 @@ abstract class MagiskInstallImpl : KoinComponent { | ||||
|  | ||||
|     protected fun doPatchFile(patchFile: Uri) = extractZip() && handleFile(patchFile) | ||||
|  | ||||
|     protected fun direct() = findImage() && extractZip() && patchBoot() && flashBoot() | ||||
|     protected fun direct() = findImage() && extractZip() && patchBoot() && | ||||
|         copySepolicyRules() && flashBoot() | ||||
|  | ||||
|     protected suspend fun secondSlot() = | ||||
|         findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA() | ||||
|     protected suspend fun secondSlot() = findSecondaryImage() && extractZip() && | ||||
|         patchBoot() && copySepolicyRules() && flashBoot() && postOTA() | ||||
|  | ||||
|     protected fun fixEnv(zip: Uri): Boolean { | ||||
|         installDir = SuFile("/data/adb/magisk") | ||||
|   | ||||
| @@ -7,10 +7,12 @@ import android.util.Base64OutputStream | ||||
| import com.topjohnwu.magisk.core.Config | ||||
| import com.topjohnwu.signing.CryptoUtils.readCertificate | ||||
| import com.topjohnwu.signing.CryptoUtils.readPrivateKey | ||||
| import com.topjohnwu.signing.KeyData | ||||
| import org.bouncycastle.asn1.x500.X500Name | ||||
| import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter | ||||
| import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder | ||||
| import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder | ||||
| import java.io.ByteArrayInputStream | ||||
| import java.io.ByteArrayOutputStream | ||||
| import java.math.BigInteger | ||||
| import java.security.KeyPairGenerator | ||||
| @@ -58,10 +60,10 @@ class Keygen(context: Context) : CertKeyProvider { | ||||
|  | ||||
|     class TestProvider : CertKeyProvider { | ||||
|         override val cert by lazy { | ||||
|             readCertificate(javaClass.getResourceAsStream("/keys/testkey.x509.pem")) | ||||
|             readCertificate(ByteArrayInputStream(KeyData.testCert())) | ||||
|         } | ||||
|         override val key by lazy { | ||||
|             readPrivateKey(javaClass.getResourceAsStream("/keys/testkey.pk8")) | ||||
|             readPrivateKey(ByteArrayInputStream(KeyData.testKey())) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| package com.topjohnwu.magisk.core.utils | ||||
|  | ||||
| import android.annotation.SuppressLint | ||||
| import android.content.ContentResolver | ||||
| import android.content.ContentUris | ||||
| import android.content.ContentValues | ||||
| @@ -40,15 +39,17 @@ object MediaStoreUtils { | ||||
|  | ||||
|     private val relativePath get() = relativePath(Config.downloadDir) | ||||
|  | ||||
|     @RequiresApi(api = 29) | ||||
|     @RequiresApi(api = 30) | ||||
|     @Throws(IOException::class) | ||||
|     private fun insertFile(displayName: String): MediaStoreFile { | ||||
|         val values = ContentValues() | ||||
|         values.put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath) | ||||
|         values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName) | ||||
|  | ||||
|         // before Android 11, MediaStore can not rename new file when file exists, | ||||
|         // insert will return null. use newFile() instead. | ||||
|         // When a file with the same name exists and was not created by us: | ||||
|         // - Before Android 11, insert will return null | ||||
|         // - On Android 11+, the system will automatically create a new name | ||||
|         // Thus the reason to restrict this method call to API 30+ | ||||
|         val fileUri = cr.insert(tableUri, values) ?: throw IOException("Can't insert $displayName.") | ||||
|  | ||||
|         val projection = arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA) | ||||
| @@ -65,14 +66,8 @@ object MediaStoreUtils { | ||||
|         throw IOException("Can't insert $displayName.") | ||||
|     } | ||||
|  | ||||
|     @RequiresApi(api = 29) | ||||
|     private fun queryFile(displayName: String): UriFile? { | ||||
|         if (Build.VERSION.SDK_INT < 30) { | ||||
|             // Fallback to file based I/O pre Android 11 | ||||
|             val parent = File(Environment.getExternalStorageDirectory(), relativePath) | ||||
|             parent.mkdirs() | ||||
|             return LegacyUriFile(File(parent, displayName)) | ||||
|         } | ||||
|  | ||||
|         val projection = arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA) | ||||
|         // Before Android 10, we wrote the DISPLAY_NAME field when insert, so it can be used. | ||||
|         val selection = "${MediaStore.MediaColumns.DISPLAY_NAME} == ?" | ||||
| @@ -92,11 +87,17 @@ object MediaStoreUtils { | ||||
|         return null | ||||
|     } | ||||
|  | ||||
|     @SuppressLint("NewApi") | ||||
|     @Throws(IOException::class) | ||||
|     fun getFile(displayName: String): UriFile { | ||||
|         return queryFile(displayName) ?: | ||||
|         /* this code path will never happen pre 29 */ insertFile(displayName) | ||||
|     fun getFile(displayName: String, skipQuery: Boolean = false): UriFile { | ||||
|         if (Build.VERSION.SDK_INT < 30) { | ||||
|             // Fallback to file based I/O pre Android 11 | ||||
|             val parent = File(Environment.getExternalStorageDirectory(), relativePath) | ||||
|             parent.mkdirs() | ||||
|             return LegacyUriFile(File(parent, displayName)) | ||||
|         } | ||||
|  | ||||
|         return if (skipQuery) insertFile(displayName) | ||||
|         else queryFile(displayName) ?: insertFile(displayName) | ||||
|     } | ||||
|  | ||||
|     fun Uri.inputStream() = cr.openInputStream(this) ?: throw FileNotFoundException() | ||||
|   | ||||
| @@ -29,6 +29,6 @@ val viewModelModules = module { | ||||
|     viewModel { MainViewModel() } | ||||
|  | ||||
|     // Legacy | ||||
|     viewModel { (args: FlashFragmentArgs) -> FlashViewModel(args, get()) } | ||||
|     viewModel { (args: FlashFragmentArgs) -> FlashViewModel(args) } | ||||
|     viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) } | ||||
| } | ||||
|   | ||||
							
								
								
									
										193
									
								
								app/src/main/java/com/topjohnwu/magisk/ktx/RecyclerView.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								app/src/main/java/com/topjohnwu/magisk/ktx/RecyclerView.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,193 @@ | ||||
| @file:Suppress("unused") | ||||
|  | ||||
| package com.topjohnwu.magisk.ktx | ||||
|  | ||||
| import android.graphics.Canvas | ||||
| import android.graphics.Rect | ||||
| import android.view.View | ||||
| import android.widget.EdgeEffect | ||||
| import androidx.recyclerview.widget.LinearLayoutManager | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import com.topjohnwu.magisk.R | ||||
|  | ||||
| fun RecyclerView.addInvalidateItemDecorationsObserver() { | ||||
|  | ||||
|     adapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { | ||||
|         override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { | ||||
|             invalidateItemDecorations() | ||||
|         } | ||||
|  | ||||
|         override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { | ||||
|             invalidateItemDecorations() | ||||
|         } | ||||
|  | ||||
|         override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { | ||||
|             invalidateItemDecorations() | ||||
|         } | ||||
|     }) | ||||
| } | ||||
|  | ||||
| fun RecyclerView.addVerticalPadding(paddingTop: Int = 0, paddingBottom: Int = 0) { | ||||
|     addItemDecoration(VerticalPaddingDecoration(paddingTop, paddingBottom)) | ||||
| } | ||||
|  | ||||
| private class VerticalPaddingDecoration(private val paddingTop: Int = 0, private val paddingBottom: Int = 0) : RecyclerView.ItemDecoration() { | ||||
|  | ||||
|     private var allowTop: Boolean = true | ||||
|     private var allowBottom: Boolean = true | ||||
|  | ||||
|     override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { | ||||
|         val adapter = parent.adapter ?: return | ||||
|         val position = parent.getChildAdapterPosition(view) | ||||
|         val count = adapter.itemCount | ||||
|         if (position == 0 && allowTop) { | ||||
|             outRect.top = paddingTop | ||||
|         } else if (position == count - 1 && allowBottom) { | ||||
|             outRect.bottom = paddingBottom | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun RecyclerView.addSimpleItemDecoration( | ||||
|     left: Int = 0, | ||||
|     top: Int = 0, | ||||
|     right: Int = 0, | ||||
|     bottom: Int = 0, | ||||
| ) { | ||||
|     addItemDecoration(SimpleItemDecoration(left, top, right, bottom)) | ||||
| } | ||||
|  | ||||
| private class SimpleItemDecoration( | ||||
|     private val left: Int = 0, | ||||
|     private val top: Int = 0, | ||||
|     private val right: Int = 0, | ||||
|     private val bottom: Int = 0 | ||||
| ) : RecyclerView.ItemDecoration() { | ||||
|  | ||||
|     private var allowLeft: Boolean = true | ||||
|     private var allowTop: Boolean = true | ||||
|     private var allowRight: Boolean = true | ||||
|     private var allowBottom: Boolean = true | ||||
|  | ||||
|     override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { | ||||
|         if (parent.adapter == null) { | ||||
|             return | ||||
|         } | ||||
|         if (allowLeft) { | ||||
|             outRect.left = left | ||||
|         } | ||||
|         if (allowTop) { | ||||
|             outRect.top = top | ||||
|         } | ||||
|         if (allowRight) { | ||||
|             outRect.right = right | ||||
|         } | ||||
|         if (allowBottom) { | ||||
|             outRect.top = bottom | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun RecyclerView.fixEdgeEffect(overScrollIfContentScrolls: Boolean = true, alwaysClipToPadding: Boolean = true) { | ||||
|     if (overScrollIfContentScrolls) { | ||||
|         val listener = OverScrollIfContentScrollsListener() | ||||
|         addOnLayoutChangeListener(listener) | ||||
|         setTag(R.id.tag_rikka_recyclerView_OverScrollIfContentScrollsListener, listener) | ||||
|     } else { | ||||
|         val listener = getTag(R.id.tag_rikka_recyclerView_OverScrollIfContentScrollsListener) as? OverScrollIfContentScrollsListener | ||||
|         if (listener != null) { | ||||
|             removeOnLayoutChangeListener(listener) | ||||
|             setTag(R.id.tag_rikka_recyclerView_OverScrollIfContentScrollsListener, null) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     edgeEffectFactory = if (alwaysClipToPadding && !clipToPadding) { | ||||
|         AlwaysClipToPaddingEdgeEffectFactory() | ||||
|     } else { | ||||
|         RecyclerView.EdgeEffectFactory() | ||||
|     } | ||||
| } | ||||
|  | ||||
| private class OverScrollIfContentScrollsListener : View.OnLayoutChangeListener { | ||||
|     private var show = true | ||||
|     override fun onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) { | ||||
|         if (shouldDrawOverScroll(v as RecyclerView) != show) { | ||||
|             show = !show | ||||
|             if (show) { | ||||
|                 v.setOverScrollMode(View.OVER_SCROLL_IF_CONTENT_SCROLLS) | ||||
|             } else { | ||||
|                 v.setOverScrollMode(View.OVER_SCROLL_NEVER) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun shouldDrawOverScroll(recyclerView: RecyclerView): Boolean { | ||||
|         if (recyclerView.layoutManager == null || recyclerView.adapter == null || recyclerView.adapter!!.itemCount == 0) { | ||||
|             return false | ||||
|         } | ||||
|         if (recyclerView.layoutManager is LinearLayoutManager) { | ||||
|             val itemCount = recyclerView.layoutManager!!.itemCount | ||||
|             val firstPosition: Int = (recyclerView.layoutManager as LinearLayoutManager?)!!.findFirstCompletelyVisibleItemPosition() | ||||
|             val lastPosition: Int = (recyclerView.layoutManager as LinearLayoutManager?)!!.findLastCompletelyVisibleItemPosition() | ||||
|             return firstPosition != 0 || lastPosition != itemCount - 1 | ||||
|         } | ||||
|         return true | ||||
|     } | ||||
| } | ||||
|  | ||||
| private class AlwaysClipToPaddingEdgeEffectFactory : RecyclerView.EdgeEffectFactory() { | ||||
|  | ||||
|     override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect { | ||||
|  | ||||
|         return object : EdgeEffect(view.context) { | ||||
|             private var ensureSize = false | ||||
|  | ||||
|             private fun ensureSize() { | ||||
|                 if (ensureSize) return | ||||
|                 ensureSize = true | ||||
|  | ||||
|                 when (direction) { | ||||
|                     DIRECTION_LEFT -> { | ||||
|                         setSize(view.measuredHeight - view.paddingTop - view.paddingBottom, | ||||
|                             view.measuredWidth - view.paddingLeft - view.paddingRight) | ||||
|                     } | ||||
|                     DIRECTION_TOP -> { | ||||
|                         setSize(view.measuredWidth - view.paddingLeft - view.paddingRight, | ||||
|                             view.measuredHeight - view.paddingTop - view.paddingBottom) | ||||
|                     } | ||||
|                     DIRECTION_RIGHT -> { | ||||
|                         setSize(view.measuredHeight - view.paddingTop - view.paddingBottom, | ||||
|                             view.measuredWidth - view.paddingLeft - view.paddingRight) | ||||
|                     } | ||||
|                     DIRECTION_BOTTOM -> { | ||||
|                         setSize(view.measuredWidth - view.paddingLeft - view.paddingRight, | ||||
|                             view.measuredHeight - view.paddingTop - view.paddingBottom) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             override fun draw(c: Canvas): Boolean { | ||||
|                 ensureSize() | ||||
|  | ||||
|                 val restore = c.save() | ||||
|                 when (direction) { | ||||
|                     DIRECTION_LEFT -> { | ||||
|                         c.translate(view.paddingBottom.toFloat(), 0f) | ||||
|                     } | ||||
|                     DIRECTION_TOP -> { | ||||
|                         c.translate(view.paddingLeft.toFloat(), view.paddingTop.toFloat()) | ||||
|                     } | ||||
|                     DIRECTION_RIGHT -> { | ||||
|                         c.translate(-view.paddingTop.toFloat(), 0f) | ||||
|                     } | ||||
|                     DIRECTION_BOTTOM -> { | ||||
|                         c.translate(view.paddingRight.toFloat(), view.paddingBottom.toFloat()) | ||||
|                     } | ||||
|                 } | ||||
|                 val res = super.draw(c) | ||||
|                 c.restoreToCount(restore) | ||||
|                 return res | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -7,9 +7,11 @@ import android.content.Context | ||||
| import android.content.ContextWrapper | ||||
| import android.content.Intent | ||||
| import android.content.pm.ApplicationInfo | ||||
| import android.content.pm.ComponentInfo | ||||
| import android.content.pm.PackageManager | ||||
| import android.content.pm.PackageManager.* | ||||
| import android.content.pm.ServiceInfo | ||||
| import android.content.pm.ServiceInfo.FLAG_ISOLATED_PROCESS | ||||
| import android.content.pm.ServiceInfo.FLAG_USE_APP_ZYGOTE | ||||
| import android.content.res.Configuration | ||||
| import android.content.res.Resources | ||||
| import android.database.Cursor | ||||
| @@ -57,32 +59,10 @@ import java.lang.reflect.Array as JArray | ||||
|  | ||||
| val packageName: String get() = get<Context>().packageName | ||||
|  | ||||
| val ApplicationInfo.processes: List<String> @SuppressLint("InlinedApi") get() { | ||||
|     val pm = get<PackageManager>() | ||||
|     val appProcessName = processName ?: packageName | ||||
|     val baseFlag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES | ||||
|     val packageInfo = try { | ||||
|         val request = GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS | ||||
|         pm.getPackageInfo(packageName, baseFlag or request) | ||||
|     } catch (e: NameNotFoundException) { // EdXposed hooked, issue#3276 | ||||
|         return listOf(appProcessName) | ||||
|     } catch (e: Exception) { | ||||
|         // Exceed binder data transfer limit, fetch each component type separately | ||||
|         pm.getPackageInfo(packageName, baseFlag).apply { | ||||
|             runCatching { activities = pm.getPackageInfo(packageName, GET_ACTIVITIES).activities } | ||||
|             runCatching { services = pm.getPackageInfo(packageName, GET_SERVICES).services } | ||||
|             runCatching { receivers = pm.getPackageInfo(packageName, GET_RECEIVERS).receivers } | ||||
|             runCatching { providers = pm.getPackageInfo(packageName, GET_PROVIDERS).providers } | ||||
|         } | ||||
|     } | ||||
|     fun Array<out ComponentInfo>.processNames() = map { it.processName ?: appProcessName } | ||||
|     return with(packageInfo) { | ||||
|         activities?.processNames().orEmpty() + | ||||
|         services?.processNames().orEmpty() + | ||||
|         receivers?.processNames().orEmpty() + | ||||
|         providers?.processNames().orEmpty() | ||||
|     } | ||||
| } | ||||
| val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0 | ||||
|  | ||||
| @get:SuppressLint("InlinedApi") | ||||
| val ServiceInfo.useAppZygote get() = (flags and FLAG_USE_APP_ZYGOTE) != 0 | ||||
|  | ||||
| fun Context.rawResource(id: Int) = resources.openRawResource(id) | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,6 @@ import android.view.WindowManager | ||||
| import androidx.coordinatorlayout.widget.CoordinatorLayout | ||||
| import androidx.core.content.pm.ShortcutManagerCompat | ||||
| import androidx.core.view.forEach | ||||
| import androidx.core.view.setPadding | ||||
| import androidx.core.view.updateLayoutParams | ||||
| import androidx.navigation.NavDirections | ||||
| import com.google.android.material.card.MaterialCardView | ||||
| @@ -26,9 +25,9 @@ import com.topjohnwu.magisk.ui.home.HomeFragmentDirections | ||||
| import com.topjohnwu.magisk.utils.HideBottomViewOnScrollBehavior | ||||
| import com.topjohnwu.magisk.utils.HideTopViewOnScrollBehavior | ||||
| 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 com.topjohnwu.superuser.Shell | ||||
| import org.koin.androidx.viewmodel.ext.android.viewModel | ||||
|  | ||||
| class MainViewModel : BaseViewModel() | ||||
| @@ -39,14 +38,6 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>( | ||||
|     override val viewModel by viewModel<MainViewModel>() | ||||
|     override val navHost: Int = R.id.main_nav_host | ||||
|  | ||||
|     //This temporarily fixes unwanted feature of BottomNavigationView - where the view applies | ||||
|     //padding on itself given insets are not consumed beforehand. Unfortunately the listener | ||||
|     //implementation doesn't favor us against the design library, so on re-create it's often given | ||||
|     //upper hand. | ||||
|     private val navObserver = ViewTreeObserver.OnGlobalLayoutListener { | ||||
|         binding.mainNavigation.setPadding(0) | ||||
|     } | ||||
|  | ||||
|     private var isRootFragment = true | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
| @@ -100,8 +91,6 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>( | ||||
|             (currentFragment as? ReselectionTarget)?.onReselected() | ||||
|         } | ||||
|  | ||||
|         binding.mainNavigation.viewTreeObserver.addOnGlobalLayoutListener(navObserver) | ||||
|  | ||||
|         val section = if (intent.action == ACTION_APPLICATION_PREFERENCES) Const.Nav.SETTINGS | ||||
|         else intent.getStringExtra(Const.Key.OPEN_SECTION) | ||||
|         getScreen(section)?.navigate() | ||||
| @@ -116,16 +105,11 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>( | ||||
|     override fun onResume() { | ||||
|         super.onResume() | ||||
|         binding.mainNavigation.menu.apply { | ||||
|             findItem(R.id.superuserFragment)?.isEnabled = Info.env.isActive | ||||
|             findItem(R.id.superuserFragment)?.isEnabled = Utils.showSuperUser() | ||||
|             findItem(R.id.logFragment)?.isEnabled = Info.env.isActive | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun onDestroy() { | ||||
|         binding.mainNavigation.viewTreeObserver.removeOnGlobalLayoutListener(navObserver) | ||||
|         super.onDestroy() | ||||
|     } | ||||
|  | ||||
|     override fun onOptionsItemSelected(item: MenuItem): Boolean { | ||||
|         when (item.itemId) { | ||||
|             android.R.id.home -> onBackPressed() | ||||
|   | ||||
| @@ -32,6 +32,10 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>() | ||||
|         super.onStart() | ||||
|         setHasOptionsMenu(true) | ||||
|         activity.setTitle(R.string.flash_screen_title) | ||||
|  | ||||
|         viewModel.subtitle.observe(this) { | ||||
|             activity.supportActionBar?.setSubtitle(it) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| package com.topjohnwu.magisk.ui.flash | ||||
|  | ||||
| import android.content.res.Resources | ||||
| import android.net.Uri | ||||
| import android.view.MenuItem | ||||
| import androidx.databinding.Bindable | ||||
| import androidx.lifecycle.LiveData | ||||
| import androidx.lifecycle.MutableLiveData | ||||
| import androidx.lifecycle.viewModelScope | ||||
| import com.topjohnwu.magisk.BR | ||||
| import com.topjohnwu.magisk.R | ||||
| @@ -13,31 +14,28 @@ import com.topjohnwu.magisk.arch.itemBindingOf | ||||
| import com.topjohnwu.magisk.core.Const | ||||
| import com.topjohnwu.magisk.core.tasks.FlashZip | ||||
| import com.topjohnwu.magisk.core.tasks.MagiskInstaller | ||||
| import com.topjohnwu.magisk.core.utils.MediaStoreUtils | ||||
| import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream | ||||
| import com.topjohnwu.magisk.databinding.RvBindingAdapter | ||||
| import com.topjohnwu.magisk.events.SnackbarEvent | ||||
| import com.topjohnwu.magisk.ktx.* | ||||
| import com.topjohnwu.magisk.core.utils.MediaStoreUtils | ||||
| import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream | ||||
| import com.topjohnwu.magisk.utils.set | ||||
| import com.topjohnwu.magisk.view.Notifications | ||||
| import com.topjohnwu.superuser.CallbackList | ||||
| import com.topjohnwu.superuser.Shell | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.launch | ||||
| import kotlinx.coroutines.withContext | ||||
|  | ||||
| class FlashViewModel( | ||||
|     args: FlashFragmentArgs, | ||||
|     private val resources: Resources | ||||
|     args: FlashFragmentArgs | ||||
| ) : BaseViewModel() { | ||||
|  | ||||
|     @get:Bindable | ||||
|     var showReboot = Shell.rootAccess() | ||||
|         set(value) = set(value, field, { field = it }, BR.showReboot) | ||||
|  | ||||
|     @get:Bindable | ||||
|     var behaviorText = resources.getString(R.string.flashing) | ||||
|         set(value) = set(value, field, { field = it }, BR.behaviorText) | ||||
|     private val _subtitle = MutableLiveData(R.string.flashing) | ||||
|     val subtitle get() = _subtitle as LiveData<Int> | ||||
|  | ||||
|     val adapter = RvBindingAdapter<ConsoleItem>() | ||||
|     val items = diffListOf<ConsoleItem>() | ||||
| @@ -92,9 +90,9 @@ class FlashViewModel( | ||||
|  | ||||
|     private fun onResult(success: Boolean) { | ||||
|         state = if (success) State.LOADED else State.LOADING_FAILED | ||||
|         behaviorText = when { | ||||
|             success -> resources.getString(R.string.done) | ||||
|             else -> resources.getString(R.string.failure) | ||||
|         when { | ||||
|             success -> _subtitle.postValue(R.string.done) | ||||
|             else -> _subtitle.postValue(R.string.failure) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -106,18 +104,16 @@ class FlashViewModel( | ||||
|     } | ||||
|  | ||||
|     private fun savePressed() = withExternalRW { | ||||
|         viewModelScope.launch { | ||||
|             withContext(Dispatchers.IO) { | ||||
|                 val name = Const.MAGISK_INSTALL_LOG_FILENAME.format(now.toTime(timeFormatStandard)) | ||||
|                 val file = MediaStoreUtils.getFile(name) | ||||
|                 file.uri.outputStream().bufferedWriter().use { writer -> | ||||
|                     logItems.forEach { | ||||
|                         writer.write(it) | ||||
|                         writer.newLine() | ||||
|                     } | ||||
|         viewModelScope.launch(Dispatchers.IO) { | ||||
|             val name = "magisk_install_log_%s.log".format(now.toTime(timeFormatStandard)) | ||||
|             val file = MediaStoreUtils.getFile(name, true) | ||||
|             file.uri.outputStream().bufferedWriter().use { writer -> | ||||
|                 logItems.forEach { | ||||
|                     writer.write(it) | ||||
|                     writer.newLine() | ||||
|                 } | ||||
|                 SnackbarEvent(file.toString()).publish() | ||||
|             } | ||||
|             SnackbarEvent(file.toString()).publish() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user