From ec8fffe61cf6c2ce66a52d9e8b33cd6e60c7ceb8 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Fri, 22 Jan 2021 02:28:53 -0800 Subject: [PATCH] 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 --- app/.gitignore | 5 +- app/build.gradle.kts | 89 +++- app/src/main/AndroidManifest.xml | 2 + .../java/com/topjohnwu/magisk/core/App.kt | 7 + .../java/com/topjohnwu/magisk/core/Const.kt | 25 +- .../topjohnwu/magisk/core/download/Action.kt | 31 -- .../magisk/core/download/BaseDownloader.kt | 20 +- .../magisk/core/download/DownloadService.kt | 33 +- .../topjohnwu/magisk/core/download/Subject.kt | 62 +-- .../topjohnwu/magisk/core/tasks/FlashZip.kt | 90 ++-- .../topjohnwu/magisk/core/tasks/HideAPK.kt | 3 +- .../magisk/core/tasks/MagiskInstaller.kt | 282 ++++++----- .../topjohnwu/magisk/core/utils/RootInit.kt | 70 ++- .../com/topjohnwu/magisk/events/ViewEvents.kt | 2 +- .../magisk/events/dialog/DialogEvent.kt | 4 +- .../magisk/events/dialog/EnvFixDialog.kt | 25 +- .../events/dialog/ModuleInstallDialog.kt | 2 +- .../magisk/events/dialog/UninstallDialog.kt | 6 +- .../java/com/topjohnwu/magisk/ktx/XAndroid.kt | 20 + .../magisk/ui/flash/FlashFragment.kt | 48 +- .../magisk/ui/flash/FlashViewModel.kt | 27 +- .../magisk/ui/install/InstallFragment.kt | 2 - .../magisk/ui/install/InstallViewModel.kt | 41 +- .../main/res/layout/fragment_install_md2.xml | 451 +++++++----------- app/src/main/res/navigation/main.xml | 4 - app/src/main/res/raw/.gitignore | 1 - app/src/main/res/raw/manager.sh | 44 +- build.gradle.kts | 2 +- build.py | 144 +----- buildSrc/src/main/java/BuildSource.kt | 5 +- config.prop.sample | 3 - scripts/boot_patch.sh | 28 +- scripts/flash_script.sh | 31 +- scripts/magisk_uninstaller.sh | 79 +-- scripts/update_binary.sh | 53 +- scripts/util_functions.sh | 64 +-- stub/build.gradle.kts | 4 +- 37 files changed, 784 insertions(+), 1025 deletions(-) delete mode 100644 app/src/main/java/com/topjohnwu/magisk/core/download/Action.kt delete mode 100644 app/src/main/res/raw/.gitignore diff --git a/app/.gitignore b/app/.gitignore index 88ab538f6..50b291029 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -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 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7024c0746..186bfe1e0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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("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") } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 73c3175b0..28a97b807 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -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"> diff --git a/app/src/main/java/com/topjohnwu/magisk/core/App.kt b/app/src/main/java/com/topjohnwu/magisk/core/App.kt index fed406a5a..988640ce4 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/App.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/App.kt @@ -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) diff --git a/app/src/main/java/com/topjohnwu/magisk/core/Const.kt b/app/src/main/java/com/topjohnwu/magisk/core/Const.kt index b5e91f1b4..0298cfded 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/Const.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/Const.kt @@ -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 diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/Action.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/Action.kt deleted file mode 100644 index ae3b9cc0b..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/Action.kt +++ /dev/null @@ -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() - -} diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/BaseDownloader.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/BaseDownloader.kt index 7e20d0bef..4f7500051 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/BaseDownloader.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/BaseDownloader.kt @@ -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) diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt index 06f9e9b54..7f7b0755a 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt @@ -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()) } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt index 4e03eb038..ad88d8913 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt @@ -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() } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/tasks/FlashZip.kt b/app/src/main/java/com/topjohnwu/magisk/core/tasks/FlashZip.kt index e054f7c1b..d76c05200 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/tasks/FlashZip.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/tasks/FlashZip.kt @@ -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 ): 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, - log: MutableList - ) : 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 - } - } - } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt b/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt index f221714fb..a89cb375d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt @@ -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 { diff --git a/app/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt b/app/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt index eebb6ddf9..e8f8a7cd3 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt @@ -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 = NOPList.getInstance(), private val logs: MutableList = 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() - 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.sh() = Shell.sh(*this).to(console, logs).exec() private fun String.fsh() = ShellUtils.fastCmd(this) private fun Array.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, logs: MutableList -) : MagiskInstallImpl(zip, console, logs) { - - init { - installDir = File(get(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, logs: MutableList - ) : MagiskInstaller(zip, console, logs) { + ) : MagiskInstaller(console, logs) { override suspend fun operations() = doPatchFile(uri) } class SecondSlot( - zip: Uri, console: MutableList, logs: MutableList - ) : MagiskInstaller(zip, console, logs) { + ) : MagiskInstaller(console, logs) { override suspend fun operations() = secondSlot() } class Direct( - zip: Uri, console: MutableList, logs: MutableList - ) : MagiskInstaller(zip, console, logs) { + ) : MagiskInstaller(console, logs) { override suspend fun operations() = direct() } class Emulator( - zip: Uri, console: MutableList, logs: MutableList - ) : MagiskInstallImpl(zip, console, logs) { + ) : MagiskInstaller(console, logs) { override suspend fun operations() = fixEnv() + } + + class Uninstall( + console: MutableList, + logs: MutableList + ) : 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 + } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/utils/RootInit.kt b/app/src/main/java/com/topjohnwu/magisk/core/utils/RootInit.kt index 20db3339a..ac1a8f3b7 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/utils/RootInit.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/utils/RootInit.kt @@ -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") diff --git a/app/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt b/app/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt index b72a1c6ad..2a6165cd3 100644 --- a/app/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt +++ b/app/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt @@ -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() } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/events/dialog/DialogEvent.kt b/app/src/main/java/com/topjohnwu/magisk/events/dialog/DialogEvent.kt index 293c71e41..700326abb 100644 --- a/app/src/main/java/com/topjohnwu/magisk/events/dialog/DialogEvent.kt +++ b/app/src/main/java/com/topjohnwu/magisk/events/dialog/DialogEvent.kt @@ -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) diff --git a/app/src/main/java/com/topjohnwu/magisk/events/dialog/EnvFixDialog.kt b/app/src/main/java/com/topjohnwu/magisk/events/dialog/EnvFixDialog.kt index 375f9d915..89c27ac94 100644 --- a/app/src/main/java/com/topjohnwu/magisk/events/dialog/EnvFixDialog.kt +++ b/app/src/main/java/com/topjohnwu/magisk/events/dialog/EnvFixDialog.kt @@ -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" diff --git a/app/src/main/java/com/topjohnwu/magisk/events/dialog/ModuleInstallDialog.kt b/app/src/main/java/com/topjohnwu/magisk/events/dialog/ModuleInstallDialog.kt index ea7b1e069..0e0711940 100644 --- a/app/src/main/java/com/topjohnwu/magisk/events/dialog/ModuleInstallDialog.kt +++ b/app/src/main/java/com/topjohnwu/magisk/events/dialog/ModuleInstallDialog.kt @@ -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) } diff --git a/app/src/main/java/com/topjohnwu/magisk/events/dialog/UninstallDialog.kt b/app/src/main/java/com/topjohnwu/magisk/events/dialog/UninstallDialog.kt index 7ca5c42de..7eeb01a69 100644 --- a/app/src/main/java/com/topjohnwu/magisk/events/dialog/UninstallDialog.kt +++ b/app/src/main/java/com/topjohnwu/magisk/events/dialog/UninstallDialog.kt @@ -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() } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt b/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt index cc874e754..274803dcd 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt @@ -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().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() { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt index d57608b4c..8b4e22d72 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt @@ -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() { override val layoutRes = R.layout.fragment_flash_md2 override val viewModel by viewModel { - parametersOf(args.fromBundle(requireArguments())) + parametersOf(FlashFragmentArgs.fromBundle(requireArguments())) } private var defaultOrientation = -1 @@ -78,7 +77,7 @@ class FlashFragment : BaseUIFragment() 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() /* 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) } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt index ca97e0be5..796a8f506 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt @@ -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() diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/install/InstallFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/install/InstallFragment.kt index 53cc4407e..5958aad16 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/install/InstallFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/install/InstallFragment.kt @@ -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= 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") - } } diff --git a/app/src/main/res/layout/fragment_install_md2.xml b/app/src/main/res/layout/fragment_install_md2.xml index 1555928a6..5fc64ea12 100644 --- a/app/src/main/res/layout/fragment_install_md2.xml +++ b/app/src/main/res/layout/fragment_install_md2.xml @@ -25,306 +25,217 @@ app:fitsSystemWindowsInsets="top|bottom" tools:paddingTop="24dp"> - + android:layout_height="wrap_content" + android:clipToPadding="false" + android:orientation="vertical" + android:paddingTop="@dimen/l_50"> - - - - - - - - - - - - -