mirror of
				https://github.com/topjohnwu/Magisk
				synced 2025-11-03 15:52:30 +01:00 
			
		
		
		
	Compare commits
	
		
			132 Commits
		
	
	
		
			canary-270
			...
			canary-281
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					b62835cbeb | ||
| 
						 | 
					6ea740b5ab | ||
| 
						 | 
					7ab98dd5ac | ||
| 
						 | 
					fc8b3400fc | ||
| 
						 | 
					54428ba415 | ||
| 
						 | 
					95d1e69d8e | ||
| 
						 | 
					a0f13ab49f | ||
| 
						 | 
					c3e8405020 | ||
| 
						 | 
					a93593ea66 | ||
| 
						 | 
					23eff70883 | ||
| 
						 | 
					110dd4a8b9 | ||
| 
						 | 
					d9c2bffc9f | ||
| 
						 | 
					049db49dc8 | ||
| 
						 | 
					7c1d2ec61e | ||
| 
						 | 
					a1b2830c06 | ||
| 
						 | 
					82d1d19267 | ||
| 
						 | 
					4d4195c02d | ||
| 
						 | 
					5637a258fc | ||
| 
						 | 
					ee6810f417 | ||
| 
						 | 
					7098248c64 | ||
| 
						 | 
					0d31d356ef | ||
| 
						 | 
					b782e7dcb7 | ||
| 
						 | 
					a4671b4698 | ||
| 
						 | 
					7edd8be169 | ||
| 
						 | 
					24650eefe4 | ||
| 
						 | 
					8e1a44e7eb | ||
| 
						 | 
					2722875190 | ||
| 
						 | 
					3ca6d06f69 | ||
| 
						 | 
					10e47248de | ||
| 
						 | 
					e73ff679ac | ||
| 
						 | 
					53e401fa2d | ||
| 
						 | 
					d2768357da | ||
| 
						 | 
					a6c2ba7c1e | ||
| 
						 | 
					aae5b466fb | ||
| 
						 | 
					2b7be8b949 | ||
| 
						 | 
					b6511a510d | ||
| 
						 | 
					704541aef2 | ||
| 
						 | 
					005560a4c5 | ||
| 
						 | 
					231a5d1853 | ||
| 
						 | 
					9e2b59060d | ||
| 
						 | 
					08ea937f7c | ||
| 
						 | 
					2baedf74d1 | ||
| 
						 | 
					32faa4ced6 | ||
| 
						 | 
					ccdb0b5d13 | ||
| 
						 | 
					8506b672ad | ||
| 
						 | 
					ce2e33bb20 | ||
| 
						 | 
					6707b72260 | ||
| 
						 | 
					5885b8c20d | ||
| 
						 | 
					820710c086 | ||
| 
						 | 
					51cf196bf7 | ||
| 
						 | 
					dadba44cf9 | ||
| 
						 | 
					2ce4a5543b | ||
| 
						 | 
					9112a3a4f5 | ||
| 
						 | 
					24615afda1 | ||
| 
						 | 
					c5778f398b | ||
| 
						 | 
					4eb4097b9b | ||
| 
						 | 
					c512496847 | ||
| 
						 | 
					506961a10d | ||
| 
						 | 
					3414415907 | ||
| 
						 | 
					dc2ae7cfd1 | ||
| 
						 | 
					2e86d21c29 | ||
| 
						 | 
					2654382c43 | ||
| 
						 | 
					9e26b73813 | ||
| 
						 | 
					10cd13bf80 | ||
| 
						 | 
					f10ee5f887 | ||
| 
						 | 
					47cc532d96 | ||
| 
						 | 
					218327f92b | ||
| 
						 | 
					4eae66a1a7 | ||
| 
						 | 
					b09ceeb43c | ||
| 
						 | 
					4fb539c110 | ||
| 
						 | 
					849b284da5 | ||
| 
						 | 
					895b5f6cbf | ||
| 
						 | 
					cb3d4ea514 | ||
| 
						 | 
					0d89a2a97d | ||
| 
						 | 
					3ca5913055 | ||
| 
						 | 
					df6b808f49 | ||
| 
						 | 
					09c7ac754b | ||
| 
						 | 
					805da67c23 | ||
| 
						 | 
					3c6889505b | ||
| 
						 | 
					c8e9ce7627 | ||
| 
						 | 
					837c679a31 | ||
| 
						 | 
					06616659b8 | ||
| 
						 | 
					a34c04f999 | ||
| 
						 | 
					da43ac89a0 | ||
| 
						 | 
					830fc758b9 | ||
| 
						 | 
					0f3cfef278 | ||
| 
						 | 
					b32d7bfafd | ||
| 
						 | 
					c0899f2939 | ||
| 
						 | 
					082330808f | ||
| 
						 | 
					024da05888 | ||
| 
						 | 
					377b6d0cc2 | ||
| 
						 | 
					c661009b31 | ||
| 
						 | 
					613f2d31c5 | ||
| 
						 | 
					7dbb973db5 | ||
| 
						 | 
					f4502f8be8 | ||
| 
						 | 
					455b13b83c | ||
| 
						 | 
					8b98709743 | ||
| 
						 | 
					1b12f45f39 | ||
| 
						 | 
					a5cad532ff | ||
| 
						 | 
					070719db50 | ||
| 
						 | 
					28cccdf7aa | ||
| 
						 | 
					b7e0986a5c | ||
| 
						 | 
					da709745dd | ||
| 
						 | 
					8b6771d487 | ||
| 
						 | 
					e1b847fbc5 | ||
| 
						 | 
					7188de1205 | ||
| 
						 | 
					44fb7dbcbe | ||
| 
						 | 
					8086b5933c | ||
| 
						 | 
					7f675f4bf7 | ||
| 
						 | 
					5e6b53e0da | ||
| 
						 | 
					5b29fefc65 | ||
| 
						 | 
					16a168535d | ||
| 
						 | 
					33f70f8f6d | ||
| 
						 | 
					4f18a66d73 | ||
| 
						 | 
					250dc16007 | ||
| 
						 | 
					7af273e047 | ||
| 
						 | 
					e381aebaa0 | ||
| 
						 | 
					45d91c9658 | ||
| 
						 | 
					4a9158f667 | ||
| 
						 | 
					0d9ee89e7f | ||
| 
						 | 
					abaff72304 | ||
| 
						 | 
					b828e2d0b2 | ||
| 
						 | 
					53d7cbc11b | ||
| 
						 | 
					310be7ab47 | ||
| 
						 | 
					60894e458f | ||
| 
						 | 
					fbebb6ac10 | ||
| 
						 | 
					a9f8c20703 | ||
| 
						 | 
					ae0b15d197 | ||
| 
						 | 
					869aa62328 | ||
| 
						 | 
					dcd3bc58a3 | ||
| 
						 | 
					a82f17c594 | ||
| 
						 | 
					b38fd1ca5f | 
							
								
								
									
										4
									
								
								.github/actions/setup/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/actions/setup/action.yml
									
									
									
									
										vendored
									
									
								
							@@ -6,11 +6,11 @@ inputs:
 | 
			
		||||
runs:
 | 
			
		||||
  using: "composite"
 | 
			
		||||
  steps:
 | 
			
		||||
    - name: Set up JDK 17
 | 
			
		||||
    - name: Set up JDK 21
 | 
			
		||||
      uses: actions/setup-java@v4
 | 
			
		||||
      with:
 | 
			
		||||
        distribution: "temurin"
 | 
			
		||||
        java-version: "17"
 | 
			
		||||
        java-version: "21"
 | 
			
		||||
 | 
			
		||||
    - name: Set up Python 3
 | 
			
		||||
      uses: actions/setup-python@v5
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										27
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,15 +1,6 @@
 | 
			
		||||
