mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-06-12 21:27:41 +02:00
Merge Magisk install zip into Magisk Manager
Distribute Magisk directly with Magisk Manager APK. The APK will contain all required binaries and scripts for installation and uninstallation. App versions will now align with Magisk releases. Extra effort is spent to make the APK itself also a flashable zip that can be used in custom recoveries, so those still prefer to install Magisk with recoveries will not be affected with this change. As a bonus, this makes the whole installation and uninstallation process 100% offline. The existing Magisk Manager was not really functional without an Internet connection, as the installation process was highly tied to zips hosted on the server. An additional bonus: since all binaries are now shipped as "native libraries" of the APK, we can finally bump the target SDK version higher than 28. The target SDK version was stuck at 28 for a long time because newer SELinux restricts running executables from internal storage. More details can be found here: https://github.com/termux/termux-app/issues/1072 The target SDK bump will be addressed in a future commit. Co-authored with @vvb2060
This commit is contained in:
5
app/.gitignore
vendored
5
app/.gitignore
vendored
@ -6,6 +6,7 @@
|
||||
app/release
|
||||
*.hprof
|
||||
.externalNativeBuild/
|
||||
public.certificate.x509.pem
|
||||
private.key.pk8
|
||||
*.apk
|
||||
src/main/assets
|
||||
src/main/jniLibs
|
||||
src/main/resources
|
||||
|
@ -1,3 +1,4 @@
|
||||
import org.apache.tools.ant.filters.FixCrLfFilter
|
||||
import java.io.PrintStream
|
||||
|
||||
plugins {
|
||||
@ -22,8 +23,8 @@ android {
|
||||
applicationId = "com.topjohnwu.magisk"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
multiDexEnabled = true
|
||||
versionName = Config.appVersion
|
||||
versionCode = Config.appVersionCode
|
||||
versionName = Config.version
|
||||
versionCode = Config.versionCode
|
||||
|
||||
javaCompileOptions.annotationProcessorOptions.arguments(
|
||||
mapOf("room.incremental" to "true")
|
||||
@ -51,13 +52,14 @@ android {
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude("/META-INF/**")
|
||||
exclude("/META-INF/*")
|
||||
exclude("/org/bouncycastle/**")
|
||||
exclude("/kotlin/**")
|
||||
exclude("/kotlinx/**")
|
||||
exclude("/okhttp3/**")
|
||||
exclude("/*.txt")
|
||||
exclude("/*.bin")
|
||||
doNotStrip("**/*.so")
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
@ -65,10 +67,82 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
tasks["preBuild"]?.dependsOn(tasks.register("copyUtils", Copy::class) {
|
||||
from(rootProject.file("scripts/util_functions.sh"))
|
||||
into("src/main/res/raw")
|
||||
})
|
||||
val syncLibs by tasks.registering(Sync::class) {
|
||||
into("src/main/jniLibs")
|
||||
into("armeabi-v7a") {
|
||||
from(rootProject.file("native/out/armeabi-v7a")) {
|
||||
include("busybox", "magiskboot", "magiskinit", "magisk")
|
||||
rename { if (it == "magisk") "libmagisk32.so" else "lib$it.so" }
|
||||
}
|
||||
from(rootProject.file("native/out/arm64-v8a")) {
|
||||
include("magisk")
|
||||
rename { if (it == "magisk") "libmagisk64.so" else "lib$it.so" }
|
||||
}
|
||||
}
|
||||
into("x86") {
|
||||
from(rootProject.file("native/out/x86")) {
|
||||
include("busybox", "magiskboot", "magiskinit", "magisk")
|
||||
rename { if (it == "magisk") "libmagisk32.so" else "lib$it.so" }
|
||||
}
|
||||
from(rootProject.file("native/out/x86_64")) {
|
||||
include("magisk")
|
||||
rename { if (it == "magisk") "libmagisk64.so" else "lib$it.so" }
|
||||
}
|
||||
}
|
||||
doFirst {
|
||||
if (inputs.sourceFiles.files.size != 10)
|
||||
throw StopExecutionException("Build binary files first")
|
||||
}
|
||||
}
|
||||
|
||||
val createStubLibs by tasks.registering {
|
||||
dependsOn(syncLibs)
|
||||
doLast {
|
||||
val arm64 = project.file("src/main/jniLibs/arm64-v8a/libstub.so")
|
||||
arm64.parentFile.mkdirs()
|
||||
arm64.createNewFile()
|
||||
val x64 = project.file("src/main/jniLibs/x86_64/libstub.so")
|
||||
x64.parentFile.mkdirs()
|
||||
x64.createNewFile()
|
||||
}
|
||||
}
|
||||
|
||||
val syncAssets by tasks.registering(Sync::class) {
|
||||
dependsOn(createStubLibs)
|
||||
inputs.property("version", Config.version)
|
||||
inputs.property("versionCode", Config.versionCode)
|
||||
into("src/main/assets")
|
||||
from(rootProject.file("scripts")) {
|
||||
include("util_functions.sh", "boot_patch.sh", "magisk_uninstaller.sh", "addon.d.sh")
|
||||
}
|
||||
into("chromeos") {
|
||||
from(rootProject.file("tools/futility"))
|
||||
from(rootProject.file("tools/keys")) {
|
||||
include("kernel_data_key.vbprivk", "kernel.keyblock")
|
||||
}
|
||||
}
|
||||
filesMatching("**/util_functions.sh") {
|
||||
filter {
|
||||
it.replace("#MAGISK_VERSION_STUB",
|
||||
"MAGISK_VER='${Config.version}'\n" +
|
||||
"MAGISK_VER_CODE=${Config.versionCode}")
|
||||
}
|
||||
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
|
||||
}
|
||||
}
|
||||
|
||||
val syncResources by tasks.registering(Sync::class) {
|
||||
dependsOn(syncAssets)
|
||||
into("src/main/resources/META-INF/com/google/android")
|
||||
from(rootProject.file("scripts/update_binary.sh")) {
|
||||
rename { "update-binary" }
|
||||
}
|
||||
from(rootProject.file("scripts/flash_script.sh")) {
|
||||
rename { "updater-script" }
|
||||
}
|
||||
}
|
||||
|
||||
tasks["preBuild"]?.dependsOn(syncResources)
|
||||
|
||||
android.applicationVariants.all {
|
||||
val keysDir = rootProject.file("tools/keys")
|
||||
@ -178,6 +252,5 @@ dependencies {
|
||||
implementation("androidx.transition:transition:1.3.1")
|
||||
implementation("androidx.multidex:multidex:2.0.1")
|
||||
implementation("androidx.core:core-ktx:1.3.2")
|
||||
implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.0.0")
|
||||
implementation("com.google.android.material:material:1.2.1")
|
||||
}
|
||||
|
@ -13,6 +13,8 @@
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:name="a.e"
|
||||
android:allowBackup="false"
|
||||
android:multiArch="true"
|
||||
android:extractNativeLibs="true"
|
||||
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
|
||||
|
||||
<!-- Splash -->
|
||||
|
@ -19,6 +19,7 @@ import com.topjohnwu.superuser.Shell
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.context.startKoin
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
open class App() : Application() {
|
||||
@ -61,6 +62,12 @@ open class App() : Application() {
|
||||
val wrapped = impl.wrap()
|
||||
super.attachBaseContext(wrapped)
|
||||
|
||||
val info = base.applicationInfo
|
||||
val libDir = runCatching {
|
||||
info.javaClass.getDeclaredField("secondaryNativeLibraryDir").get(info) as String?
|
||||
}.getOrNull() ?: info.nativeLibraryDir
|
||||
Const.NATIVE_LIB_DIR = File(libDir)
|
||||
|
||||
// Normal startup
|
||||
startKoin {
|
||||
androidContext(wrapped)
|
||||
|
@ -1,13 +1,30 @@
|
||||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Process
|
||||
import java.io.File
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
object Const {
|
||||
|
||||
val CPU_ABI: String
|
||||
val CPU_ABI_32: String
|
||||
|
||||
init {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
CPU_ABI = Build.SUPPORTED_ABIS[0]
|
||||
CPU_ABI_32 = Build.SUPPORTED_32_BIT_ABIS[0]
|
||||
} else {
|
||||
CPU_ABI = Build.CPU_ABI
|
||||
CPU_ABI_32 = CPU_ABI
|
||||
}
|
||||
}
|
||||
|
||||
// Paths
|
||||
lateinit var MAGISKTMP: String
|
||||
lateinit var NATIVE_LIB_DIR: File
|
||||
val MAGISK_PATH get() = "$MAGISKTMP/modules"
|
||||
const val TMP_FOLDER_PATH = "/dev/tmp"
|
||||
const val TMPDIR = "/dev/tmp"
|
||||
const val MAGISK_LOG = "/cache/magisk.log"
|
||||
|
||||
// Versions
|
||||
@ -19,11 +36,9 @@ object Const {
|
||||
val USER_ID = Process.myUid() / 100000
|
||||
|
||||
object Version {
|
||||
const val MIN_VERSION = "v19.0"
|
||||
const val MIN_VERCODE = 19000
|
||||
const val MIN_VERSION = "v20.4"
|
||||
const val MIN_VERCODE = 20400
|
||||
|
||||
fun atLeast_20_2() = Info.env.magiskVersionCode >= 20200 || isCanary()
|
||||
fun atLeast_20_4() = Info.env.magiskVersionCode >= 20400 || isCanary()
|
||||
fun atLeast_21_0() = Info.env.magiskVersionCode >= 21000 || isCanary()
|
||||
fun atLeast_21_2() = Info.env.magiskVersionCode >= 21200 || isCanary()
|
||||
fun isCanary() = Info.env.magiskVersionCode % 100 != 0
|
||||
|
@ -1,31 +0,0 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
sealed class Action : Parcelable {
|
||||
|
||||
sealed class Flash : Action() {
|
||||
|
||||
@Parcelize
|
||||
object Primary : Flash()
|
||||
|
||||
@Parcelize
|
||||
object Secondary : Flash()
|
||||
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
object Download : Action()
|
||||
|
||||
@Parcelize
|
||||
object Uninstall : Action()
|
||||
|
||||
@Parcelize
|
||||
object EnvFix : Action()
|
||||
|
||||
@Parcelize
|
||||
data class Patch(val fileUri: Uri) : Action()
|
||||
|
||||
}
|
@ -8,7 +8,6 @@ import androidx.lifecycle.MutableLiveData
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.ForegroundTracker
|
||||
import com.topjohnwu.magisk.core.base.BaseService
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.checkSum
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
@ -69,17 +68,14 @@ abstract class BaseDownloader : BaseService(), KoinComponent {
|
||||
// -- Download logic
|
||||
|
||||
private suspend fun Subject.startDownload() {
|
||||
val skip = this is Subject.Magisk && file.checkSum("MD5", magisk.md5)
|
||||
if (!skip) {
|
||||
val stream = service.fetchFile(url).toProgressStream(this)
|
||||
when (this) {
|
||||
is Subject.Module -> // Download and process on-the-fly
|
||||
stream.toModule(file, service.fetchInstaller().byteStream())
|
||||
else -> {
|
||||
withStreams(stream, file.outputStream()) { it, out -> it.copyTo(out) }
|
||||
if (this is Subject.Manager)
|
||||
handleAPK(this)
|
||||
}
|
||||
val stream = service.fetchFile(url).toProgressStream(this)
|
||||
when (this) {
|
||||
is Subject.Module -> // Download and process on-the-fly
|
||||
stream.toModule(file, service.fetchInstaller().byteStream())
|
||||
else -> {
|
||||
withStreams(stream, file.outputStream()) { it, out -> it.copyTo(out) }
|
||||
if (this is Subject.Manager)
|
||||
handleAPK(this)
|
||||
}
|
||||
}
|
||||
val newId = notifyFinish(this)
|
||||
|
@ -7,11 +7,10 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.net.toFile
|
||||
import com.topjohnwu.magisk.core.download.Action.*
|
||||
import com.topjohnwu.magisk.core.download.Action.Flash.Secondary
|
||||
import com.topjohnwu.magisk.core.download.Subject.*
|
||||
import com.topjohnwu.magisk.core.download.Action.Flash
|
||||
import com.topjohnwu.magisk.core.download.Subject.Manager
|
||||
import com.topjohnwu.magisk.core.download.Subject.Module
|
||||
import com.topjohnwu.magisk.core.intent
|
||||
import com.topjohnwu.magisk.core.tasks.EnvFixTask
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import kotlin.random.Random.Default.nextInt
|
||||
@ -22,25 +21,12 @@ open class DownloadService : BaseDownloader() {
|
||||
private val context get() = this
|
||||
|
||||
override suspend fun onFinish(subject: Subject, id: Int) = when (subject) {
|
||||
is Magisk -> subject.onFinish(id)
|
||||
is Module -> subject.onFinish(id)
|
||||
is Manager -> subject.onFinish(id)
|
||||
}
|
||||
|
||||
private suspend fun Magisk.onFinish(id: Int) = when (val action = action) {
|
||||
Uninstall -> FlashFragment.uninstall(file, id)
|
||||
EnvFix -> {
|
||||
remove(id)
|
||||
EnvFixTask(file).exec()
|
||||
Unit
|
||||
}
|
||||
is Patch -> FlashFragment.patch(file, action.fileUri, id)
|
||||
is Flash -> FlashFragment.flash(file, action is Secondary, id)
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
private fun Module.onFinish(id: Int) = when (action) {
|
||||
is Flash -> FlashFragment.install(file, id)
|
||||
Flash -> FlashFragment.install(file, id)
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
@ -53,22 +39,13 @@ open class DownloadService : BaseDownloader() {
|
||||
|
||||
override fun Notification.Builder.setIntent(subject: Subject)
|
||||
= when (subject) {
|
||||
is Magisk -> setIntent(subject)
|
||||
is Module -> setIntent(subject)
|
||||
is Manager -> setIntent(subject)
|
||||
}
|
||||
|
||||
private fun Notification.Builder.setIntent(subject: Magisk)
|
||||
= when (val action = subject.action) {
|
||||
Uninstall -> setContentIntent(FlashFragment.uninstallIntent(context, subject.file))
|
||||
is Flash -> setContentIntent(FlashFragment.flashIntent(context, subject.file, action is Secondary))
|
||||
is Patch -> setContentIntent(FlashFragment.patchIntent(context, subject.file, action.fileUri))
|
||||
else -> setContentIntent(Intent())
|
||||
}
|
||||
|
||||
private fun Notification.Builder.setIntent(subject: Module)
|
||||
= when (subject.action) {
|
||||
is Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file))
|
||||
Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file))
|
||||
else -> setContentIntent(Intent())
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import androidx.core.net.toUri
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.model.MagiskJson
|
||||
import com.topjohnwu.magisk.core.model.ManagerJson
|
||||
import com.topjohnwu.magisk.core.model.StubJson
|
||||
import com.topjohnwu.magisk.core.model.module.OnlineModule
|
||||
@ -53,57 +52,12 @@ sealed class Subject : Parcelable {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract class Magisk : Subject() {
|
||||
|
||||
val magisk: MagiskJson = Info.remote.magisk
|
||||
|
||||
@Parcelize
|
||||
private class Internal(
|
||||
override val action: Action
|
||||
) : Magisk() {
|
||||
override val url: String get() = magisk.link
|
||||
override val title: String get() = "Magisk-${magisk.version}(${magisk.versionCode})"
|
||||
|
||||
@IgnoredOnParcel
|
||||
override val file by lazy {
|
||||
cachedFile("magisk.zip")
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
private class Uninstall : Magisk() {
|
||||
override val action get() = Action.Uninstall
|
||||
override val url: String get() = Info.remote.uninstaller.link
|
||||
override val title: String get() = "uninstall.zip"
|
||||
|
||||
@IgnoredOnParcel
|
||||
override val file by lazy {
|
||||
cachedFile(title)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
private class Download : Magisk() {
|
||||
override val action get() = Action.Download
|
||||
override val url: String get() = magisk.link
|
||||
override val title: String get() = "Magisk-${magisk.version}(${magisk.versionCode}).zip"
|
||||
|
||||
@IgnoredOnParcel
|
||||
override val file by lazy {
|
||||
MediaStoreUtils.getFile(title).uri
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
operator fun invoke(config: Action) = when (config) {
|
||||
Action.Download -> Download()
|
||||
Action.Uninstall -> Uninstall()
|
||||
Action.EnvFix, is Action.Flash, is Action.Patch -> Internal(config)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sealed class Action : Parcelable {
|
||||
@Parcelize
|
||||
object Flash : Action()
|
||||
|
||||
@Parcelize
|
||||
object Download : Action()
|
||||
}
|
||||
|
@ -2,14 +2,13 @@ package com.topjohnwu.magisk.core.tasks
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.os.postDelayed
|
||||
import androidx.core.net.toFile
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
|
||||
import com.topjohnwu.magisk.core.utils.unzip
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
|
||||
import com.topjohnwu.magisk.core.utils.unzip
|
||||
import com.topjohnwu.magisk.ktx.writeTo
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.KoinComponent
|
||||
@ -25,61 +24,50 @@ open class FlashZip(
|
||||
private val logs: MutableList<String>
|
||||
): KoinComponent {
|
||||
|
||||
val context: Context by inject()
|
||||
private val installFolder = File(context.cacheDir, "flash").apply {
|
||||
if (!exists()) mkdirs()
|
||||
}
|
||||
private val tmpFile: File = File(installFolder, "install.zip")
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun unzipAndCheck(): Boolean {
|
||||
val parentFile = tmpFile.parentFile ?: return false
|
||||
tmpFile.unzip(parentFile, "META-INF/com/google/android", true)
|
||||
|
||||
val updaterScript = File(parentFile, "updater-script")
|
||||
return Shell
|
||||
.su("grep -q '#MAGISK' $updaterScript")
|
||||
.exec()
|
||||
.isSuccess
|
||||
}
|
||||
private val context: Context by inject()
|
||||
private val installDir = File(context.cacheDir, "flash")
|
||||
private lateinit var zipFile: File
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun flash(): Boolean {
|
||||
console.add("- Copying zip to temp directory")
|
||||
installDir.deleteRecursively()
|
||||
installDir.mkdirs()
|
||||
|
||||
runCatching {
|
||||
mUri.inputStream().writeTo(tmpFile)
|
||||
}.getOrElse {
|
||||
when (it) {
|
||||
is FileNotFoundException -> console.add("! Invalid Uri")
|
||||
is IOException -> console.add("! Cannot copy to cache")
|
||||
zipFile = if (mUri.scheme == "file") {
|
||||
mUri.toFile()
|
||||
} else {
|
||||
File(installDir, "install.zip").also {
|
||||
console.add("- Copying zip to temp directory")
|
||||
try {
|
||||
mUri.inputStream().writeTo(it)
|
||||
} catch (e: IOException) {
|
||||
when (e) {
|
||||
is FileNotFoundException -> console.add("! Invalid Uri")
|
||||
else -> console.add("! Cannot copy to cache")
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
throw it
|
||||
}
|
||||
|
||||
val isMagiskModule = runCatching {
|
||||
unzipAndCheck()
|
||||
val isValid = runCatching {
|
||||
zipFile.unzip(installDir, "META-INF/com/google/android", true)
|
||||
val script = File(installDir, "updater-script")
|
||||
script.readText().contains("#MAGISK")
|
||||
}.getOrElse {
|
||||
console.add("! Unzip error")
|
||||
throw it
|
||||
}
|
||||
|
||||
if (!isMagiskModule) {
|
||||
console.add("! This zip is not a Magisk Module!")
|
||||
if (!isValid) {
|
||||
console.add("! This zip is not a Magisk module!")
|
||||
return false
|
||||
}
|
||||
|
||||
console.add("- Installing ${mUri.displayName}")
|
||||
|
||||
val parentFile = tmpFile.parent ?: return false
|
||||
|
||||
return Shell
|
||||
.su(
|
||||
"cd $parentFile",
|
||||
"BOOTMODE=true sh update-binary dummy 1 $tmpFile"
|
||||
)
|
||||
.to(console, logs)
|
||||
.exec().isSuccess
|
||||
return Shell.su("sh $installDir/update-binary dummy 1 $zipFile")
|
||||
.to(console, logs).exec().isSuccess
|
||||
}
|
||||
|
||||
open suspend fun exec() = withContext(Dispatchers.IO) {
|
||||
@ -94,25 +82,7 @@ open class FlashZip(
|
||||
Timber.e(e)
|
||||
false
|
||||
} finally {
|
||||
Shell.su("cd /", "rm -rf ${tmpFile.parent} ${Const.TMP_FOLDER_PATH}").submit()
|
||||
Shell.su("cd /", "rm -rf $installDir ${Const.TMPDIR}").submit()
|
||||
}
|
||||
}
|
||||
|
||||
class Uninstall(
|
||||
uri: Uri,
|
||||
console: MutableList<String>,
|
||||
log: MutableList<String>
|
||||
) : FlashZip(uri, console, log) {
|
||||
|
||||
override suspend fun exec(): Boolean {
|
||||
val success = super.exec()
|
||||
if (success) {
|
||||
UiThreadHandler.handler.postDelayed(3000) {
|
||||
Shell.su("pm uninstall " + context.packageName).exec()
|
||||
}
|
||||
}
|
||||
return success
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -91,8 +91,7 @@ object HideAPK {
|
||||
}
|
||||
|
||||
private suspend fun patchAndHide(context: Context, label: String): Boolean {
|
||||
val dlStub = !isRunningAsStub && SDK_INT >= 28 && Const.Version.atLeast_20_2()
|
||||
val src = if (dlStub) {
|
||||
val src = if (!isRunningAsStub && SDK_INT >= 28) {
|
||||
val stub = File(context.cacheDir, "stub.apk")
|
||||
try {
|
||||
svc.fetchFile(Info.remote.stub.link).byteStream().use {
|
||||
|
@ -1,25 +1,25 @@
|
||||
package com.topjohnwu.magisk.core.tasks
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.os.postDelayed
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.DynAPK
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.di.Protected
|
||||
import com.topjohnwu.magisk.events.dialog.EnvFixDialog
|
||||
import com.topjohnwu.magisk.ktx.reboot
|
||||
import com.topjohnwu.magisk.ktx.symlink
|
||||
import com.topjohnwu.magisk.ktx.withStreams
|
||||
import com.topjohnwu.magisk.ktx.writeTo
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.signing.SignBoot
|
||||
import com.topjohnwu.superuser.Shell
|
||||
@ -37,7 +37,6 @@ import org.kamranzafar.jtar.TarHeader
|
||||
import org.kamranzafar.jtar.TarInputStream
|
||||
import org.kamranzafar.jtar.TarOutputStream
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.get
|
||||
import org.koin.core.inject
|
||||
import timber.log.Timber
|
||||
import java.io.*
|
||||
@ -46,10 +45,8 @@ import java.security.SecureRandom
|
||||
import java.util.*
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import kotlin.collections.set
|
||||
|
||||
abstract class MagiskInstallImpl protected constructor(
|
||||
private var zipUri: Uri,
|
||||
protected val console: MutableList<String> = NOPList.getInstance(),
|
||||
private val logs: MutableList<String> = NOPList.getInstance()
|
||||
) : KoinComponent {
|
||||
@ -59,17 +56,7 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
|
||||
private var tarOut: TarOutputStream? = null
|
||||
private val service: NetworkService by inject()
|
||||
protected val context: Context by inject()
|
||||
|
||||
companion object {
|
||||
private val ABI_MAP = TreeMap<String, String>()
|
||||
init {
|
||||
ABI_MAP["armeabi-v7a"] = "arm"
|
||||
ABI_MAP["arm64-v8a"] = "arm64"
|
||||
ABI_MAP["x86"] = "x86"
|
||||
ABI_MAP["x86_64"] = "x64"
|
||||
}
|
||||
}
|
||||
protected val context: Context by inject(Protected)
|
||||
|
||||
private fun findImage(): Boolean {
|
||||
srcBoot = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
|
||||
@ -98,69 +85,85 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun extractZip(): Boolean {
|
||||
val arch: String
|
||||
val arch32: String
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
arch = ABI_MAP[Build.SUPPORTED_ABIS[0]]!!
|
||||
arch32 = ABI_MAP[Build.SUPPORTED_32_BIT_ABIS[0]]!!
|
||||
private fun installDirFile(name: String): File {
|
||||
return if (installDir is SuFile)
|
||||
SuFile(installDir, name)
|
||||
else
|
||||
File(installDir, name)
|
||||
}
|
||||
|
||||
private fun extractFiles(): Boolean {
|
||||
console.add("- Device platform: ${Const.CPU_ABI}")
|
||||
console.add("- Installing: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
|
||||
|
||||
val binDir = File(context.filesDir.parent, "install")
|
||||
binDir.deleteRecursively()
|
||||
binDir.mkdirs()
|
||||
|
||||
installDir = if (Shell.rootAccess()) {
|
||||
SuFile("${Const.TMPDIR}/install")
|
||||
} else {
|
||||
arch = ABI_MAP[Build.CPU_ABI]!!
|
||||
arch32 = arch
|
||||
}
|
||||
|
||||
console.add("- Device platform: $arch")
|
||||
console.add("- Magisk Manager: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
|
||||
console.add("- Install target: ${Info.remote.magisk.version} (${Info.remote.magisk.versionCode})")
|
||||
|
||||
fun newFile(name: String): File {
|
||||
return if (installDir is SuFile)
|
||||
SuFile(installDir, name)
|
||||
else
|
||||
File(installDir, name)
|
||||
binDir
|
||||
}
|
||||
|
||||
try {
|
||||
ZipInputStream(zipUri.inputStream().buffered()).use { zi ->
|
||||
lateinit var ze: ZipEntry
|
||||
while (zi.nextEntry?.let { ze = it } != null) {
|
||||
if (ze.isDirectory)
|
||||
continue
|
||||
// Extract binaries
|
||||
if (isRunningAsStub) {
|
||||
ZipInputStream(DynAPK.current(context).inputStream().buffered()).use { zi ->
|
||||
lateinit var ze: ZipEntry
|
||||
while (zi.nextEntry?.let { ze = it } != null) {
|
||||
if (ze.isDirectory)
|
||||
continue
|
||||
|
||||
var name: String? = null
|
||||
|
||||
if (ze.name.startsWith("chromeos/")) {
|
||||
name = ze.name
|
||||
} else {
|
||||
for (n in listOf("$arch32/", "common/", "META-INF/com/google/android/update-binary")) {
|
||||
if (ze.name.startsWith(n)) {
|
||||
name = ze.name.substring(ze.name.lastIndexOf('/') + 1)
|
||||
break
|
||||
}
|
||||
val name = if (ze.name.startsWith("lib/${Const.CPU_ABI_32}/")) {
|
||||
val n = ze.name.substring(ze.name.lastIndexOf('/') + 1)
|
||||
n.substring(3, n.length - 3)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
val dest = File(binDir, name)
|
||||
dest.outputStream().use { zi.copyTo(it) }
|
||||
}
|
||||
|
||||
name ?: continue
|
||||
|
||||
val dest = newFile(name)
|
||||
dest.parentFile!!.mkdirs()
|
||||
SuFileOutputStream(dest).use { s -> zi.copyTo(s) }
|
||||
}
|
||||
} else {
|
||||
val libs = Const.NATIVE_LIB_DIR.listFiles { _, name ->
|
||||
name.startsWith("lib") && name.endsWith(".so")
|
||||
} ?: emptyArray()
|
||||
for (lib in libs) {
|
||||
val name = lib.name.substring(3, lib.name.length - 3)
|
||||
val bin = File(binDir, name)
|
||||
symlink(lib.path, bin.path)
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
console.add("! Cannot unzip zip")
|
||||
|
||||
// Extract scripts
|
||||
for (script in listOf("util_functions.sh", "boot_patch.sh", "addon.d.sh")) {
|
||||
val dest = File(binDir, script)
|
||||
context.assets.open(script).use { it.writeTo(dest) }
|
||||
}
|
||||
// Extract chromeos tools
|
||||
File(binDir, "chromeos").mkdir()
|
||||
for (file in listOf("futility", "kernel_data_key.vbprivk", "kernel.keyblock")) {
|
||||
val name = "chromeos/$file"
|
||||
val dest = File(binDir, name)
|
||||
context.assets.open(name).use { it.writeTo(dest) }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
console.add("! Unable to extract files")
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
|
||||
val init64 = newFile("magiskinit64")
|
||||
if (init64.exists() && arch != arch32) {
|
||||
init64.renameTo(newFile("magiskinit"))
|
||||
} else {
|
||||
init64.delete()
|
||||
if (installDir !== binDir) {
|
||||
arrayOf(
|
||||
"rm -rf $installDir",
|
||||
"mkdir -p $installDir",
|
||||
"cp_readlink $binDir $installDir",
|
||||
"rm -rf $binDir"
|
||||
).sh()
|
||||
}
|
||||
"cd $installDir; chmod 755 *".sh()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -177,7 +180,7 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
lateinit var entry: TarEntry
|
||||
|
||||
fun decompressedStream() =
|
||||
if (entry.name.contains(".lz4")) LZ4FrameInputStream(tarIn) else tarIn
|
||||
if (entry.name.endsWith(".lz4")) LZ4FrameInputStream(tarIn) else tarIn
|
||||
|
||||
while (tarIn.nextEntry?.let { entry = it } != null) {
|
||||
if (entry.name.contains("boot.img") ||
|
||||
@ -186,12 +189,9 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
console.add("-- Extracting: $name")
|
||||
|
||||
val extract = File(installDir, name)
|
||||
FileOutputStream(extract).use { decompressedStream().copyTo(it) }
|
||||
decompressedStream().writeTo(extract)
|
||||
} else if (entry.name.contains("vbmeta.img")) {
|
||||
val rawData = ByteArrayOutputStream().let {
|
||||
decompressedStream().copyTo(it)
|
||||
it.toByteArray()
|
||||
}
|
||||
val rawData = decompressedStream().readBytes()
|
||||
// Valid vbmeta.img should be at least 256 bytes
|
||||
if (rawData.size < 256)
|
||||
continue
|
||||
@ -208,8 +208,8 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
tarIn.copyTo(tarOut, bufferSize = 1024 * 1024)
|
||||
}
|
||||
}
|
||||
val boot = SuFile.open(installDir, "boot.img")
|
||||
val recovery = SuFile.open(installDir, "recovery.img")
|
||||
val boot = installDirFile("boot.img")
|
||||
val recovery = installDirFile("recovery.img")
|
||||
if (Config.recovery && recovery.exists() && boot.exists()) {
|
||||
// Install Magisk to recovery
|
||||
srcBoot = recovery.path
|
||||
@ -306,10 +306,19 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
return false
|
||||
}
|
||||
|
||||
// Fix up binaries
|
||||
if (installDir is SuFile) {
|
||||
"fix_env $installDir".sh()
|
||||
} else {
|
||||
"cp_readlink $installDir".sh()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun patchBoot(): Boolean {
|
||||
"cd $installDir".sh()
|
||||
|
||||
var srcNand = ""
|
||||
if ("[ -c $srcBoot ] && nanddump -f boot.img $srcBoot".sh().isSuccess) {
|
||||
srcNand = srcBoot
|
||||
@ -334,26 +343,20 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
"KEEPVERITY=${Config.keepVerity} " +
|
||||
"RECOVERYMODE=${Config.recovery}"
|
||||
|
||||
if (!("$FLAGS sh update-binary sh boot_patch.sh $srcBoot").sh().isSuccess) {
|
||||
if (!"$FLAGS sh boot_patch.sh $srcBoot".sh().isSuccess)
|
||||
return false
|
||||
}
|
||||
|
||||
if (srcNand.isNotEmpty()) {
|
||||
if (srcNand.isNotEmpty())
|
||||
srcBoot = srcNand
|
||||
}
|
||||
|
||||
val job = Shell.sh(
|
||||
"./magiskboot cleanup",
|
||||
"mv bin/busybox busybox",
|
||||
"rm -rf magisk.apk bin boot.img update-binary",
|
||||
"cd /")
|
||||
val job = Shell.sh("./magiskboot cleanup", "cd /")
|
||||
|
||||
val patched = File(installDir, "new-boot.img")
|
||||
val patched = installDirFile("new-boot.img")
|
||||
if (isSigned) {
|
||||
console.add("- Signing boot image with verity keys")
|
||||
val signed = File(installDir, "signed.img")
|
||||
val signed = installDirFile("signed.img")
|
||||
try {
|
||||
withStreams(SuFileInputStream(patched), signed.outputStream().buffered()) {
|
||||
withStreams(SuFileInputStream(patched), SuFileOutputStream(signed)) {
|
||||
input, out -> SignBoot.doSignature(null, null, input, out, "/boot")
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
@ -368,18 +371,10 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun copySepolicyRules(): Boolean {
|
||||
if (Info.remote.magisk.versionCode >= 21100) {
|
||||
// Copy existing rules for migration
|
||||
"copy_sepolicy_rules".sh()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun flashBoot(): Boolean {
|
||||
if (!"direct_install $installDir $srcBoot".sh().isSuccess)
|
||||
return false
|
||||
"run_migrations".sh()
|
||||
arrayOf("run_migrations", "copy_sepolicy_rules").sh()
|
||||
return true
|
||||
}
|
||||
|
||||
@ -403,24 +398,28 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
protected fun String.sh() = Shell.sh(this).to(console, logs).exec()
|
||||
protected fun uninstall(): Boolean {
|
||||
val apk = if (isRunningAsStub) {
|
||||
DynAPK.current(context).path
|
||||
} else {
|
||||
context.packageCodePath
|
||||
}
|
||||
return "run_uninstaller $apk".sh().isSuccess
|
||||
}
|
||||
|
||||
private fun String.sh() = Shell.sh(this).to(console, logs).exec()
|
||||
private fun Array<String>.sh() = Shell.sh(*this).to(console, logs).exec()
|
||||
private fun String.fsh() = ShellUtils.fastCmd(this)
|
||||
private fun Array<String>.fsh() = ShellUtils.fastCmd(*this)
|
||||
|
||||
protected fun doPatchFile(patchFile: Uri) = extractZip() && handleFile(patchFile)
|
||||
protected fun doPatchFile(patchFile: Uri) = extractFiles() && handleFile(patchFile)
|
||||
|
||||
protected fun direct() = findImage() && extractZip() && patchBoot() &&
|
||||
copySepolicyRules() && flashBoot()
|
||||
protected fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot()
|
||||
|
||||
protected suspend fun secondSlot() = findSecondaryImage() && extractZip() &&
|
||||
patchBoot() && copySepolicyRules() && flashBoot() && postOTA()
|
||||
protected suspend fun secondSlot() =
|
||||
findSecondaryImage() && extractFiles() && patchBoot() && flashBoot() && postOTA()
|
||||
|
||||
protected fun fixEnv(): Boolean {
|
||||
installDir = SuFile("/data/adb/magisk")
|
||||
Shell.su("rm -rf /data/adb/magisk/*").exec()
|
||||
return extractZip() && Shell.su("fix_env").exec().isSuccess
|
||||
}
|
||||
protected fun fixEnv() = extractFiles() && "fix_env $installDir".sh().isSuccess
|
||||
|
||||
@WorkerThread
|
||||
protected abstract suspend fun operations(): Boolean
|
||||
@ -429,85 +428,84 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
}
|
||||
|
||||
abstract class MagiskInstaller(
|
||||
zip: Uri,
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstallImpl(zip, console, logs) {
|
||||
|
||||
init {
|
||||
installDir = File(get<Context>(Protected).filesDir.parent, "install")
|
||||
"rm -rf $installDir".sh()
|
||||
installDir.mkdirs()
|
||||
}
|
||||
) : MagiskInstallImpl(console, logs) {
|
||||
|
||||
override suspend fun exec(): Boolean {
|
||||
val success = super.exec()
|
||||
if (success) {
|
||||
console.add("- All done!")
|
||||
} else {
|
||||
Shell.sh("rm -rf $installDir").submit()
|
||||
if (installDir is SuFile) {
|
||||
Shell.sh("rm -rf ${Const.TMPDIR}").submit()
|
||||
} else {
|
||||
Shell.sh("rm -rf $installDir").submit()
|
||||
}
|
||||
console.add("! Installation failed")
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
class Patch(
|
||||
zip: Uri,
|
||||
private val uri: Uri,
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstaller(zip, console, logs) {
|
||||
) : MagiskInstaller(console, logs) {
|
||||
override suspend fun operations() = doPatchFile(uri)
|
||||
}
|
||||
|
||||
class SecondSlot(
|
||||
zip: Uri,
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstaller(zip, console, logs) {
|
||||
) : MagiskInstaller(console, logs) {
|
||||
override suspend fun operations() = secondSlot()
|
||||
}
|
||||
|
||||
class Direct(
|
||||
zip: Uri,
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstaller(zip, console, logs) {
|
||||
) : MagiskInstaller(console, logs) {
|
||||
override suspend fun operations() = direct()
|
||||
}
|
||||
|
||||
class Emulator(
|
||||
zip: Uri,
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstallImpl(zip, console, logs) {
|
||||
) : MagiskInstaller(console, logs) {
|
||||
override suspend fun operations() = fixEnv()
|
||||
}
|
||||
|
||||
class Uninstall(
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstallImpl(console, logs) {
|
||||
override suspend fun operations() = uninstall()
|
||||
|
||||
override suspend fun exec(): Boolean {
|
||||
val success = super.exec()
|
||||
if (success) {
|
||||
console.add("- All done!")
|
||||
} else {
|
||||
console.add("! Installation failed")
|
||||
UiThreadHandler.handler.postDelayed(3000) {
|
||||
Shell.su("pm uninstall ${context.packageName}").exec()
|
||||
}
|
||||
}
|
||||
return success
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EnvFixTask(zip: Uri) : MagiskInstallImpl(zip) {
|
||||
class FixEnv(private val callback: () -> Unit) : MagiskInstallImpl() {
|
||||
override suspend fun operations() = fixEnv()
|
||||
|
||||
override suspend fun operations() = fixEnv()
|
||||
|
||||
override suspend fun exec(): Boolean {
|
||||
val success = super.exec()
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(EnvFixDialog.DISMISS))
|
||||
Utils.toast(
|
||||
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
if (success)
|
||||
UiThreadHandler.handler.postDelayed(5000) { reboot() }
|
||||
return success
|
||||
override suspend fun exec(): Boolean {
|
||||
val success = super.exec()
|
||||
callback()
|
||||
Utils.toast(
|
||||
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
if (success)
|
||||
UiThreadHandler.handler.postDelayed(5000) { reboot() }
|
||||
return success
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,17 @@ package com.topjohnwu.magisk.core.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.topjohnwu.magisk.DynAPK
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.wrap
|
||||
import com.topjohnwu.magisk.core.*
|
||||
import com.topjohnwu.magisk.ktx.cachedFile
|
||||
import com.topjohnwu.magisk.ktx.deviceProtectedContext
|
||||
import com.topjohnwu.magisk.ktx.rawResource
|
||||
import com.topjohnwu.magisk.ktx.writeTo
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import java.io.File
|
||||
import java.util.jar.JarFile
|
||||
|
||||
class RootInit : Shell.Initializer() {
|
||||
|
||||
@ -17,31 +20,50 @@ class RootInit : Shell.Initializer() {
|
||||
return init(context.wrap(), shell)
|
||||
}
|
||||
|
||||
fun init(context: Context, shell: Shell): Boolean {
|
||||
shell.newJob().apply {
|
||||
add("export SDK_INT=${Build.VERSION.SDK_INT}")
|
||||
if (Const.Version.atLeast_20_4()) {
|
||||
add("export MAGISKTMP=\$(magisk --path)/.magisk")
|
||||
} else {
|
||||
add("export MAGISKTMP=/sbin/.magisk")
|
||||
}
|
||||
if (Const.Version.atLeast_21_0()) {
|
||||
add("export ASH_STANDALONE=1")
|
||||
add("[ -x /data/adb/magisk/busybox ] && exec /data/adb/magisk/busybox sh")
|
||||
} else {
|
||||
add("export PATH=\"\$MAGISKTMP/busybox:\$PATH\"")
|
||||
}
|
||||
add(context.rawResource(R.raw.manager))
|
||||
if (shell.isRoot) {
|
||||
add(context.rawResource(R.raw.util_functions))
|
||||
}
|
||||
add("mm_init")
|
||||
}.exec()
|
||||
private fun init(context: Context, shell: Shell): Boolean {
|
||||
|
||||
fun fastCmd(cmd: String) = ShellUtils.fastCmd(shell, cmd)
|
||||
fun getVar(name: String) = fastCmd("echo \$$name")
|
||||
fun getBool(name: String) = getVar(name).toBoolean()
|
||||
|
||||
shell.newJob().apply {
|
||||
add("export SDK_INT=${Build.VERSION.SDK_INT}")
|
||||
add("export ASH_STANDALONE=1")
|
||||
|
||||
val localBB: File
|
||||
if (isRunningAsStub) {
|
||||
val jar = JarFile(DynAPK.current(context))
|
||||
val bb = jar.getJarEntry("lib/${Const.CPU_ABI_32}/libbusybox.so")
|
||||
localBB = context.deviceProtectedContext.cachedFile("busybox")
|
||||
jar.getInputStream(bb).writeTo(localBB)
|
||||
localBB.setExecutable(true)
|
||||
} else {
|
||||
localBB = File(Const.NATIVE_LIB_DIR, "libbusybox.so")
|
||||
}
|
||||
|
||||
if (!shell.isRoot) {
|
||||
// Directly execute the file
|
||||
add("exec $localBB sh")
|
||||
} else {
|
||||
// Copy it out of /data to workaround Samsung BS
|
||||
add(
|
||||
"export MAGISKTMP=\$(magisk --path)/.magisk",
|
||||
"if [ -x \$MAGISKTMP/busybox/busybox ]; then",
|
||||
" cp -af $localBB \$MAGISKTMP/busybox/busybox",
|
||||
" exec \$MAGISKTMP/busybox/busybox sh",
|
||||
"else",
|
||||
" exec $localBB sh",
|
||||
"fi"
|
||||
)
|
||||
}
|
||||
|
||||
add(context.rawResource(R.raw.manager))
|
||||
if (shell.isRoot) {
|
||||
add(context.assets.open("util_functions.sh"))
|
||||
}
|
||||
add("mm_init")
|
||||
}.exec()
|
||||
|
||||
Const.MAGISKTMP = getVar("MAGISKTMP")
|
||||
Info.isSAR = getBool("SYSTEM_ROOT")
|
||||
Info.ramdisk = getBool("RAMDISKEXIST")
|
||||
|
@ -110,7 +110,7 @@ class SelectModuleEvent : ViewEvent(), FragmentExecutor {
|
||||
activity.startActivityForResult(intent, Const.ID.FETCH_ZIP) { code, intent ->
|
||||
if (code == Activity.RESULT_OK && intent != null) {
|
||||
intent.data?.also {
|
||||
MainDirections.actionFlashFragment(it, Const.Value.FLASH_ZIP).navigate()
|
||||
MainDirections.actionFlashFragment(Const.Value.FLASH_ZIP, it).navigate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,9 @@ abstract class DialogEvent : ViewEvent(), ActivityExecutor {
|
||||
protected lateinit var dialog: MagiskDialog
|
||||
|
||||
override fun invoke(activity: BaseUIActivity<*, *>) {
|
||||
dialog = MagiskDialog(activity).apply(this::build).reveal()
|
||||
dialog = MagiskDialog(activity)
|
||||
.apply { setOwnerActivity(activity) }
|
||||
.apply(this::build).reveal()
|
||||
}
|
||||
|
||||
abstract fun build(dialog: MagiskDialog)
|
||||
|
@ -1,15 +1,11 @@
|
||||
package com.topjohnwu.magisk.events.dialog
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.download.Action.EnvFix
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.Subject.Magisk
|
||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class EnvFixDialog : DialogEvent() {
|
||||
|
||||
@ -24,20 +20,17 @@ class EnvFixDialog : DialogEvent() {
|
||||
.applyMessage(R.string.setup_msg)
|
||||
.resetButtons()
|
||||
.cancellable(false)
|
||||
val lbm = LocalBroadcastManager.getInstance(dialog.context)
|
||||
lbm.registerReceiver(object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
(dialog.ownerActivity as BaseActivity).lifecycleScope.launch {
|
||||
MagiskInstaller.FixEnv {
|
||||
dialog.dismiss()
|
||||
lbm.unregisterReceiver(this)
|
||||
}
|
||||
}, IntentFilter(DISMISS))
|
||||
DownloadService.start(dialog.context, Magisk(EnvFix))
|
||||
}.exec()
|
||||
}
|
||||
}
|
||||
}
|
||||
.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||
titleRes = android.R.string.cancel
|
||||
}
|
||||
.let { Unit }
|
||||
.let { }
|
||||
|
||||
companion object {
|
||||
const val DISMISS = "com.topjohnwu.magisk.ENV_DONE"
|
||||
|
@ -14,7 +14,7 @@ class ModuleInstallDialog(private val item: OnlineModule) : DialogEvent() {
|
||||
with(dialog) {
|
||||
|
||||
fun download(install: Boolean) {
|
||||
val config = if (install) Action.Flash.Primary else Action.Download
|
||||
val config = if (install) Action.Flash else Action.Download
|
||||
val subject = Subject.Module(item, config)
|
||||
DownloadService.start(context, subject)
|
||||
}
|
||||
|
@ -4,9 +4,7 @@ import android.app.ProgressDialog
|
||||
import android.widget.Toast
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.download.Action
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import com.topjohnwu.superuser.Shell
|
||||
@ -46,7 +44,7 @@ class UninstallDialog : DialogEvent() {
|
||||
}
|
||||
|
||||
private fun completeUninstall() {
|
||||
DownloadService.start(dialog.context, Subject.Magisk(Action.Uninstall))
|
||||
FlashFragment.uninstall()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import android.graphics.drawable.LayerDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.system.Os
|
||||
import android.text.PrecomputedText
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -59,6 +60,20 @@ import java.lang.reflect.Array as JArray
|
||||
|
||||
val packageName: String get() = get<Context>().packageName
|
||||
|
||||
fun symlink(oldPath: String, newPath: String) {
|
||||
if (SDK_INT >= 21) {
|
||||
Os.symlink(oldPath, newPath)
|
||||
} else {
|
||||
// Just copy the files pre 5.0
|
||||
val old = File(oldPath)
|
||||
val newFile = File(newPath)
|
||||
old.copyTo(newFile)
|
||||
if (old.canExecute())
|
||||
newFile.setExecutable(true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0
|
||||
|
||||
@get:SuppressLint("InlinedApi")
|
||||
@ -83,6 +98,11 @@ fun Context.getBitmap(id: Int): Bitmap {
|
||||
return bitmap
|
||||
}
|
||||
|
||||
val Context.deviceProtectedContext: Context get() =
|
||||
if (SDK_INT >= 24) {
|
||||
createDeviceProtectedStorageContext()
|
||||
} else { this }
|
||||
|
||||
fun Intent.startActivity(context: Context) = context.startActivity(this)
|
||||
|
||||
fun Intent.startActivityWithRoot() {
|
||||
|
@ -17,13 +17,12 @@ import com.topjohnwu.magisk.ui.MainActivity
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import com.topjohnwu.magisk.MainDirections.Companion.actionFlashFragment as toFlash
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragmentArgs as args
|
||||
|
||||
class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>() {
|
||||
|
||||
override val layoutRes = R.layout.fragment_flash_md2
|
||||
override val viewModel by viewModel<FlashViewModel> {
|
||||
parametersOf(args.fromBundle(requireArguments()))
|
||||
parametersOf(FlashFragmentArgs.fromBundle(requireArguments()))
|
||||
}
|
||||
|
||||
private var defaultOrientation = -1
|
||||
@ -78,7 +77,7 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
|
||||
|
||||
companion object {
|
||||
|
||||
private fun createIntent(context: Context, args: args) =
|
||||
private fun createIntent(context: Context, args: FlashFragmentArgs) =
|
||||
NavDeepLinkBuilder(context)
|
||||
.setGraph(R.navigation.main)
|
||||
.setComponentName(MainActivity::class.java.cmp(context.packageName))
|
||||
@ -91,59 +90,34 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
|
||||
|
||||
/* Flashing is understood as installing / flashing magisk itself */
|
||||
|
||||
fun flashIntent(context: Context, file: Uri, isSecondSlot: Boolean, id: Int = -1) = args(
|
||||
installer = file,
|
||||
action = flashType(isSecondSlot),
|
||||
dismissId = id
|
||||
).let { createIntent(context, it) }
|
||||
|
||||
fun flash(file: Uri, isSecondSlot: Boolean, id: Int) = toFlash(
|
||||
installer = file,
|
||||
action = flashType(isSecondSlot),
|
||||
dismissId = id
|
||||
fun flash(isSecondSlot: Boolean) = toFlash(
|
||||
action = flashType(isSecondSlot)
|
||||
).let { BaseUIActivity.postDirections(it) }
|
||||
|
||||
/* Patching is understood as injecting img files with magisk */
|
||||
|
||||
fun patchIntent(context: Context, file: Uri, uri: Uri, id: Int = -1) = args(
|
||||
installer = file,
|
||||
fun patch(uri: Uri) = toFlash(
|
||||
action = Const.Value.PATCH_FILE,
|
||||
additionalData = uri,
|
||||
dismissId = id
|
||||
).let { createIntent(context, it) }
|
||||
|
||||
fun patch(file: Uri, uri: Uri, id: Int) = toFlash(
|
||||
installer = file,
|
||||
action = Const.Value.PATCH_FILE,
|
||||
additionalData = uri,
|
||||
dismissId = id
|
||||
additionalData = uri
|
||||
).let { BaseUIActivity.postDirections(it) }
|
||||
|
||||
/* Uninstalling is understood as removing magisk entirely */
|
||||
|
||||
fun uninstallIntent(context: Context, file: Uri, id: Int = -1) = args(
|
||||
installer = file,
|
||||
action = Const.Value.UNINSTALL,
|
||||
dismissId = id
|
||||
).let { createIntent(context, it) }
|
||||
|
||||
fun uninstall(file: Uri, id: Int) = toFlash(
|
||||
installer = file,
|
||||
action = Const.Value.UNINSTALL,
|
||||
dismissId = id
|
||||
fun uninstall() = toFlash(
|
||||
action = Const.Value.UNINSTALL
|
||||
).let { BaseUIActivity.postDirections(it) }
|
||||
|
||||
/* Installing is understood as flashing modules / zips */
|
||||
|
||||
fun installIntent(context: Context, file: Uri, id: Int = -1) = args(
|
||||
installer = file,
|
||||
fun installIntent(context: Context, file: Uri, id: Int = -1) = FlashFragmentArgs(
|
||||
action = Const.Value.FLASH_ZIP,
|
||||
additionalData = file,
|
||||
dismissId = id
|
||||
).let { createIntent(context, it) }
|
||||
|
||||
fun install(file: Uri, id: Int) = toFlash(
|
||||
installer = file,
|
||||
action = Const.Value.FLASH_ZIP,
|
||||
additionalData = file,
|
||||
dismissId = id
|
||||
).let { BaseUIActivity.postDirections(it) }
|
||||
}
|
||||
|
@ -27,9 +27,7 @@ import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class FlashViewModel(
|
||||
args: FlashFragmentArgs
|
||||
) : BaseViewModel() {
|
||||
class FlashViewModel(args: FlashFragmentArgs) : BaseViewModel() {
|
||||
|
||||
@get:Bindable
|
||||
var showReboot = Shell.rootAccess()
|
||||
@ -52,36 +50,35 @@ class FlashViewModel(
|
||||
}
|
||||
|
||||
init {
|
||||
args.dismissId.takeIf { it != -1 }?.also {
|
||||
Notifications.mgr.cancel(it)
|
||||
}
|
||||
val (installer, action, uri) = args
|
||||
startFlashing(installer, uri, action)
|
||||
val (action, uri, id) = args
|
||||
if (id != -1)
|
||||
Notifications.mgr.cancel(id)
|
||||
startFlashing(action, uri)
|
||||
}
|
||||
|
||||
private fun startFlashing(installer: Uri, uri: Uri?, action: String) {
|
||||
private fun startFlashing(action: String, uri: Uri?) {
|
||||
viewModelScope.launch {
|
||||
val result = when (action) {
|
||||
Const.Value.FLASH_ZIP -> {
|
||||
FlashZip(installer, outItems, logItems).exec()
|
||||
FlashZip(uri!!, outItems, logItems).exec()
|
||||
}
|
||||
Const.Value.UNINSTALL -> {
|
||||
showReboot = false
|
||||
FlashZip.Uninstall(installer, outItems, logItems).exec()
|
||||
MagiskInstaller.Uninstall(outItems, logItems).exec()
|
||||
}
|
||||
Const.Value.FLASH_MAGISK -> {
|
||||
if (Info.isEmulator)
|
||||
MagiskInstaller.Emulator(installer, outItems, logItems).exec()
|
||||
MagiskInstaller.Emulator(outItems, logItems).exec()
|
||||
else
|
||||
MagiskInstaller.Direct(installer, outItems, logItems).exec()
|
||||
MagiskInstaller.Direct(outItems, logItems).exec()
|
||||
}
|
||||
Const.Value.FLASH_INACTIVE_SLOT -> {
|
||||
MagiskInstaller.SecondSlot(installer, outItems, logItems).exec()
|
||||
MagiskInstaller.SecondSlot(outItems, logItems).exec()
|
||||
}
|
||||
Const.Value.PATCH_FILE -> {
|
||||
uri ?: return@launch
|
||||
showReboot = false
|
||||
MagiskInstaller.Patch(installer, uri, outItems, logItems).exec()
|
||||
MagiskInstaller.Patch(uri, outItems, logItems).exec()
|
||||
}
|
||||
else -> {
|
||||
back()
|
||||
|
@ -7,7 +7,6 @@ import android.view.ViewGroup
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseUIFragment
|
||||
import com.topjohnwu.magisk.core.download.BaseDownloader
|
||||
import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding
|
||||
import com.topjohnwu.magisk.ktx.coroutineScope
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
@ -23,7 +22,6 @@ class InstallFragment : BaseUIFragment<InstallViewModel, FragmentInstallMd2Bindi
|
||||
|
||||
// Allow markwon to run in viewmodel scope
|
||||
binding.releaseNotes.coroutineScope = viewModel.viewModelScope
|
||||
BaseDownloader.observeProgress(this, viewModel::onProgressUpdate)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -8,23 +8,19 @@ import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.download.Action
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.data.repository.NetworkService
|
||||
import com.topjohnwu.magisk.events.MagiskInstallFileEvent
|
||||
import com.topjohnwu.magisk.events.dialog.SecondSlotWarningDialog
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.get
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class InstallViewModel(
|
||||
svc: NetworkService
|
||||
) : BaseViewModel(State.LOADED) {
|
||||
) : BaseViewModel() {
|
||||
|
||||
val isRooted = Shell.rootAccess()
|
||||
val skipOptions = Info.isEmulator || (Info.ramdisk && !Info.isFDE && Info.isSAR)
|
||||
@ -53,10 +49,6 @@ class InstallViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var progress = 0
|
||||
set(value) = set(value, field, { field = it }, BR.progress)
|
||||
|
||||
@get:Bindable
|
||||
var data: Uri? = null
|
||||
set(value) = set(value, field, { field = it }, BR.data)
|
||||
@ -75,34 +67,17 @@ class InstallViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun onProgressUpdate(progress: Float, subject: Subject) {
|
||||
if (subject !is Subject.Magisk) {
|
||||
return
|
||||
}
|
||||
this.progress = progress.times(100).roundToInt()
|
||||
if (this.progress >= 100) {
|
||||
state = State.LOADED
|
||||
} else if (this.progress < -150) {
|
||||
state = State.LOADING_FAILED
|
||||
}
|
||||
}
|
||||
|
||||
fun step(nextStep: Int) {
|
||||
step = nextStep
|
||||
}
|
||||
|
||||
fun install() {
|
||||
DownloadService.start(get(), Subject.Magisk(resolveAction()))
|
||||
when (method) {
|
||||
R.id.method_patch -> FlashFragment.patch(data!!)
|
||||
R.id.method_direct -> FlashFragment.flash(false)
|
||||
R.id.method_inactive_slot -> FlashFragment.flash(true)
|
||||
else -> error("Unknown value")
|
||||
}
|
||||
state = State.LOADING
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
private fun resolveAction() = when (method) {
|
||||
R.id.method_download -> Action.Download
|
||||
R.id.method_patch -> Action.Patch(data!!)
|
||||
R.id.method_direct -> Action.Flash.Primary
|
||||
R.id.method_inactive_slot -> Action.Flash.Secondary
|
||||
else -> error("Unknown value")
|
||||
}
|
||||
}
|
||||
|
@ -25,306 +25,217 @@
|
||||
app:fitsSystemWindowsInsets="top|bottom"
|
||||
tools:paddingTop="24dp">
|
||||
|
||||
<FrameLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="@dimen/l_50">
|
||||
|
||||
<LinearLayout
|
||||
goneUnless="@{viewModel.loaded && viewModel.progress <= 0}"
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/WidgetFoundation.Card"
|
||||
gone="@{viewModel.skipOptions}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="@dimen/l_50">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/WidgetFoundation.Card"
|
||||
gone="@{viewModel.skipOptions}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:focusable="false">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
style="@style/WidgetFoundation.Icon"
|
||||
isSelected="@{viewModel.step > 0}"
|
||||
android:layout_marginStart="@dimen/l_25"
|
||||
app:srcCompat="@drawable/ic_check_circle_md2" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/install_options_title"
|
||||
android:textAppearance="@style/AppearanceFoundation.Body"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
style="@style/WidgetFoundation.Button.Text"
|
||||
gone="@{viewModel.step != 0}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="@{() -> viewModel.step(1)}"
|
||||
android:text="@string/install_next" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
gone="@{viewModel.step != 0}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_marginTop="@dimen/l_50"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:layout_marginBottom="@dimen/l_50"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="3dp"
|
||||
android:paddingEnd="3dp"
|
||||
tools:layout_gravity="center">
|
||||
|
||||
<CheckBox
|
||||
style="@style/WidgetFoundation.Checkbox"
|
||||
gone="@{Info.isSAR}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={Config.keepVerity}"
|
||||
android:text="@string/keep_dm_verity"
|
||||
tools:checked="true" />
|
||||
|
||||
<CheckBox
|
||||
style="@style/WidgetFoundation.Checkbox"
|
||||
goneUnless="@{Info.isFDE}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={Config.keepEnc}"
|
||||
android:text="@string/keep_force_encryption"
|
||||
app:tint="?colorPrimary" />
|
||||
|
||||
<CheckBox
|
||||
style="@style/WidgetFoundation.Checkbox"
|
||||
gone="@{Info.ramdisk}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={Config.recovery}"
|
||||
android:text="@string/recovery_mode"
|
||||
app:tint="?colorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/WidgetFoundation.Card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_marginTop="@dimen/l1"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:focusable="false">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
style="@style/WidgetFoundation.Icon"
|
||||
isSelected="@{viewModel.step > 1}"
|
||||
android:layout_marginStart="@dimen/l_25"
|
||||
app:srcCompat="@drawable/ic_check_circle_md2" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/install_method_title"
|
||||
android:textAppearance="@style/AppearanceFoundation.Body"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
style="@style/WidgetFoundation.Button.Text"
|
||||
gone="@{viewModel.step != 1}"
|
||||
isEnabled="@{viewModel.method == @id/method_patch ? viewModel.data != null : viewModel.method != -1}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="@{() -> viewModel.install()}"
|
||||
android:text="@string/install_start"
|
||||
app:icon="@drawable/ic_forth_md2"
|
||||
app:iconGravity="textEnd" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<RadioGroup
|
||||
gone="@{viewModel.step != 1}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_marginTop="@dimen/l_50"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:layout_marginBottom="@dimen/l_50"
|
||||
android:checkedButton="@={viewModel.method}"
|
||||
android:paddingStart="3dp"
|
||||
android:paddingEnd="3dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/method_download"
|
||||
style="@style/WidgetFoundation.RadioButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/download_zip_only"
|
||||
tools:checked="true" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/method_patch"
|
||||
style="@style/WidgetFoundation.RadioButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/select_patch_file" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/method_direct"
|
||||
style="@style/WidgetFoundation.RadioButton"
|
||||
gone="@{!viewModel.rooted}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/direct_install" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/method_inactive_slot"
|
||||
style="@style/WidgetFoundation.RadioButton"
|
||||
gone="@{viewModel.noSecondSlot}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/install_inactive_slot" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/WidgetFoundation.Card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/l1"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:focusable="false">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/release_notes"
|
||||
markdownText="@{viewModel.notes}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="15dp"
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption"
|
||||
android:visibility="gone"
|
||||
tools:maxLines="5"
|
||||
tools:text="@tools:sample/lorem/random"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
goneUnless="@{viewModel.loading}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
android:focusable="false">
|
||||
|
||||
<TextView
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/loading"
|
||||
android:textAppearance="@style/AppearanceFoundation.Title" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/l1">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ProgressBar
|
||||
style="@style/WidgetFoundation.ProgressBar"
|
||||
invisible="@{viewModel.progress <= 0}"
|
||||
android:layout_width="100dp"
|
||||
android:layout_gravity="center"
|
||||
android:progress="@{viewModel.progress}" />
|
||||
<ImageView
|
||||
style="@style/WidgetFoundation.Icon"
|
||||
isSelected="@{viewModel.step > 0}"
|
||||
android:layout_marginStart="@dimen/l_25"
|
||||
app:srcCompat="@drawable/ic_check_circle_md2" />
|
||||
|
||||
<ProgressBar
|
||||
style="@style/WidgetFoundation.ProgressBar.Indeterminate"
|
||||
invisibleUnless="@{viewModel.progress <= 0}"
|
||||
android:layout_width="100dp"
|
||||
android:layout_gravity="center" />
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/install_options_title"
|
||||
android:textAppearance="@style/AppearanceFoundation.Body"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</FrameLayout>
|
||||
<Button
|
||||
style="@style/WidgetFoundation.Button.Text"
|
||||
gone="@{viewModel.step != 0}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="@{() -> viewModel.step(1)}"
|
||||
android:text="@string/install_next" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
gone="@{viewModel.step != 0}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_marginTop="@dimen/l_50"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:layout_marginBottom="@dimen/l_50"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="3dp"
|
||||
android:paddingEnd="3dp"
|
||||
tools:layout_gravity="center">
|
||||
|
||||
<LinearLayout
|
||||
goneUnless="@{viewModel.loaded && viewModel.progress >= 100}"
|
||||
<CheckBox
|
||||
style="@style/WidgetFoundation.Checkbox"
|
||||
gone="@{Info.isSAR}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={Config.keepVerity}"
|
||||
android:text="@string/keep_dm_verity"
|
||||
tools:checked="true" />
|
||||
|
||||
<CheckBox
|
||||
style="@style/WidgetFoundation.Checkbox"
|
||||
goneUnless="@{Info.isFDE}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={Config.keepEnc}"
|
||||
android:text="@string/keep_force_encryption"
|
||||
app:tint="?colorPrimary" />
|
||||
|
||||
<CheckBox
|
||||
style="@style/WidgetFoundation.Checkbox"
|
||||
gone="@{Info.ramdisk}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={Config.recovery}"
|
||||
android:text="@string/recovery_mode"
|
||||
app:tint="?colorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/WidgetFoundation.Card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_marginTop="@dimen/l1"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:focusable="false">
|
||||
|
||||
<TextView
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/download_complete"
|
||||
android:textAppearance="@style/AppearanceFoundation.Title" />
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
goneUnless="@{viewModel.loadFailed}"
|
||||
<ImageView
|
||||
style="@style/WidgetFoundation.Icon"
|
||||
isSelected="@{viewModel.step > 1}"
|
||||
android:layout_marginStart="@dimen/l_25"
|
||||
app:srcCompat="@drawable/ic_check_circle_md2" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/install_method_title"
|
||||
android:textAppearance="@style/AppearanceFoundation.Body"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
style="@style/WidgetFoundation.Button.Text"
|
||||
gone="@{viewModel.step != 1}"
|
||||
isEnabled="@{viewModel.method == @id/method_patch ? viewModel.data != null : viewModel.method != -1}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="@{() -> viewModel.install()}"
|
||||
android:text="@string/install_start"
|
||||
app:icon="@drawable/ic_forth_md2"
|
||||
app:iconGravity="textEnd" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<RadioGroup
|
||||
gone="@{viewModel.step != 1}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_marginTop="@dimen/l_50"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:layout_marginBottom="@dimen/l_50"
|
||||
android:checkedButton="@={viewModel.method}"
|
||||
android:paddingStart="3dp"
|
||||
android:paddingEnd="3dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/method_patch"
|
||||
style="@style/WidgetFoundation.RadioButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/select_patch_file" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/method_direct"
|
||||
style="@style/WidgetFoundation.RadioButton"
|
||||
gone="@{!viewModel.rooted}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/direct_install" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/method_inactive_slot"
|
||||
style="@style/WidgetFoundation.RadioButton"
|
||||
gone="@{viewModel.noSecondSlot}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/install_inactive_slot" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/WidgetFoundation.Card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
android:layout_marginTop="@dimen/l1"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:focusable="false">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/release_notes"
|
||||
markdownText="@{viewModel.notes}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/download_file_error"
|
||||
android:textAppearance="@style/AppearanceFoundation.Title" />
|
||||
android:layout_margin="15dp"
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption"
|
||||
android:visibility="gone"
|
||||
tools:maxLines="5"
|
||||
tools:text="@tools:sample/lorem/random"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
|
@ -49,10 +49,6 @@
|
||||
android:label="FlashFragment"
|
||||
tools:layout="@layout/fragment_flash_md2">
|
||||
|
||||
<argument
|
||||
android:name="installer"
|
||||
app:argType="android.net.Uri" />
|
||||
|
||||
<argument
|
||||
android:name="action"
|
||||
app:argType="string" />
|
||||
|
1
app/src/main/res/raw/.gitignore
vendored
1
app/src/main/res/raw/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/util_functions.sh
|
@ -13,21 +13,35 @@ env_check() {
|
||||
return 0
|
||||
}
|
||||
|
||||
fix_env() {
|
||||
cd $MAGISKBIN
|
||||
PATH=/system/bin /system/bin/sh update-binary -x
|
||||
./busybox rm -f update-binary magisk.apk
|
||||
./busybox chmod -R 755 .
|
||||
./magiskinit -x magisk magisk
|
||||
cp_readlink() {
|
||||
if [ -z $2 ]; then
|
||||
cd $1
|
||||
else
|
||||
cp -af $1/. $2
|
||||
cd $2
|
||||
fi
|
||||
for file in *; do
|
||||
if [ -L $file ]; then
|
||||
local full=$(readlink -f $file)
|
||||
rm $file
|
||||
cp -af $full $file
|
||||
fi
|
||||
done
|
||||
chmod -R 755 .
|
||||
cd /
|
||||
}
|
||||
|
||||
direct_install() {
|
||||
rm -rf $MAGISKBIN/* 2>/dev/null
|
||||
fix_env() {
|
||||
# Cleanup and make dirs
|
||||
rm -rf $MAGISKBIN/*
|
||||
mkdir -p $MAGISKBIN 2>/dev/null
|
||||
chmod 700 $NVBASE
|
||||
cp -af $1/. $MAGISKBIN
|
||||
rm -f $MAGISKBIN/new-boot.img
|
||||
rm -rf $1
|
||||
chown -R 0:0 $MAGISKBIN
|
||||
}
|
||||
|
||||
direct_install() {
|
||||
echo "- Flashing new boot image"
|
||||
flash_image $1/new-boot.img $2
|
||||
case $? in
|
||||
@ -40,10 +54,20 @@ direct_install() {
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
rm -rf $1
|
||||
|
||||
rm -f $1/new-boot.img
|
||||
fix_env $1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
run_uninstaller() {
|
||||
rm -rf /dev/tmp
|
||||
mkdir -p /dev/tmp/install
|
||||
unzip -o "$1" "assets/*" "lib/*" -d /dev/tmp/install
|
||||
INSTALLER=/dev/tmp/install sh /dev/tmp/install/assets/magisk_uninstaller.sh dummy 1 "$1"
|
||||
}
|
||||
|
||||
restore_imgs() {
|
||||
[ -z $SHA1 ] && return 1
|
||||
local BACKUPDIR=/data/magisk_backup_$SHA1
|
||||
|
Reference in New Issue
Block a user