Always update stub APK when upgrade

This commit is contained in:
topjohnwu 2024-03-14 14:31:02 -07:00
parent 050a073771
commit c951b208a1
3 changed files with 30 additions and 67 deletions

View File

@ -13,29 +13,26 @@ import android.os.Bundle
import androidx.collection.SparseArrayCompat import androidx.collection.SparseArrayCompat
import androidx.collection.isNotEmpty import androidx.collection.isNotEmpty
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.net.toFile
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.ActivityTracker import com.topjohnwu.magisk.core.ActivityTracker
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.JobService import com.topjohnwu.magisk.core.JobService
import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.cmp import com.topjohnwu.magisk.core.cmp
import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.intent import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.isRunningAsStub import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.copyAndClose import com.topjohnwu.magisk.core.ktx.cachedFile
import com.topjohnwu.magisk.core.ktx.copyAll import com.topjohnwu.magisk.core.ktx.copyAll
import com.topjohnwu.magisk.core.ktx.copyAndClose
import com.topjohnwu.magisk.core.ktx.forEach import com.topjohnwu.magisk.core.ktx.forEach
import com.topjohnwu.magisk.core.ktx.selfLaunchIntent
import com.topjohnwu.magisk.core.ktx.set import com.topjohnwu.magisk.core.ktx.set
import com.topjohnwu.magisk.core.ktx.withStreams import com.topjohnwu.magisk.core.ktx.withStreams
import com.topjohnwu.magisk.core.ktx.writeTo import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.tasks.HideAPK import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.core.utils.ProgressInputStream import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.utils.APKInstall import com.topjohnwu.magisk.utils.APKInstall
@ -46,11 +43,9 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.ResponseBody import okhttp3.ResponseBody
import timber.log.Timber import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.util.Properties
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipFile import java.util.zip.ZipFile
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
@ -169,7 +164,7 @@ class DownloadEngine(
when (subject) { when (subject) {
is Subject.App -> handleApp(stream, subject) is Subject.App -> handleApp(stream, subject)
is Subject.Module -> handleModule(stream, subject.file) is Subject.Module -> handleModule(stream, subject.file)
is Subject.Test -> stream.copyAndClose(subject.file.outputStream()) else -> stream.copyAndClose(subject.file.outputStream())
} }
val activity = ActivityTracker.foreground val activity = ActivityTracker.foreground
if (activity != null && subject.autoLaunch) { if (activity != null && subject.autoLaunch) {
@ -178,7 +173,6 @@ class DownloadEngine(
} else { } else {
notifyFinish(subject) notifyFinish(subject)
} }
subject.postDownload?.invoke()
} catch (e: IOException) { } catch (e: IOException) {
Timber.e(e) Timber.e(e)
notifyFail(subject) notifyFail(subject)
@ -271,23 +265,14 @@ class DownloadEngine(
} }
private suspend fun handleApp(stream: InputStream, subject: Subject.App) { private suspend fun handleApp(stream: InputStream, subject: Subject.App) {
suspend fun writeTee(output: OutputStream) { val external = subject.file.outputStream()
val uri = MediaStoreUtils.getFile("${subject.title}.apk").uri
val external = uri.outputStream()
stream.copyAndClose(TeeOutputStream(external, output))
}
if (isRunningAsStub) { if (isRunningAsStub) {
val updateApk = StubApk.update(context) val updateApk = StubApk.update(context)
try { try {
// Download full APK to stub update path // Download full APK to stub update path
writeTee(updateApk.outputStream()) stream.copyAndClose(TeeOutputStream(external, updateApk.outputStream()))
val zf = ZipFile(updateApk)
val prop = Properties()
prop.load(ByteArrayInputStream(zf.comment.toByteArray()))
val stubVersion = prop.getProperty("stubVersion").toIntOrNull() ?: -1
if (Info.stub!!.version < stubVersion) {
// Also upgrade stub // Also upgrade stub
notifyUpdate(subject.notifyId) { notifyUpdate(subject.notifyId) {
it.setProgress(0, 0, true) it.setProgress(0, 0, true)
@ -296,7 +281,9 @@ class DownloadEngine(
} }
// Extract stub // Extract stub
val apk = subject.file.toFile() val zf = ZipFile(updateApk)
val apk = context.cachedFile("stub.apk")
apk.delete()
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk) zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
zf.close() zf.close()
@ -304,17 +291,6 @@ class DownloadEngine(
subject.intent = HideAPK.upgrade(context, apk) subject.intent = HideAPK.upgrade(context, apk)
?: throw IOException("HideAPK patch error") ?: throw IOException("HideAPK patch error")
apk.delete() apk.delete()
} else {
ActivityTracker.foreground?.let {
// Relaunch the process if we are foreground
StubApk.restartProcess(it)
} ?: run {
// Or else kill the current process after posting notification
subject.intent = context.selfLaunchIntent()
subject.postDownload = { Runtime.getRuntime().exit(0) }
}
return
}
} catch (e: Exception) { } catch (e: Exception) {
// If any error occurred, do not let stub load the new APK // If any error occurred, do not let stub load the new APK
updateApk.delete() updateApk.delete()
@ -322,14 +298,14 @@ class DownloadEngine(
} }
} else { } else {
val session = APKInstall.startSession(context) val session = APKInstall.startSession(context)
writeTee(session.openStream(context)) stream.copyAndClose(TeeOutputStream(external, session.openStream(context)))
subject.intent = session.waitIntent() subject.intent = session.waitIntent()
} }
} }
private suspend fun handleModule(src: InputStream, file: Uri) { private suspend fun handleModule(src: InputStream, file: Uri) {
val input = ZipInputStream(src.buffered()) val input = ZipInputStream(src)
val output = ZipOutputStream(file.outputStream().buffered()) val output = ZipOutputStream(file.outputStream())
withStreams(input, output) { zin, zout -> withStreams(input, output) { zin, zout ->
zout.putNextEntry(ZipEntry("META-INF/")) zout.putNextEntry(ZipEntry("META-INF/"))
@ -337,7 +313,7 @@ class DownloadEngine(
zout.putNextEntry(ZipEntry("META-INF/com/google/")) zout.putNextEntry(ZipEntry("META-INF/com/google/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/")) zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary")) zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
context.assets.open("module_installer.sh").copyAll(zout) context.assets.open("module_installer.sh").use { it.copyAll(zout) }
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script")) zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
zout.write("#MAGISK\n".toByteArray()) zout.write("#MAGISK\n".toByteArray())

View File

@ -20,11 +20,6 @@ import kotlinx.parcelize.Parcelize
import java.io.File import java.io.File
import java.util.UUID import java.util.UUID
enum class Action {
Flash,
Download
}
sealed class Subject : Parcelable { sealed class Subject : Parcelable {
abstract val url: String abstract val url: String
@ -32,19 +27,17 @@ sealed class Subject : Parcelable {
abstract val title: String abstract val title: String
abstract val notifyId: Int abstract val notifyId: Int
open val autoLaunch: Boolean get() = true open val autoLaunch: Boolean get() = true
open val postDownload: (() -> Unit)? get() = null
open fun pendingIntent(context: Context): PendingIntent? = null open fun pendingIntent(context: Context): PendingIntent? = null
@Parcelize @Parcelize
class Module( class Module(
val module: OnlineModule, private val module: OnlineModule,
val action: Action, override val autoLaunch: Boolean,
override val notifyId: Int = Notifications.nextId() override val notifyId: Int = Notifications.nextId()
) : Subject() { ) : Subject() {
override val url: String get() = module.zipUrl override val url: String get() = module.zipUrl
override val title: String get() = module.downloadFilename override val title: String get() = module.downloadFilename
override val autoLaunch: Boolean get() = action == Action.Flash
@IgnoredOnParcel @IgnoredOnParcel
override val file by lazy { override val file by lazy {
@ -65,12 +58,9 @@ sealed class Subject : Parcelable {
@IgnoredOnParcel @IgnoredOnParcel
override val file by lazy { override val file by lazy {
AppContext.cachedFile("manager.apk").apply { delete() }.toUri() MediaStoreUtils.getFile("${title}.apk").uri
} }
@IgnoredOnParcel
override var postDownload: (() -> Unit)? = null
@IgnoredOnParcel @IgnoredOnParcel
var intent: Intent? = null var intent: Intent? = null
override fun pendingIntent(context: Context) = intent?.toPending(context) override fun pendingIntent(context: Context) = intent?.toPending(context)

View File

@ -2,7 +2,6 @@ package com.topjohnwu.magisk.dialog
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.download.Action
import com.topjohnwu.magisk.core.download.DownloadEngine import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.core.download.Subject import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.core.model.module.OnlineModule import com.topjohnwu.magisk.core.model.module.OnlineModule
@ -22,9 +21,7 @@ class OnlineModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog
dialog.apply { dialog.apply {
fun download(install: Boolean) { fun download(install: Boolean) {
val action = if (install) Action.Flash else Action.Download DownloadEngine.startWithActivity(activity, Subject.Module(item, install))
val subject = Subject.Module(item, action)
DownloadEngine.startWithActivity(activity, subject)
} }
val title = context.getString(R.string.repo_install_title, val title = context.getString(R.string.repo_install_title,