name: Magisk Build
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches: [master]
 | 
			
		||||
    paths:
 | 
			
		||||
      - "app/**"
 | 
			
		||||
      - "native/**"
 | 
			
		||||
      - "buildSrc/**"
 | 
			
		||||
      - "build.py"
 | 
			
		||||
      - "gradle.properties"
 | 
			
		||||
      - ".github/workflows/build.yml"
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches: [master]
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
@@ -17,7 +8,7 @@ on:
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
    name: Build Magisk artifacts
 | 
			
		||||
    runs-on: macos-14
 | 
			
		||||
    runs-on: macos-15
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
    steps:
 | 
			
		||||
@@ -60,7 +51,7 @@ jobs:
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        os: [windows-latest, ubuntu-latest]
 | 
			
		||||
        os: [windows-2025, ubuntu-24.04]
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
@@ -78,16 +69,18 @@ jobs:
 | 
			
		||||
 | 
			
		||||
  avd-test:
 | 
			
		||||
    name: Test API ${{ matrix.version }} (x86_64)
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs: build
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]
 | 
			
		||||
        version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
 | 
			
		||||
        type: [""]
 | 
			
		||||
        include:
 | 
			
		||||
          - version: 35
 | 
			
		||||
          - version: "Baklava"
 | 
			
		||||
            type: "google_apis"
 | 
			
		||||
          - version: "Baklava"
 | 
			
		||||
            type: "google_apis_ps16k"
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out
 | 
			
		||||
@@ -122,7 +115,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
  avd-test-32:
 | 
			
		||||
    name: Test API ${{ matrix.version }} (x86)
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs: build
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
@@ -161,7 +154,7 @@ jobs:
 | 
			
		||||
            kernel.log
 | 
			
		||||
            logcat.log
 | 
			
		||||
 | 
			
		||||
  cf_test:
 | 
			
		||||
  cf-test:
 | 
			
		||||
    name: Test ${{ matrix.device }}
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs: build
 | 
			
		||||
@@ -173,8 +166,6 @@ jobs:
 | 
			
		||||
        include:
 | 
			
		||||
          - branch: "aosp-main"
 | 
			
		||||
            device: "aosp_cf_x86_64_phone"
 | 
			
		||||
          - branch: "aosp-main-throttled"
 | 
			
		||||
            device: "aosp_cf_x86_64_phone_pgagnostic"
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -14,6 +14,7 @@ native/out
 | 
			
		||||
*.iml
 | 
			
		||||
.gradle
 | 
			
		||||
.idea
 | 
			
		||||
.kotlin
 | 
			
		||||
/local.properties
 | 
			
		||||
/build
 | 
			
		||||
/captures
 | 
			
		||||
 
 | 
			
		||||
@@ -20,9 +20,9 @@ Some highlight features:
 | 
			
		||||
 | 
			
		||||
Click the icon below to download Magisk apk.
 | 
			
		||||
 | 
			
		||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
 | 
			
		||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
 | 
			
		||||
