mirror of
https://github.com/topjohnwu/Magisk
synced 2025-11-14 08:47:34 +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:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
java-version: "17"
|
java-version: "21"
|
||||||
|
|
||||||
- name: Set up Python 3
|
- name: Set up Python 3
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
|
|||||||
27
.github/workflows/build.yml
vendored
27
.github/workflows/build.yml
vendored
@@ -1,15 +1,6 @@
|
|||||||
name: Magisk Build
|
name: Magisk Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
paths:
|
|
||||||
- "app/**"
|
|
||||||
- "native/**"
|
|
||||||
- "buildSrc/**"
|
|
||||||
- "build.py"
|
|
||||||
- "gradle.properties"
|
|
||||||
- ".github/workflows/build.yml"
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -17,7 +8,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build Magisk artifacts
|
name: Build Magisk artifacts
|
||||||
runs-on: macos-14
|
runs-on: macos-15
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
@@ -60,7 +51,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest, ubuntu-latest]
|
os: [windows-2025, ubuntu-24.04]
|
||||||
steps:
|
steps:
|
||||||
- name: Check out
|
- name: Check out
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -78,16 +69,18 @@ jobs:
|
|||||||
|
|
||||||
avd-test:
|
avd-test:
|
||||||
name: Test API ${{ matrix.version }} (x86_64)
|
name: Test API ${{ matrix.version }} (x86_64)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
needs: build
|
needs: build
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
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: [""]
|
type: [""]
|
||||||
include:
|
include:
|
||||||
- version: 35
|
- version: "Baklava"
|
||||||
type: "google_apis"
|
type: "google_apis"
|
||||||
|
- version: "Baklava"
|
||||||
|
type: "google_apis_ps16k"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out
|
- name: Check out
|
||||||
@@ -122,7 +115,7 @@ jobs:
|
|||||||
|
|
||||||
avd-test-32:
|
avd-test-32:
|
||||||
name: Test API ${{ matrix.version }} (x86)
|
name: Test API ${{ matrix.version }} (x86)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
needs: build
|
needs: build
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -161,7 +154,7 @@ jobs:
|
|||||||
kernel.log
|
kernel.log
|
||||||
logcat.log
|
logcat.log
|
||||||
|
|
||||||
cf_test:
|
cf-test:
|
||||||
name: Test ${{ matrix.device }}
|
name: Test ${{ matrix.device }}
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs: build
|
needs: build
|
||||||
@@ -173,8 +166,6 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- branch: "aosp-main"
|
- branch: "aosp-main"
|
||||||
device: "aosp_cf_x86_64_phone"
|
device: "aosp_cf_x86_64_phone"
|
||||||
- branch: "aosp-main-throttled"
|
|
||||||
device: "aosp_cf_x86_64_phone_pgagnostic"
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out
|
- name: Check out
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,6 +14,7 @@ native/out
|
|||||||
*.iml
|
*.iml
|
||||||
.gradle
|
.gradle
|
||||||
.idea
|
.idea
|
||||||
|
.kotlin
|
||||||
/local.properties
|
/local.properties
|
||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ Some highlight features:
|
|||||||
|
|
||||||
Click the icon below to download Magisk apk.
|
Click the icon below to download Magisk apk.
|
||||||
|
|
||||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
|
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
|
||||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
|
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
|
||||||
[](https://github.com/topjohnwu/Magisk/releases/tag/canary-27007)
|
[](https://github.com/topjohnwu/Magisk/releases/tag/canary-28102)
|
||||||
|
|
||||||
## Useful Links
|
## Useful Links
|
||||||
|
|
||||||
|
|||||||
@@ -6,46 +6,37 @@ plugins {
|
|||||||
id("androidx.navigation.safeargs.kotlin")
|
id("androidx.navigation.safeargs.kotlin")
|
||||||
}
|
}
|
||||||
|
|
||||||
setupAppCommon()
|
setupMainApk()
|
||||||
|
|
||||||
kapt {
|
kapt {
|
||||||
correctErrorTypes = true
|
correctErrorTypes = true
|
||||||
useBuildCache = true
|
useBuildCache = true
|
||||||
mapDiagnosticLocations = true
|
mapDiagnosticLocations = true
|
||||||
javacOptions {
|
javacOptions {
|
||||||
option("-Xmaxerrs", 1000)
|
option("-Xmaxerrs", "1000")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
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 {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
isShrinkResources = true
|
isShrinkResources = true
|
||||||
proguardFiles("proguard-rules.pro")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
|
||||||
dataBinding = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":app:core"))
|
implementation(project(":app:core"))
|
||||||
|
coreLibraryDesugaring(libs.jdk.libs)
|
||||||
|
|
||||||
implementation(libs.indeterminate.checkbox)
|
implementation(libs.indeterminate.checkbox)
|
||||||
implementation(libs.rikka.layoutinflater)
|
implementation(libs.rikka.layoutinflater)
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
package com.topjohnwu.magisk.dialog
|
package com.topjohnwu.magisk.dialog
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.os.postDelayed
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.topjohnwu.magisk.core.BuildConfig
|
import com.topjohnwu.magisk.core.BuildConfig
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.R
|
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.core.tasks.MagiskInstaller
|
||||||
import com.topjohnwu.magisk.events.DialogBuilder
|
import com.topjohnwu.magisk.events.DialogBuilder
|
||||||
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
||||||
import com.topjohnwu.magisk.view.MagiskDialog
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class EnvFixDialog(private val vm: HomeViewModel, private val code: Int) : DialogBuilder {
|
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)
|
setCancelable(false)
|
||||||
}
|
}
|
||||||
dialog.activity.lifecycleScope.launch {
|
dialog.activity.lifecycleScope.launch {
|
||||||
MagiskInstaller.FixEnv {
|
MagiskInstaller.FixEnv().exec { success ->
|
||||||
dialog.dismiss()
|
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
|
package com.topjohnwu.magisk.dialog
|
||||||
|
|
||||||
import android.app.ProgressDialog
|
import android.app.ProgressDialog
|
||||||
import android.content.Context
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.topjohnwu.magisk.arch.NavigationActivity
|
import com.topjohnwu.magisk.arch.NavigationActivity
|
||||||
|
import com.topjohnwu.magisk.arch.UIActivity
|
||||||
import com.topjohnwu.magisk.core.R
|
import com.topjohnwu.magisk.core.R
|
||||||
import com.topjohnwu.magisk.core.ktx.toast
|
import com.topjohnwu.magisk.core.ktx.toast
|
||||||
|
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||||
import com.topjohnwu.magisk.events.DialogBuilder
|
import com.topjohnwu.magisk.events.DialogBuilder
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||||
import com.topjohnwu.magisk.view.MagiskDialog
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
import com.topjohnwu.superuser.Shell
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class UninstallDialog : DialogBuilder {
|
class UninstallDialog : DialogBuilder {
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@ class UninstallDialog : DialogBuilder {
|
|||||||
setMessage(R.string.uninstall_magisk_msg)
|
setMessage(R.string.uninstall_magisk_msg)
|
||||||
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||||
text = R.string.restore_img
|
text = R.string.restore_img
|
||||||
onClick { restore(dialog.context) }
|
onClick { restore(dialog.activity) }
|
||||||
}
|
}
|
||||||
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||||
text = R.string.complete_uninstall
|
text = R.string.complete_uninstall
|
||||||
@@ -29,18 +31,20 @@ class UninstallDialog : DialogBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
private fun restore(context: Context) {
|
private fun restore(activity: UIActivity<*>) {
|
||||||
val dialog = ProgressDialog(context).apply {
|
val dialog = ProgressDialog(activity).apply {
|
||||||
setMessage(context.getString(R.string.restore_img_msg))
|
setMessage(activity.getString(R.string.restore_img_msg))
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
|
||||||
Shell.cmd("restore_imgs").submit { result ->
|
activity.lifecycleScope.launch {
|
||||||
|
MagiskInstaller.Restore().exec { success ->
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
if (result.isSuccess) {
|
if (success) {
|
||||||
context.toast(R.string.restore_done, Toast.LENGTH_SHORT)
|
activity.toast(R.string.restore_done, Toast.LENGTH_SHORT)
|
||||||
} else {
|
} else {
|
||||||
context.toast(R.string.restore_fail, Toast.LENGTH_LONG)
|
activity.toast(R.string.restore_fail, Toast.LENGTH_LONG)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>(), MenuProvider {
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
defaultOrientation = activity?.requestedOrientation ?: -1
|
defaultOrientation = activity?.requestedOrientation ?: -1
|
||||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
viewModel.startFlashing()
|
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
|
override val layoutRes = R.layout.item_module_md2
|
||||||
|
|
||||||
val showNotice: Boolean
|
val showNotice: Boolean
|
||||||
|
val showAction: Boolean
|
||||||
val noticeText: TextHolder
|
val noticeText: TextHolder
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -35,6 +36,7 @@ class LocalModuleRvItem(
|
|||||||
showNotice = zygiskUnloaded ||
|
showNotice = zygiskUnloaded ||
|
||||||
(Info.isZygiskEnabled && isRiru) ||
|
(Info.isZygiskEnabled && isRiru) ||
|
||||||
(!Info.isZygiskEnabled && isZygisk)
|
(!Info.isZygiskEnabled && isZygisk)
|
||||||
|
showAction = item.hasAction && !showNotice
|
||||||
noticeText =
|
noticeText =
|
||||||
when {
|
when {
|
||||||
zygiskUnloaded -> CoreR.string.zygisk_module_unloaded.asText()
|
zygiskUnloaded -> CoreR.string.zygisk_module_unloaded.asText()
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import android.net.Uri
|
|||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.MainDirections
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
|
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.base.ContentResultCallback
|
import com.topjohnwu.magisk.core.base.ContentResultCallback
|
||||||
import com.topjohnwu.magisk.core.model.module.LocalModule
|
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 {
|
companion object {
|
||||||
private val uri = MutableLiveData<Uri?>()
|
private val uri = MutableLiveData<Uri?>()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import com.topjohnwu.magisk.BR
|
|||||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||||
import com.topjohnwu.magisk.core.AppContext
|
import com.topjohnwu.magisk.core.AppContext
|
||||||
import com.topjohnwu.magisk.core.BuildConfig
|
import com.topjohnwu.magisk.core.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.core.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.R
|
import com.topjohnwu.magisk.core.R
|
||||||
@@ -92,7 +93,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
|||||||
DownloadPath -> withExternalRW(doAction)
|
DownloadPath -> withExternalRW(doAction)
|
||||||
UpdateChecker -> withPostNotificationPermission(doAction)
|
UpdateChecker -> withPostNotificationPermission(doAction)
|
||||||
Authentication -> AuthEvent(doAction).publish()
|
Authentication -> AuthEvent(doAction).publish()
|
||||||
Hide, Restore -> withInstallPermission(doAction)
|
AutomaticResponse -> if (Config.suAuth) AuthEvent(doAction).publish() else doAction()
|
||||||
else -> doAction()
|
else -> doAction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ class SuperuserViewModel(
|
|||||||
this@SuperuserViewModel, policy,
|
this@SuperuserViewModel, policy,
|
||||||
info.packageName,
|
info.packageName,
|
||||||
info.sharedUserId != null,
|
info.sharedUserId != null,
|
||||||
info.applicationInfo.loadIcon(pm),
|
info.applicationInfo?.loadIcon(pm) ?: pm.defaultActivityIcon,
|
||||||
info.applicationInfo.getLabel(pm)
|
info.applicationInfo?.getLabel(pm) ?: info.packageName
|
||||||
)
|
)
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class SuRequestViewModel(
|
|||||||
// shared UID. We have no way to know where this request comes from.
|
// shared UID. We have no way to know where this request comes from.
|
||||||
icon = pm.defaultActivityIcon
|
icon = pm.defaultActivityIcon
|
||||||
title = "[SharedUID] ${info.sharedUserId}"
|
title = "[SharedUID] ${info.sharedUserId}"
|
||||||
packageName = info.sharedUserId
|
packageName = info.sharedUserId.toString()
|
||||||
} else {
|
} else {
|
||||||
val prefix = if (info.sharedUserId == null) "" else "[SharedUID] "
|
val prefix = if (info.sharedUserId == null) "" else "[SharedUID] "
|
||||||
icon = app.loadIcon(pm)
|
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"
|
android:textColor="?colorError"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/module_remove"
|
app:layout_constraintBottom_toBottomOf="@+id/module_remove"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/bottom_bar_barrier"
|
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"
|
app:layout_constraintTop_toTopOf="@+id/module_remove"
|
||||||
tools:lines="2"
|
tools:lines="2"
|
||||||
tools:text="@tools:sample/lorem/random"
|
tools:text="@tools:sample/lorem/random"
|
||||||
tools:visibility="visible" />
|
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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|||||||
@@ -53,6 +53,21 @@
|
|||||||
|
|
||||||
</fragment>
|
</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
|
<fragment
|
||||||
android:id="@+id/installFragment"
|
android:id="@+id/installFragment"
|
||||||
android:name="com.topjohnwu.magisk.ui.install.InstallFragment"
|
android:name="com.topjohnwu.magisk.ui.install.InstallFragment"
|
||||||
@@ -152,4 +167,12 @@
|
|||||||
app:popEnterAnim="@anim/fragment_enter_pop"
|
app:popEnterAnim="@anim/fragment_enter_pop"
|
||||||
app:popExitAnim="@anim/fragment_exit_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>
|
</navigation>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ android {
|
|||||||
buildConfigField("int", "APP_VERSION_CODE", "${Config.versionCode}")
|
buildConfigField("int", "APP_VERSION_CODE", "${Config.versionCode}")
|
||||||
buildConfigField("String", "APP_VERSION_NAME", "\"${Config.version}\"")
|
buildConfigField("String", "APP_VERSION_NAME", "\"${Config.version}\"")
|
||||||
buildConfigField("int", "STUB_VERSION", Config.stubVersion)
|
buildConfigField("int", "STUB_VERSION", Config.stubVersion)
|
||||||
|
consumerProguardFile("proguard-rules.pro")
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
@@ -59,5 +60,10 @@ dependencies {
|
|||||||
implementation(libs.activity)
|
implementation(libs.activity)
|
||||||
implementation(libs.collection.ktx)
|
implementation(libs.collection.ktx)
|
||||||
implementation(libs.profileinstaller)
|
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;
|
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
|
# Strip Timber verbose and debug logging
|
||||||
-assumenosideeffects class timber.log.Timber$Tree {
|
-assumenosideeffects class timber.log.Timber$Tree {
|
||||||
public void v(**);
|
public void v(**);
|
||||||
public void d(**);
|
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
|
# With R8 full mode generic signatures are stripped for classes that are not
|
||||||
# kept. Suspend functions are wrapped in continuations where the type argument
|
# kept. Suspend functions are wrapped in continuations where the type argument
|
||||||
# is used.
|
# is used.
|
||||||
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
|
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
|
||||||
|
|
||||||
|
|
||||||
# Excessive obfuscation
|
# Excessive obfuscation
|
||||||
-repackageclasses 'a'
|
-flattenpackagehierarchy
|
||||||
-allowaccessmodification
|
-allowaccessmodification
|
||||||
|
|
||||||
-obfuscationdictionary ../dict.txt
|
-dontwarn org.junit.**
|
||||||
-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
|
|
||||||
@@ -16,7 +16,6 @@ import com.topjohnwu.magisk.StubApk
|
|||||||
import com.topjohnwu.magisk.core.base.UntrackedActivity
|
import com.topjohnwu.magisk.core.base.UntrackedActivity
|
||||||
import com.topjohnwu.magisk.core.utils.LocaleSetting
|
import com.topjohnwu.magisk.core.utils.LocaleSetting
|
||||||
import com.topjohnwu.magisk.core.utils.NetworkObserver
|
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.RootUtils
|
||||||
import com.topjohnwu.magisk.core.utils.ShellInit
|
import com.topjohnwu.magisk.core.utils.ShellInit
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
@@ -40,6 +39,7 @@ object AppContext : ContextWrapper(null),
|
|||||||
|
|
||||||
private var ref = WeakReference<Activity>(null)
|
private var ref = WeakReference<Activity>(null)
|
||||||
private lateinit var application: Application
|
private lateinit var application: Application
|
||||||
|
private lateinit var networkObserver: NetworkObserver
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Always log full stack trace with Timber
|
// Always log full stack trace with Timber
|
||||||
@@ -56,6 +56,10 @@ object AppContext : ContextWrapper(null),
|
|||||||
LocaleSetting.instance.updateResource(resources)
|
LocaleSetting.instance.updateResource(resources)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onActivityStarted(activity: Activity) {
|
||||||
|
networkObserver.postCurrentState()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onActivityResumed(activity: Activity) {
|
override fun onActivityResumed(activity: Activity) {
|
||||||
if (activity is UntrackedActivity) return
|
if (activity is UntrackedActivity) return
|
||||||
ref = WeakReference(activity)
|
ref = WeakReference(activity)
|
||||||
@@ -102,8 +106,7 @@ object AppContext : ContextWrapper(null),
|
|||||||
val lm = getSystemService(LocaleManager::class.java)
|
val lm = getSystemService(LocaleManager::class.java)
|
||||||
lm.overrideLocaleConfig = LocaleSetting.localeConfig
|
lm.overrideLocaleConfig = LocaleSetting.localeConfig
|
||||||
}
|
}
|
||||||
ProcessLifecycle.init(this)
|
networkObserver = NetworkObserver.init(this)
|
||||||
NetworkObserver.init(this)
|
|
||||||
if (!BuildConfig.DEBUG && !isRunningAsStub) {
|
if (!BuildConfig.DEBUG && !isRunningAsStub) {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
ProfileInstaller.writeProfile(this@AppContext)
|
ProfileInstaller.writeProfile(this@AppContext)
|
||||||
@@ -120,7 +123,6 @@ object AppContext : ContextWrapper(null),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
|
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
|
||||||
override fun onActivityStarted(activity: Activity) {}
|
|
||||||
override fun onActivityStopped(activity: Activity) {}
|
override fun onActivityStopped(activity: Activity) {}
|
||||||
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
|
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
|
||||||
override fun onActivityDestroyed(activity: Activity) {}
|
override fun onActivityDestroyed(activity: Activity) {}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ object Config : PreferenceConfig, DBConfig {
|
|||||||
const val SU_AUTO_ALLOW = 2
|
const val SU_AUTO_ALLOW = 2
|
||||||
|
|
||||||
// su timeout
|
// 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 =
|
private val defaultChannel =
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.core.content.getSystemService
|
|||||||
import com.topjohnwu.magisk.core.base.BaseJobService
|
import com.topjohnwu.magisk.core.base.BaseJobService
|
||||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
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.core.download.Subject
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -25,7 +26,7 @@ class JobService : BaseJobService() {
|
|||||||
@TargetApi(value = 34)
|
@TargetApi(value = 34)
|
||||||
inner class Session(
|
inner class Session(
|
||||||
private var params: JobParameters
|
private var params: JobParameters
|
||||||
) : DownloadEngine.Session {
|
) : DownloadSession {
|
||||||
|
|
||||||
override val context get() = this@JobService
|
override val context get() = this@JobService
|
||||||
val engine = DownloadEngine(this)
|
val engine = DownloadEngine(this)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.topjohnwu.magisk.core
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.topjohnwu.magisk.core.base.BaseProvider
|
import com.topjohnwu.magisk.core.base.BaseProvider
|
||||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||||
import com.topjohnwu.magisk.core.su.TestHandler
|
|
||||||
|
|
||||||
class Provider : BaseProvider() {
|
class Provider : BaseProvider() {
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ class Provider : BaseProvider() {
|
|||||||
SuCallbackHandler.run(context!!, method, extras)
|
SuCallbackHandler.run(context!!, method, extras)
|
||||||
Bundle.EMPTY
|
Bundle.EMPTY
|
||||||
}
|
}
|
||||||
else -> TestHandler.run(method)
|
else -> Bundle.EMPTY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import androidx.core.app.ServiceCompat
|
|||||||
import androidx.core.content.IntentCompat
|
import androidx.core.content.IntentCompat
|
||||||
import com.topjohnwu.magisk.core.base.BaseService
|
import com.topjohnwu.magisk.core.base.BaseService
|
||||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
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.core.download.Subject
|
||||||
|
|
||||||
class Service : BaseService(), DownloadEngine.Session {
|
class Service : BaseService(), DownloadSession {
|
||||||
|
|
||||||
private var mEngine: DownloadEngine? = null
|
private var mEngine: DownloadEngine? = null
|
||||||
override val context get() = this
|
override val context get() = this
|
||||||
|
|||||||
@@ -7,9 +7,13 @@ import kotlinx.coroutines.withContext
|
|||||||
|
|
||||||
open class MagiskDB {
|
open class MagiskDB {
|
||||||
|
|
||||||
suspend fun <R> exec(
|
class Literal(
|
||||||
|
val str: String
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend inline fun <R> exec(
|
||||||
query: String,
|
query: String,
|
||||||
mapper: suspend (Map<String, String>) -> R
|
crossinline mapper: (Map<String, String>) -> R
|
||||||
): List<R> {
|
): List<R> {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
val out = Shell.cmd("magisk --sqlite '$query'").await().out
|
val out = Shell.cmd("magisk --sqlite '$query'").await().out
|
||||||
@@ -18,13 +22,15 @@ open class MagiskDB {
|
|||||||
.map { it.split("=", limit = 2) }
|
.map { it.split("=", limit = 2) }
|
||||||
.filter { it.size == 2 }
|
.filter { it.size == 2 }
|
||||||
.associate { it[0] to it[1] }
|
.associate { it[0] to it[1] }
|
||||||
.let { mapper(it) }
|
.let(mapper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend inline fun exec(query: String) {
|
suspend fun exec(query: String) {
|
||||||
exec(query) {}
|
withContext(Dispatchers.IO) {
|
||||||
|
Shell.cmd("magisk --sqlite '$query'").await()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Map<String, Any>.toQuery(): String {
|
fun Map<String, Any>.toQuery(): String {
|
||||||
@@ -33,6 +39,7 @@ open class MagiskDB {
|
|||||||
when (it) {
|
when (it) {
|
||||||
is Boolean -> if (it) "1" else "0"
|
is Boolean -> if (it) "1" else "0"
|
||||||
is Number -> it.toString()
|
is Number -> it.toString()
|
||||||
|
is Literal -> it.str
|
||||||
else -> "\"$it\""
|
else -> "\"$it\""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,24 +3,24 @@ package com.topjohnwu.magisk.core.data.magiskdb
|
|||||||
import com.topjohnwu.magisk.core.AppContext
|
import com.topjohnwu.magisk.core.AppContext
|
||||||
import com.topjohnwu.magisk.core.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
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() {
|
class PolicyDao : MagiskDB() {
|
||||||
|
|
||||||
suspend fun deleteOutdated() {
|
suspend fun deleteOutdated() {
|
||||||
val nowSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
|
|
||||||
val query = "DELETE FROM ${Table.POLICY} WHERE " +
|
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)
|
exec(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun delete(uid: Int) {
|
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)
|
exec(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetch(uid: Int): SuPolicy? {
|
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()
|
return exec(query, ::toPolicy).firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ class PolicyDao : MagiskDB() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetchAll(): List<SuPolicy> {
|
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()
|
return exec(query, ::toPolicy).filterNotNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +43,15 @@ class PolicyDao : MagiskDB() {
|
|||||||
val uid = map["uid"]?.toInt() ?: return null
|
val uid = map["uid"]?.toInt() ?: return null
|
||||||
val policy = SuPolicy(uid)
|
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["policy"]?.toInt()?.let { policy.policy = it }
|
||||||
map["until"]?.toLong()?.let { policy.until = it }
|
|
||||||
map["logging"]?.toInt()?.let { policy.logging = it != 0 }
|
map["logging"]?.toInt()?.let { policy.logging = it != 0 }
|
||||||
map["notification"]?.toInt()?.let { policy.notification = it != 0 }
|
map["notification"]?.toInt()?.let { policy.notification = it != 0 }
|
||||||
return policy
|
return policy
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.topjohnwu.magisk.core.data.magiskdb
|
|||||||
class SettingsDao : MagiskDB() {
|
class SettingsDao : MagiskDB() {
|
||||||
|
|
||||||
suspend fun delete(key: String) {
|
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)
|
exec(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ class SettingsDao : MagiskDB() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetch(key: String, default: Int = -1): Int {
|
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
|
return exec(query) { it["value"]?.toInt() }.firstOrNull() ?: default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.topjohnwu.magisk.core.data.magiskdb
|
|||||||
class StringDao : MagiskDB() {
|
class StringDao : MagiskDB() {
|
||||||
|
|
||||||
suspend fun delete(key: String) {
|
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)
|
exec(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ class StringDao : MagiskDB() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetch(key: String, default: String = ""): String {
|
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
|
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