[](https://github.com/topjohnwu/Magisk/releases/tag/canary-27007)
 | 
			
		||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
 | 
			
		||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
 | 
			
		||||
[](https://github.com/topjohnwu/Magisk/releases/tag/canary-28102)
 | 
			
		||||
 | 
			
		||||
## Useful Links
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,46 +6,37 @@ plugins {
 | 
			
		||||
    id("androidx.navigation.safeargs.kotlin")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
setupAppCommon()
 | 
			
		||||
setupMainApk()
 | 
			
		||||
 | 
			
		||||
kapt {
 | 
			
		||||
    correctErrorTypes = true
 | 
			
		||||
    useBuildCache = true
 | 
			
		||||
    mapDiagnosticLocations = true
 | 
			
		||||
    javacOptions {
 | 
			
		||||
        option("-Xmaxerrs", 1000)
 | 
			
		||||
        option("-Xmaxerrs", "1000")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    namespace = "com.topjohnwu.magisk"
 | 
			
		||||
    buildFeatures {
 | 
			
		||||
        dataBinding = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    defaultConfig {
 | 
			
		||||
        applicationId = "com.topjohnwu.magisk"
 | 
			
		||||
        vectorDrawables.useSupportLibrary = true
 | 
			
		||||
        versionName = Config.version
 | 
			
		||||
        versionCode = Config.versionCode
 | 
			
		||||
        ndk {
 | 
			
		||||
            abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
 | 
			
		||||
            debugSymbolLevel = "FULL"
 | 
			
		||||
        }
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        isCoreLibraryDesugaringEnabled = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildTypes {
 | 
			
		||||
        release {
 | 
			
		||||
            isMinifyEnabled = true
 | 
			
		||||
            isShrinkResources = true
 | 
			
		||||
            proguardFiles("proguard-rules.pro")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildFeatures {
 | 
			
		||||
        dataBinding = true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation(project(":app:core"))
 | 
			
		||||
    coreLibraryDesugaring(libs.jdk.libs)
 | 
			
		||||
 | 
			
		||||
    implementation(libs.indeterminate.checkbox)
 | 
			
		||||
    implementation(libs.rikka.layoutinflater)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,18 @@
 | 
			
		||||
package com.topjohnwu.magisk.dialog
 | 
			
		||||
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import androidx.core.os.postDelayed
 | 
			
		||||
import androidx.lifecycle.lifecycleScope
 | 
			
		||||
import com.topjohnwu.magisk.core.BuildConfig
 | 
			
		||||
import com.topjohnwu.magisk.core.Info
 | 
			
		||||
import com.topjohnwu.magisk.core.R
 | 
			
		||||
import com.topjohnwu.magisk.core.ktx.reboot
 | 
			
		||||
import com.topjohnwu.magisk.core.ktx.toast
 | 
			
		||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
 | 
			
		||||
import com.topjohnwu.magisk.events.DialogBuilder
 | 
			
		||||
import com.topjohnwu.magisk.ui.home.HomeViewModel
 | 
			
		||||
import com.topjohnwu.magisk.view.MagiskDialog
 | 
			
		||||
import com.topjohnwu.superuser.internal.UiThreadHandler
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
 | 
			
		||||
class EnvFixDialog(private val vm: HomeViewModel, private val code: Int) : DialogBuilder {
 | 
			
		||||
@@ -27,9 +32,15 @@ class EnvFixDialog(private val vm: HomeViewModel, private val code: Int) : Dialo
 | 
			
		||||
                        setCancelable(false)
 | 
			
		||||
                    }
 | 
			
		||||
                    dialog.activity.lifecycleScope.launch {
 | 
			
		||||
                        MagiskInstaller.FixEnv {
 | 
			
		||||
                        MagiskInstaller.FixEnv().exec { success ->
 | 
			
		||||
                            dialog.dismiss()
 | 
			
		||||
                        }.exec()
 | 
			
		||||
                            context.toast(
 | 
			
		||||
                                if (success) R.string.reboot_delay_toast else R.string.setup_fail,
 | 
			
		||||
                                Toast.LENGTH_LONG
 | 
			
		||||
                            )
 | 
			
		||||
                            if (success)
 | 
			
		||||
                                UiThreadHandler.handler.postDelayed(5000) { reboot() }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,17 @@
 | 
			
		||||
package com.topjohnwu.magisk.dialog
 | 
			
		||||
 | 
			
		||||
import android.app.ProgressDialog
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import androidx.lifecycle.lifecycleScope
 | 
			
		||||
import com.topjohnwu.magisk.arch.NavigationActivity
 | 
			
		||||
import com.topjohnwu.magisk.arch.UIActivity
 | 
			
		||||
import com.topjohnwu.magisk.core.R
 | 
			
		||||
import com.topjohnwu.magisk.core.ktx.toast
 | 
			
		||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
 | 
			
		||||
import com.topjohnwu.magisk.events.DialogBuilder
 | 
			
		||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
 | 
			
		||||
import com.topjohnwu.magisk.view.MagiskDialog
 | 
			
		||||
import com.topjohnwu.superuser.Shell
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
 | 
			
		||||
class UninstallDialog : DialogBuilder {
 | 
			
		||||
 | 
			
		||||
@@ -19,7 +21,7 @@ class UninstallDialog : DialogBuilder {
 | 
			
		||||
            setMessage(R.string.uninstall_magisk_msg)
 | 
			
		||||
            setButton(MagiskDialog.ButtonType.POSITIVE) {
 | 
			
		||||
                text = R.string.restore_img
 | 
			
		||||
                onClick { restore(dialog.context) }
 | 
			
		||||
                onClick { restore(dialog.activity) }
 | 
			
		||||
            }
 | 
			
		||||
            setButton(MagiskDialog.ButtonType.NEGATIVE) {
 | 
			
		||||
                text = R.string.complete_uninstall
 | 
			
		||||
@@ -29,18 +31,20 @@ class UninstallDialog : DialogBuilder {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Suppress("DEPRECATION")
 | 
			
		||||
    private fun restore(context: Context) {
 | 
			
		||||
        val dialog = ProgressDialog(context).apply {
 | 
			
		||||
            setMessage(context.getString(R.string.restore_img_msg))
 | 
			
		||||
    private fun restore(activity: UIActivity<*>) {
 | 
			
		||||
        val dialog = ProgressDialog(activity).apply {
 | 
			
		||||
            setMessage(activity.getString(R.string.restore_img_msg))
 | 
			
		||||
            show()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Shell.cmd("restore_imgs").submit { result ->
 | 
			
		||||
            dialog.dismiss()
 | 
			
		||||
            if (result.isSuccess) {
 | 
			
		||||
                context.toast(R.string.restore_done, Toast.LENGTH_SHORT)
 | 
			
		||||
            } else {
 | 
			
		||||
                context.toast(R.string.restore_fail, Toast.LENGTH_LONG)
 | 
			
		||||
        activity.lifecycleScope.launch {
 | 
			
		||||
            MagiskInstaller.Restore().exec { success ->
 | 
			
		||||
                dialog.dismiss()
 | 
			
		||||
                if (success) {
 | 
			
		||||
                    activity.toast(R.string.restore_done, Toast.LENGTH_SHORT)
 | 
			
		||||
                } else {
 | 
			
		||||
                    activity.toast(R.string.restore_fail, Toast.LENGTH_LONG)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -71,7 +71,7 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>(), MenuProvider {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        defaultOrientation = activity?.requestedOrientation ?: -1
 | 
			
		||||
        activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
 | 
			
		||||
        activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
 | 
			
		||||
        if (savedInstanceState == null) {
 | 
			
		||||
            viewModel.startFlashing()
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,108 @@
 | 
			
		||||
package com.topjohnwu.magisk.ui.module
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.content.pm.ActivityInfo
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.KeyEvent
 | 
			
		||||
import android.view.Menu
 | 
			
		||||
import android.view.MenuInflater
 | 
			
		||||
import android.view.MenuItem
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewTreeObserver
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import androidx.core.view.MenuProvider
 | 
			
		||||
import androidx.core.view.isVisible
 | 
			
		||||
import com.topjohnwu.magisk.R
 | 
			
		||||
import com.topjohnwu.magisk.arch.BaseFragment
 | 
			
		||||
import com.topjohnwu.magisk.arch.viewModel
 | 
			
		||||
import com.topjohnwu.magisk.core.ktx.toast
 | 
			
		||||
import com.topjohnwu.magisk.databinding.FragmentActionMd2Binding
 | 
			
		||||
import com.topjohnwu.magisk.core.R as CoreR
 | 
			
		||||
 | 
			
		||||
class ActionFragment : BaseFragment<FragmentActionMd2Binding>(), MenuProvider {
 | 
			
		||||
 | 
			
		||||
    override val layoutRes = R.layout.fragment_action_md2
 | 
			
		||||
    override val viewModel by viewModel<ActionViewModel>()
 | 
			
		||||
    override val snackbarView: View get() = binding.snackbarContainer
 | 
			
		||||
 | 
			
		||||
    private var defaultOrientation = -1
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        viewModel.args = ActionFragmentArgs.fromBundle(requireArguments())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onStart() {
 | 
			
		||||
        super.onStart()
 | 
			
		||||
        activity?.setTitle(viewModel.args.name)
 | 
			
		||||
        binding.closeBtn.setOnClickListener {
 | 
			
		||||
            activity?.onBackPressed()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        viewModel.state.observe(this) {
 | 
			
		||||
            if (it != ActionViewModel.State.RUNNING) {
 | 
			
		||||
                binding.closeBtn.apply {
 | 
			
		||||
                    if (!this.isVisible) this.show()
 | 
			
		||||
                    if (!this.isFocused) this.requestFocus()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (it != ActionViewModel.State.SUCCESS) return@observe
 | 
			
		||||
            view?.viewTreeObserver?.addOnWindowFocusChangeListener(
 | 
			
		||||
                object : ViewTreeObserver.OnWindowFocusChangeListener {
 | 
			
		||||
                    override fun onWindowFocusChanged(hasFocus: Boolean) {
 | 
			
		||||
                        if (hasFocus) return
 | 
			
		||||
                        view?.viewTreeObserver?.removeOnWindowFocusChangeListener(this)
 | 
			
		||||
                        view?.context?.apply {
 | 
			
		||||
                            toast(
 | 
			
		||||
                                getString(CoreR.string.done_action, viewModel.args.name),
 | 
			
		||||
                                Toast.LENGTH_SHORT
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
                        viewModel.back()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
 | 
			
		||||
        inflater.inflate(R.menu.menu_flash, menu)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onMenuItemSelected(item: MenuItem): Boolean {
 | 
			
		||||
        return viewModel.onMenuItemClicked(item)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        defaultOrientation = activity?.requestedOrientation ?: -1
 | 
			
		||||
        activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
 | 
			
		||||
        if (savedInstanceState == null) {
 | 
			
		||||
            viewModel.startRunAction()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("WrongConstant")
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        if (defaultOrientation != -1) {
 | 
			
		||||
            activity?.requestedOrientation = defaultOrientation
 | 
			
		||||
        }
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onKeyEvent(event: KeyEvent): Boolean {
 | 
			
		||||
        return when (event.keyCode) {
 | 
			
		||||
            KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> true
 | 
			
		||||
 | 
			
		||||
            else -> false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBackPressed(): Boolean {
 | 
			
		||||
        if (viewModel.state.value == ActionViewModel.State.RUNNING) return true
 | 
			
		||||
        return super.onBackPressed()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onPreBind(binding: FragmentActionMd2Binding) = Unit
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,88 @@
 | 
			
		||||
package com.topjohnwu.magisk.ui.module
 | 
			
		||||
 | 
			
		||||
import android.view.MenuItem
 | 
			
		||||
import androidx.databinding.ObservableArrayList
 | 
			
		||||
import androidx.lifecycle.LiveData
 | 
			
		||||
import androidx.lifecycle.MutableLiveData
 | 
			
		||||
import androidx.lifecycle.viewModelScope
 | 
			
		||||
import com.topjohnwu.magisk.R
 | 
			
		||||
import com.topjohnwu.magisk.arch.BaseViewModel
 | 
			
		||||
import com.topjohnwu.magisk.core.ktx.synchronized
 | 
			
		||||
import com.topjohnwu.magisk.core.ktx.timeFormatStandard
 | 
			
		||||
import com.topjohnwu.magisk.core.ktx.toTime
 | 
			
		||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
 | 
			
		||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
 | 
			
		||||
import com.topjohnwu.magisk.events.SnackbarEvent
 | 
			
		||||
import com.topjohnwu.magisk.ui.flash.ConsoleItem
 | 
			
		||||
import com.topjohnwu.superuser.CallbackList
 | 
			
		||||
import com.topjohnwu.superuser.Shell
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import kotlinx.coroutines.withContext
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
import java.io.IOException
 | 
			
		||||
 | 
			
		||||
class ActionViewModel : BaseViewModel() {
 | 
			
		||||
 | 
			
		||||
    enum class State {
 | 
			
		||||
        RUNNING, SUCCESS, FAILED
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val _state = MutableLiveData(State.RUNNING)
 | 
			
		||||
    val state: LiveData<State> get() = _state
 | 
			
		||||
 | 
			
		||||
    val items = ObservableArrayList<ConsoleItem>()
 | 
			
		||||
    lateinit var args: ActionFragmentArgs
 | 
			
		||||
 | 
			
		||||
    private val logItems = mutableListOf<String>().synchronized()
 | 
			
		||||
    private val outItems = object : CallbackList<String>() {
 | 
			
		||||
        override fun onAddElement(e: String?) {
 | 
			
		||||
            e ?: return
 | 
			
		||||
            items.add(ConsoleItem(e))
 | 
			
		||||
            logItems.add(e)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun startRunAction() = viewModelScope.launch {
 | 
			
		||||
        onResult(withContext(Dispatchers.IO) {
 | 
			
		||||
            try {
 | 
			
		||||
                Shell.cmd("run_action \'${args.id}\'")
 | 
			
		||||
                    .to(outItems, logItems)
 | 
			
		||||
                    .exec().isSuccess
 | 
			
		||||
            } catch (e: IOException) {
 | 
			
		||||
                Timber.e(e)
 | 
			
		||||
                false
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun onResult(success: Boolean) {
 | 
			
		||||
        _state.value = if (success) State.SUCCESS else State.FAILED
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun onMenuItemClicked(item: MenuItem): Boolean {
 | 
			
		||||
        when (item.itemId) {
 | 
			
		||||
            R.id.action_save -> savePressed()
 | 
			
		||||
        }
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun savePressed() = withExternalRW {
 | 
			
		||||
        viewModelScope.launch(Dispatchers.IO) {
 | 
			
		||||
            val name = "%s_action_log_%s.log".format(
 | 
			
		||||
                args.name,
 | 
			
		||||
                System.currentTimeMillis().toTime(timeFormatStandard)
 | 
			
		||||
            )
 | 
			
		||||
            val file = MediaStoreUtils.getFile(name)
 | 
			
		||||
            file.uri.outputStream().bufferedWriter().use { writer ->
 | 
			
		||||
                synchronized(logItems) {
 | 
			
		||||
                    logItems.forEach {
 | 
			
		||||
                        writer.write(it)
 | 
			
		||||
                        writer.newLine()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            SnackbarEvent(file.toString()).publish()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -25,6 +25,7 @@ class LocalModuleRvItem(
 | 
			
		||||
    override val layoutRes = R.layout.item_module_md2
 | 
			
		||||
 | 
			
		||||
    val showNotice: Boolean
 | 
			
		||||
    val showAction: Boolean
 | 
			
		||||
    val noticeText: TextHolder
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
@@ -35,6 +36,7 @@ class LocalModuleRvItem(
 | 
			
		||||
        showNotice = zygiskUnloaded ||
 | 
			
		||||
            (Info.isZygiskEnabled && isRiru) ||
 | 
			
		||||
            (!Info.isZygiskEnabled && isZygisk)
 | 
			
		||||
        showAction = item.hasAction && !showNotice
 | 
			
		||||
        noticeText =
 | 
			
		||||
            when {
 | 
			
		||||
                zygiskUnloaded -> CoreR.string.zygisk_module_unloaded.asText()
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,10 @@ import android.net.Uri
 | 
			
		||||
import androidx.databinding.Bindable
 | 
			
		||||
import androidx.lifecycle.MutableLiveData
 | 
			
		||||
import com.topjohnwu.magisk.BR
 | 
			
		||||
import com.topjohnwu.magisk.MainDirections
 | 
			
		||||
import com.topjohnwu.magisk.R
 | 
			
		||||
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
 | 
			
		||||
import com.topjohnwu.magisk.core.Const
 | 
			
		||||
import com.topjohnwu.magisk.core.Info
 | 
			
		||||
import com.topjohnwu.magisk.core.base.ContentResultCallback
 | 
			
		||||
import com.topjohnwu.magisk.core.model.module.LocalModule
 | 
			
		||||
@@ -96,6 +98,10 @@ class ModuleViewModel : AsyncLoadViewModel() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun runAction(id: String, name: String) {
 | 
			
		||||
        MainDirections.actionActionFragment(id, name).navigate()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val uri = MutableLiveData<Uri?>()
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ import com.topjohnwu.magisk.BR
 | 
			
		||||
import com.topjohnwu.magisk.arch.BaseViewModel
 | 
			
		||||
import com.topjohnwu.magisk.core.AppContext
 | 
			
		||||
import com.topjohnwu.magisk.core.BuildConfig
 | 
			
		||||
import com.topjohnwu.magisk.core.Config
 | 
			
		||||
import com.topjohnwu.magisk.core.Const
 | 
			
		||||
import com.topjohnwu.magisk.core.Info
 | 
			
		||||
import com.topjohnwu.magisk.core.R
 | 
			
		||||
@@ -92,7 +93,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
 | 
			
		||||
            DownloadPath -> withExternalRW(doAction)
 | 
			
		||||
            UpdateChecker -> withPostNotificationPermission(doAction)
 | 
			
		||||
            Authentication -> AuthEvent(doAction).publish()
 | 
			
		||||
            Hide, Restore -> withInstallPermission(doAction)
 | 
			
		||||
            AutomaticResponse -> if (Config.suAuth) AuthEvent(doAction).publish() else doAction()
 | 
			
		||||
            else -> doAction()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -78,8 +78,8 @@ class SuperuserViewModel(
 | 
			
		||||
                            this@SuperuserViewModel, policy,
 | 
			
		||||
                            info.packageName,
 | 
			
		||||
                            info.sharedUserId != null,
 | 
			
		||||
                            info.applicationInfo.loadIcon(pm),
 | 
			
		||||
                            info.applicationInfo.getLabel(pm)
 | 
			
		||||
                            info.applicationInfo?.loadIcon(pm) ?: pm.defaultActivityIcon,
 | 
			
		||||
                            info.applicationInfo?.getLabel(pm) ?: info.packageName
 | 
			
		||||
                        )
 | 
			
		||||
                    } catch (e: PackageManager.NameNotFoundException) {
 | 
			
		||||
                        null
 | 
			
		||||
 
 | 
			
		||||
@@ -111,7 +111,7 @@ class SuRequestViewModel(
 | 
			
		||||
            // shared UID. We have no way to know where this request comes from.
 | 
			
		||||
            icon = pm.defaultActivityIcon
 | 
			
		||||
            title = "[SharedUID] ${info.sharedUserId}"
 | 
			
		||||
            packageName = info.sharedUserId
 | 
			
		||||
            packageName = info.sharedUserId.toString()
 | 
			
		||||
        } else {
 | 
			
		||||
            val prefix = if (info.sharedUserId == null) "" else "[SharedUID] "
 | 
			
		||||
            icon = app.loadIcon(pm)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								app/apk/src/main/res/drawable/ic_action_md2.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/apk/src/main/res/drawable/ic_action_md2.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
 | 
			
		||||
 | 
			
		||||
    <path android:fillColor="@android:color/white" android:pathData="M8,5v14l11,-7z"/>
 | 
			
		||||
 | 
			
		||||
</vector>
 | 
			
		||||
							
								
								
									
										68
									
								
								app/apk/src/main/res/layout/fragment_action_md2.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								app/apk/src/main/res/layout/fragment_action_md2.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools">
 | 
			
		||||
 | 
			
		||||
    <data>
 | 
			
		||||
 | 
			
		||||
        <variable
 | 
			
		||||
            name="viewModel"
 | 
			
		||||
            type="com.topjohnwu.magisk.ui.module.ActionViewModel" />
 | 
			
		||||
 | 
			
		||||
    </data>
 | 
			
		||||
 | 
			
		||||
    <androidx.coordinatorlayout.widget.CoordinatorLayout
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent">
 | 
			
		||||
 | 
			
		||||
        <HorizontalScrollView
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="match_parent"
 | 
			
		||||
            android:layout_marginTop="@dimen/internal_action_bar_size"
 | 
			
		||||
            app:layout_fitsSystemWindowsInsets="top"
 | 
			
		||||
            tools:layout_marginTop="@dimen/internal_action_bar_size">
 | 
			
		||||
 | 
			
		||||
            <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
                android:id="@+id/flash_content"
 | 
			
		||||
                scrollToLast="@{true}"
 | 
			
		||||
                android:layout_width="wrap_content"
 | 
			
		||||
                android:layout_height="match_parent"
 | 
			
		||||
                android:clipToPadding="false"
 | 
			
		||||
                android:orientation="vertical"
 | 
			
		||||
                app:fitsSystemWindowsInsets="start|end|bottom"
 | 
			
		||||
                app:items="@{viewModel.items}"
 | 
			
		||||
                app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
 | 
			
		||||
                app:layout_behavior="@string/appbar_scrolling_view_behavior"
 | 
			
		||||
                tools:listitem="@layout/item_console_md2" />
 | 
			
		||||
 | 
			
		||||
        </HorizontalScrollView>
 | 
			
		||||
 | 
			
		||||
        <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
 | 
			
		||||
            android:id="@+id/close_btn"
 | 
			
		||||
            android:visibility="gone"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_gravity="bottom|end"
 | 
			
		||||
            android:layout_margin="@dimen/l1"
 | 
			
		||||
            android:layout_marginBottom="@dimen/l1"
 | 
			
		||||
            android:clickable="true"
 | 
			
		||||
            android:enabled="true"
 | 
			
		||||
            android:focusable="true"
 | 
			
		||||
            android:text="@string/close"
 | 
			
		||||
            android:textAllCaps="false"
 | 
			
		||||
            android:textColor="?colorOnPrimary"
 | 
			
		||||
            android:textStyle="bold"
 | 
			
		||||
            app:backgroundTint="?colorPrimary"
 | 
			
		||||
            app:icon="@drawable/ic_close_md2"
 | 
			
		||||
            app:iconTint="?colorOnPrimary"
 | 
			
		||||
            app:layout_fitsSystemWindowsInsets="bottom" />
 | 
			
		||||
 | 
			
		||||
        <androidx.coordinatorlayout.widget.CoordinatorLayout
 | 
			
		||||
            android:id="@+id/snackbar_container"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="match_parent"
 | 
			
		||||
            app:fitsSystemWindowsInsets="top|bottom" />
 | 
			
		||||
 | 
			
		||||
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
 | 
			
		||||
 | 
			
		||||
</layout>
 | 
			
		||||
@@ -189,12 +189,32 @@
 | 
			
		||||
                    android:textColor="?colorError"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="@+id/module_remove"
 | 
			
		||||
                    app:layout_constraintEnd_toStartOf="@+id/bottom_bar_barrier"
 | 
			
		||||
                    app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
                    app:layout_constraintStart_toEndOf="@id/module_config"
 | 
			
		||||
                    app:layout_constraintTop_toTopOf="@+id/module_remove"
 | 
			
		||||
                    tools:lines="2"
 | 
			
		||||
                    tools:text="@tools:sample/lorem/random"
 | 
			
		||||
                    tools:visibility="visible" />
 | 
			
		||||
 | 
			
		||||
                <Button
 | 
			
		||||
                    android:id="@+id/module_config"
 | 
			
		||||
                    style="@style/WidgetFoundation.Button.Text"
 | 
			
		||||
                    goneUnless="@{item.showAction}"
 | 
			
		||||
                    android:layout_width="wrap_content"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:clickable="true"
 | 
			
		||||
                    android:enabled="@{item.enabled}"
 | 
			
		||||
                    android:focusable="true"
 | 
			
		||||
                    android:onClick="@{() -> viewModel.runAction(item.item.id, item.item.name)}"
 | 
			
		||||
                    android:text="@string/module_action"
 | 
			
		||||
                    android:textAllCaps="false"
 | 
			
		||||
                    android:visibility="gone"
 | 
			
		||||
                    app:icon="@drawable/ic_action_md2"
 | 
			
		||||
                    app:iconGravity="textStart"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="@+id/module_remove"
 | 
			
		||||
                    app:layout_constraintTop_toTopOf="@+id/module_remove"
 | 
			
		||||
                    app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
                    app:srcCompat="@drawable/ic_download_md2" />
 | 
			
		||||
 | 
			
		||||
            </androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
 | 
			
		||||
        </com.google.android.material.card.MaterialCardView>
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,21 @@
 | 
			
		||||
 | 
			
		||||
    </fragment>
 | 
			
		||||
 | 
			
		||||
    <fragment
 | 
			
		||||
        android:id="@+id/actionFragment"
 | 
			
		||||
        android:name="com.topjohnwu.magisk.ui.module.ActionFragment"
 | 
			
		||||
        android:label="ActionFragment"
 | 
			
		||||
        tools:layout="@layout/fragment_action_md2" >
 | 
			
		||||
 | 
			
		||||
        <argument
 | 
			
		||||
            android:name="id"
 | 
			
		||||
            app:argType="string" />
 | 
			
		||||
 | 
			
		||||
        <argument
 | 
			
		||||
            android:name="name"
 | 
			
		||||
            app:argType="string" />
 | 
			
		||||
    </fragment>
 | 
			
		||||
 | 
			
		||||
    <fragment
 | 
			
		||||
        android:id="@+id/installFragment"
 | 
			
		||||
        android:name="com.topjohnwu.magisk.ui.install.InstallFragment"
 | 
			
		||||
@@ -152,4 +167,12 @@
 | 
			
		||||
        app:popEnterAnim="@anim/fragment_enter_pop"
 | 
			
		||||
        app:popExitAnim="@anim/fragment_exit_pop" />
 | 
			
		||||
 | 
			
		||||
    <action
 | 
			
		||||
        android:id="@+id/action_actionFragment"
 | 
			
		||||
        app:destination="@id/actionFragment"
 | 
			
		||||
        app:enterAnim="@anim/fragment_enter"
 | 
			
		||||
        app:exitAnim="@anim/fragment_exit"
 | 
			
		||||
        app:popEnterAnim="@anim/fragment_enter_pop"
 | 
			
		||||
        app:popExitAnim="@anim/fragment_exit_pop" />
 | 
			
		||||
 | 
			
		||||
</navigation>
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ android {
 | 
			
		||||
        buildConfigField("int", "APP_VERSION_CODE", "${Config.versionCode}")
 | 
			
		||||
        buildConfigField("String", "APP_VERSION_NAME", "\"${Config.version}\"")
 | 
			
		||||
        buildConfigField("int", "STUB_VERSION", Config.stubVersion)
 | 
			
		||||
        consumerProguardFile("proguard-rules.pro")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildFeatures {
 | 
			
		||||
@@ -59,5 +60,10 @@ dependencies {
 | 
			
		||||
    implementation(libs.activity)
 | 
			
		||||
    implementation(libs.collection.ktx)
 | 
			
		||||
    implementation(libs.profileinstaller)
 | 
			
		||||
    implementation(libs.lifecycle.process)
 | 
			
		||||
 | 
			
		||||
    // We also implement all our tests in this module.
 | 
			
		||||
    // However, we don't want to bundle test dependencies.
 | 
			
		||||
    // That's why we make it compileOnly.
 | 
			
		||||
    compileOnly(libs.test.junit)
 | 
			
		||||
    compileOnly(libs.test.uiautomator)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,42 +22,19 @@
 | 
			
		||||
  int mActivityHandlesConfigFlags;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# main
 | 
			
		||||
-keep,allowoptimization public class com.topjohnwu.magisk.signing.SignBoot {
 | 
			
		||||
    public static void main(java.lang.String[]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Strip Timber verbose and debug logging
 | 
			
		||||
-assumenosideeffects class timber.log.Timber$Tree {
 | 
			
		||||
  public void v(**);
 | 
			
		||||
  public void d(**);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# https://github.com/square/retrofit/issues/3751#issuecomment-1192043644
 | 
			
		||||
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
 | 
			
		||||
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
 | 
			
		||||
-keep,allowobfuscation,allowshrinking class retrofit2.Response
 | 
			
		||||
 | 
			
		||||
# With R8 full mode generic signatures are stripped for classes that are not
 | 
			
		||||
# kept. Suspend functions are wrapped in continuations where the type argument
 | 
			
		||||
# is used.
 | 
			
		||||
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Excessive obfuscation
 | 
			
		||||
-repackageclasses 'a'
 | 
			
		||||
-flattenpackagehierarchy
 | 
			
		||||
-allowaccessmodification
 | 
			
		||||
 | 
			
		||||
-obfuscationdictionary ../dict.txt
 | 
			
		||||
-classobfuscationdictionary ../dict.txt
 | 
			
		||||
-packageobfuscationdictionary ../dict.txt
 | 
			
		||||
 | 
			
		||||
-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
 | 
			
		||||
-dontwarn org.junit.**
 | 
			
		||||
@@ -16,7 +16,6 @@ import com.topjohnwu.magisk.StubApk
 | 
			
		||||
import com.topjohnwu.magisk.core.base.UntrackedActivity
 | 
			
		||||
import com.topjohnwu.magisk.core.utils.LocaleSetting
 | 
			
		||||
import com.topjohnwu.magisk.core.utils.NetworkObserver
 | 
			
		||||
import com.topjohnwu.magisk.core.utils.ProcessLifecycle
 | 
			
		||||
import com.topjohnwu.magisk.core.utils.RootUtils
 | 
			
		||||
import com.topjohnwu.magisk.core.utils.ShellInit
 | 
			
		||||
import com.topjohnwu.superuser.Shell
 | 
			
		||||
@@ -40,6 +39,7 @@ object AppContext : ContextWrapper(null),
 | 
			
		||||
 | 
			
		||||
    private var ref = WeakReference<Activity>(null)
 | 
			
		||||
    private lateinit var application: Application
 | 
			
		||||
    private lateinit var networkObserver: NetworkObserver
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        // Always log full stack trace with Timber
 | 
			
		||||
@@ -56,6 +56,10 @@ object AppContext : ContextWrapper(null),
 | 
			
		||||
        LocaleSetting.instance.updateResource(resources)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onActivityStarted(activity: Activity) {
 | 
			
		||||
        networkObserver.postCurrentState()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onActivityResumed(activity: Activity) {
 | 
			
		||||
        if (activity is UntrackedActivity) return
 | 
			
		||||
        ref = WeakReference(activity)
 | 
			
		||||
@@ -102,8 +106,7 @@ object AppContext : ContextWrapper(null),
 | 
			
		||||
            val lm = getSystemService(LocaleManager::class.java)
 | 
			
		||||
            lm.overrideLocaleConfig = LocaleSetting.localeConfig
 | 
			
		||||
        }
 | 
			
		||||
        ProcessLifecycle.init(this)
 | 
			
		||||
        NetworkObserver.init(this)
 | 
			
		||||
        networkObserver = NetworkObserver.init(this)
 | 
			
		||||
        if (!BuildConfig.DEBUG && !isRunningAsStub) {
 | 
			
		||||
            GlobalScope.launch(Dispatchers.IO) {
 | 
			
		||||
                ProfileInstaller.writeProfile(this@AppContext)
 | 
			
		||||
@@ -120,7 +123,6 @@ object AppContext : ContextWrapper(null),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
 | 
			
		||||
    override fun onActivityStarted(activity: Activity) {}
 | 
			
		||||
    override fun onActivityStopped(activity: Activity) {}
 | 
			
		||||
    override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
 | 
			
		||||
    override fun onActivityDestroyed(activity: Activity) {}
 | 
			
		||||
 
 | 
			
		||||
@@ -83,7 +83,7 @@ object Config : PreferenceConfig, DBConfig {
 | 
			
		||||
        const val SU_AUTO_ALLOW = 2
 | 
			
		||||
 | 
			
		||||
        // su timeout
 | 
			
		||||
        val TIMEOUT_LIST = intArrayOf(0, -1, 10, 20, 30, 60)
 | 
			
		||||
        val TIMEOUT_LIST = longArrayOf(0, -1, 10, 20, 30, 60)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val defaultChannel =
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import androidx.core.content.getSystemService
 | 
			
		||||
import com.topjohnwu.magisk.core.base.BaseJobService
 | 
			
		||||
import com.topjohnwu.magisk.core.di.ServiceLocator
 | 
			
		||||
import com.topjohnwu.magisk.core.download.DownloadEngine
 | 
			
		||||
import com.topjohnwu.magisk.core.download.DownloadSession
 | 
			
		||||
import com.topjohnwu.magisk.core.download.Subject
 | 
			
		||||
import com.topjohnwu.magisk.view.Notifications
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
@@ -25,7 +26,7 @@ class JobService : BaseJobService() {
 | 
			
		||||
    @TargetApi(value = 34)
 | 
			
		||||
    inner class Session(
 | 
			
		||||
        private var params: JobParameters
 | 
			
		||||
    ) : DownloadEngine.Session {
 | 
			
		||||
    ) : DownloadSession {
 | 
			
		||||
 | 
			
		||||
        override val context get() = this@JobService
 | 
			
		||||
        val engine = DownloadEngine(this)
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ package com.topjohnwu.magisk.core
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import com.topjohnwu.magisk.core.base.BaseProvider
 | 
			
		||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
 | 
			
		||||
import com.topjohnwu.magisk.core.su.TestHandler
 | 
			
		||||
 | 
			
		||||
class Provider : BaseProvider() {
 | 
			
		||||
 | 
			
		||||
@@ -13,7 +12,7 @@ class Provider : BaseProvider() {
 | 
			
		||||
                SuCallbackHandler.run(context!!, method, extras)
 | 
			
		||||
                Bundle.EMPTY
 | 
			
		||||
            }
 | 
			
		||||
            else -> TestHandler.run(method)
 | 
			
		||||
            else -> Bundle.EMPTY
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,9 +7,10 @@ import androidx.core.app.ServiceCompat
 | 
			
		||||
import androidx.core.content.IntentCompat
 | 
			
		||||
import com.topjohnwu.magisk.core.base.BaseService
 | 
			
		||||
import com.topjohnwu.magisk.core.download.DownloadEngine
 | 
			
		||||
import com.topjohnwu.magisk.core.download.DownloadSession
 | 
			
		||||
import com.topjohnwu.magisk.core.download.Subject
 | 
			
		||||
 | 
			
		||||
class Service : BaseService(), DownloadEngine.Session {
 | 
			
		||||
class Service : BaseService(), DownloadSession {
 | 
			
		||||
 | 
			
		||||
    private var mEngine: DownloadEngine? = null
 | 
			
		||||
    override val context get() = this
 | 
			
		||||
 
 | 
			
		||||
@@ -7,9 +7,13 @@ import kotlinx.coroutines.withContext
 | 
			
		||||
 | 
			
		||||
open class MagiskDB {
 | 
			
		||||
 | 
			
		||||
    suspend fun <R> exec(
 | 
			
		||||
    class Literal(
 | 
			
		||||
        val str: String
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    suspend inline fun <R> exec(
 | 
			
		||||
        query: String,
 | 
			
		||||
        mapper: suspend (Map<String, String>) -> R
 | 
			
		||||
        crossinline mapper: (Map<String, String>) -> R
 | 
			
		||||
    ): List<R> {
 | 
			
		||||
        return withContext(Dispatchers.IO) {
 | 
			
		||||
            val out = Shell.cmd("magisk --sqlite '$query'").await().out
 | 
			
		||||
@@ -18,13 +22,15 @@ open class MagiskDB {
 | 
			
		||||
                    .map { it.split("=", limit = 2) }
 | 
			
		||||
                    .filter { it.size == 2 }
 | 
			
		||||
                    .associate { it[0] to it[1] }
 | 
			
		||||
                    .let { mapper(it) }
 | 
			
		||||
                    .let(mapper)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend inline fun exec(query: String) {
 | 
			
		||||
        exec(query) {}
 | 
			
		||||
    suspend fun exec(query: String) {
 | 
			
		||||
        withContext(Dispatchers.IO) {
 | 
			
		||||
            Shell.cmd("magisk --sqlite '$query'").await()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun Map<String, Any>.toQuery(): String {
 | 
			
		||||
@@ -33,6 +39,7 @@ open class MagiskDB {
 | 
			
		||||
            when (it) {
 | 
			
		||||
                is Boolean -> if (it) "1" else "0"
 | 
			
		||||
                is Number -> it.toString()
 | 
			
		||||
                is Literal -> it.str
 | 
			
		||||
                else -> "\"$it\""
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,24 +3,24 @@ package com.topjohnwu.magisk.core.data.magiskdb
 | 
			
		||||
import com.topjohnwu.magisk.core.AppContext
 | 
			
		||||
import com.topjohnwu.magisk.core.Const
 | 
			
		||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
private const val SELECT_QUERY = "SELECT (until - strftime(\"%s\", \"now\")) AS remain, *"
 | 
			
		||||
 | 
			
		||||
class PolicyDao : MagiskDB() {
 | 
			
		||||
 | 
			
		||||
    suspend fun deleteOutdated() {
 | 
			
		||||
        val nowSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
 | 
			
		||||
        val query = "DELETE FROM ${Table.POLICY} WHERE " +
 | 
			
		||||
            "(until > 0 AND until < $nowSeconds) OR until < 0"
 | 
			
		||||
            "(until > 0 AND until < strftime(\"%s\", \"now\")) OR until < 0"
 | 
			
		||||
        exec(query)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun delete(uid: Int) {
 | 
			
		||||
        val query = "DELETE FROM ${Table.POLICY} WHERE uid == $uid"
 | 
			
		||||
        val query = "DELETE FROM ${Table.POLICY} WHERE uid=$uid"
 | 
			
		||||
        exec(query)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun fetch(uid: Int): SuPolicy? {
 | 
			
		||||
        val query = "SELECT * FROM ${Table.POLICY} WHERE uid == $uid LIMIT = 1"
 | 
			
		||||
        val query = "$SELECT_QUERY FROM ${Table.POLICY} WHERE uid=$uid LIMIT 1"
 | 
			
		||||
        return exec(query, ::toPolicy).firstOrNull()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -35,7 +35,7 @@ class PolicyDao : MagiskDB() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun fetchAll(): List<SuPolicy> {
 | 
			
		||||
        val query = "SELECT * FROM ${Table.POLICY} WHERE uid/100000 == ${Const.USER_ID}"
 | 
			
		||||
        val query = "$SELECT_QUERY FROM ${Table.POLICY} WHERE uid/100000=${Const.USER_ID}"
 | 
			
		||||
        return exec(query, ::toPolicy).filterNotNull()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -43,8 +43,15 @@ class PolicyDao : MagiskDB() {
 | 
			
		||||
        val uid = map["uid"]?.toInt() ?: return null
 | 
			
		||||
        val policy = SuPolicy(uid)
 | 
			
		||||
 | 
			
		||||
        map["until"]?.toLong()?.let { until ->
 | 
			
		||||
            if (until <= 0) {
 | 
			
		||||
                policy.remain = until
 | 
			
		||||
            } else {
 | 
			
		||||
                map["remain"]?.toLong()?.let { policy.remain = it }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        map["policy"]?.toInt()?.let { policy.policy = it }
 | 
			
		||||
        map["until"]?.toLong()?.let { policy.until = it }
 | 
			
		||||
        map["logging"]?.toInt()?.let { policy.logging = it != 0 }
 | 
			
		||||
        map["notification"]?.toInt()?.let { policy.notification = it != 0 }
 | 
			
		||||
        return policy
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ package com.topjohnwu.magisk.core.data.magiskdb
 | 
			
		||||
class SettingsDao : MagiskDB() {
 | 
			
		||||
 | 
			
		||||
    suspend fun delete(key: String) {
 | 
			
		||||
        val query = "DELETE FROM ${Table.SETTINGS} WHERE key == \"$key\""
 | 
			
		||||
        val query = "DELETE FROM ${Table.SETTINGS} WHERE key=\"$key\""
 | 
			
		||||
        exec(query)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -14,7 +14,7 @@ class SettingsDao : MagiskDB() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun fetch(key: String, default: Int = -1): Int {
 | 
			
		||||
        val query = "SELECT value FROM ${Table.SETTINGS} WHERE key == \"$key\" LIMIT 1"
 | 
			
		||||
        val query = "SELECT value FROM ${Table.SETTINGS} WHERE key=\"$key\" LIMIT 1"
 | 
			
		||||
        return exec(query) { it["value"]?.toInt() }.firstOrNull() ?: default
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ package com.topjohnwu.magisk.core.data.magiskdb
 | 
			
		||||
class StringDao : MagiskDB() {
 | 
			
		||||
 | 
			
		||||
    suspend fun delete(key: String) {
 | 
			
		||||
        val query = "DELETE FROM ${Table.STRINGS} WHERE key == \"$key\""
 | 
			
		||||
        val query = "DELETE FROM ${Table.STRINGS} WHERE key=\"$key\""
 | 
			
		||||
        exec(query)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -14,7 +14,7 @@ class StringDao : MagiskDB() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun fetch(key: String, default: String = ""): String {
 | 
			
		||||
        val query = "SELECT value FROM ${Table.STRINGS} WHERE key == \"$key\" LIMIT 1"
 | 
			
		||||
        val query = "SELECT value FROM ${Table.STRINGS} WHERE key=\"$key\" LIMIT 1"
 | 
			
		||||
        return exec(query) { it["value"] }.firstOrNull() ?: default
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -0,0 +1,122 @@
 | 
			
		||||
package com.topjohnwu.magisk.core.download
 | 
			
		||||
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import com.topjohnwu.magisk.StubApk
 | 
			
		||||
import com.topjohnwu.magisk.core.R
 | 
			
		||||
import com.topjohnwu.magisk.core.isRunningAsStub
 | 
			
		||||
import com.topjohnwu.magisk.core.ktx.cachedFile
 | 
			
		||||
import com.topjohnwu.magisk.core.ktx.copyAll
 | 
			
		||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
 | 
			
		||||
import com.topjohnwu.magisk.core.ktx.withInOut
 | 
			
		||||
import com.topjohnwu.magisk.core.ktx.writeTo
 | 
			
		||||
import com.topjohnwu.magisk.core.tasks.AppMigration
 | 
			
		||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
 | 
			
		||||
import com.topjohnwu.magisk.utils.APKInstall
 | 
			
		||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
 | 
			
		||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
 | 
			
		||||
import org.apache.commons.compress.archivers.zip.ZipFile
 | 
			
		||||
import java.io.IOException
 | 
			
		||||
import java.io.InputStream
 | 
			
		||||
import java.io.OutputStream
 | 
			
		||||
 | 
			
		||||
class DownloadProcessor(notifier: DownloadNotifier) : DownloadNotifier by notifier {
 | 
			
		||||
 | 
			
		||||
    suspend fun handle(stream: InputStream, subject: Subject) {
 | 
			
		||||
        when (subject) {
 | 
			
		||||
            is Subject.App -> handleApp(stream, subject)
 | 
			
		||||
            is Subject.Module -> handleModule(stream, subject.file)
 | 
			
		||||
            else -> stream.copyAndClose(subject.file.outputStream())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun handleApp(stream: InputStream, subject: Subject.App) {
 | 
			
		||||
        val external = subject.file.outputStream()
 | 
			
		||||
 | 
			
		||||
        if (isRunningAsStub) {
 | 
			
		||||
            val updateApk = StubApk.update(context)
 | 
			
		||||
            try {
 | 
			
		||||
                // Download full APK to stub update path
 | 
			
		||||
                stream.copyAndClose(TeeOutputStream(external, updateApk.outputStream()))
 | 
			
		||||
 | 
			
		||||
                // Also upgrade stub
 | 
			
		||||
                notifyUpdate(subject.notifyId) {
 | 
			
		||||
                    it.setProgress(0, 0, true)
 | 
			
		||||
                        .setContentTitle(context.getString(R.string.hide_app_title))
 | 
			
		||||
                        .setContentText("")
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Extract stub
 | 
			
		||||
                val apk = context.cachedFile("stub.apk")
 | 
			
		||||
                ZipFile.Builder().setFile(updateApk).get().use { zf ->
 | 
			
		||||
                    apk.delete()
 | 
			
		||||
                    zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Patch and install
 | 
			
		||||
                subject.intent = AppMigration.upgradeStub(context, apk)
 | 
			
		||||
                    ?: throw IOException("HideAPK patch error")
 | 
			
		||||
                apk.delete()
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
                // If any error occurred, do not let stub load the new APK
 | 
			
		||||
                updateApk.delete()
 | 
			
		||||
                throw e
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            val session = APKInstall.startSession(context)
 | 
			
		||||
            stream.copyAndClose(TeeOutputStream(external, session.openStream(context)))
 | 
			
		||||
            subject.intent = session.waitIntent()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun handleModule(src: InputStream, file: Uri) {
 | 
			
		||||
        val tmp = context.cachedFile("module.zip")
 | 
			
		||||
        try {
 | 
			
		||||
            // First download the entire zip into cache so we can process it
 | 
			
		||||
            src.writeTo(tmp)
 | 
			
		||||
 | 
			
		||||
            val input = ZipFile.Builder().setFile(tmp).get()
 | 
			
		||||
            val output = ZipArchiveOutputStream(file.outputStream())
 | 
			
		||||
            withInOut(input, output) { zin, zout ->
 | 
			
		||||
                zout.putArchiveEntry(ZipArchiveEntry("META-INF/"))
 | 
			
		||||
                zout.closeArchiveEntry()
 | 
			
		||||
                zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/"))
 | 
			
		||||
                zout.closeArchiveEntry()
 | 
			
		||||
                zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/"))
 | 
			
		||||
                zout.closeArchiveEntry()
 | 
			
		||||
                zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/"))
 | 
			
		||||
                zout.closeArchiveEntry()
 | 
			
		||||
 | 
			
		||||
                zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/update-binary"))
 | 
			
		||||
                context.assets.open("module_installer.sh").use { it.copyAll(zout) }
 | 
			
		||||
                zout.closeArchiveEntry()
 | 
			
		||||
 | 
			
		||||
                zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/updater-script"))
 | 
			
		||||
                zout.write("#MAGISK\n".toByteArray())
 | 
			
		||||
                zout.closeArchiveEntry()
 | 
			
		||||
 | 
			
		||||
                // Then simply copy all entries to output
 | 
			
		||||
                zin.copyRawEntries(zout) { entry -> !entry.name.startsWith("META-INF") }
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            tmp.delete()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private class TeeOutputStream(
 | 
			
		||||
        private val o1: OutputStream,
 | 
			
		||||
        private val o2: OutputStream
 | 
			
		||||
    ) : OutputStream() {
 | 
			
		||||
        override fun write(b: Int) {
 | 
			
		||||
            o1.write(b)
 | 
			
		||||
            o2.write(b)
 | 
			
		||||
        }
 | 
			
		||||
        override fun write(b: ByteArray?, off: Int, len: Int) {
 | 
			
		||||
            o1.write(b, off, len)
 | 
			
		||||
            o2.write(b, off, len)
 | 
			
		||||
        }
 | 
			
		||||
        override fun close() {
 | 
			
		||||
            o1.close()
 | 
			
		||||
            o2.close()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user