Commit 084e0a73 authored by topjohnwu's avatar topjohnwu

Cleanup DownloadService

parent 10f991b8
package com.topjohnwu.magisk.core.download
import androidx.core.net.toFile
import com.topjohnwu.magisk.PhoenixActivity
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.ktx.copyAndClose
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.utils.APKInstall
import java.io.File
import java.io.InputStream
import java.io.OutputStream
private class TeeOutputStream(
private val o1: OutputStream,
private val o2: OutputStream
) : OutputStream() {
override fun write(b: Int) {
o1.write(b)
o2.write(b)
}
override fun write(b: ByteArray?, off: Int, len: Int) {
o1.write(b, off, len)
o2.write(b, off, len)
}
override fun close() {
o1.close()
o2.close()
}
}
suspend fun DownloadService.handleAPK(subject: Subject.Manager, stream: InputStream) {
fun write(output: OutputStream) {
val external = subject.externalFile.outputStream()
stream.copyAndClose(TeeOutputStream(external, output))
}
if (isRunningAsStub) {
val apk = subject.file.toFile()
val id = subject.notifyId
write(StubApk.update(this).outputStream())
if (Info.stub!!.version < subject.stub.versionCode) {
// Also upgrade stub
update(id) {
it.setProgress(0, 0, true)
.setContentTitle(getString(R.string.hide_app_title))
.setContentText("")
}
service.fetchFile(subject.stub.link).byteStream().writeTo(apk)
val patched = File(apk.parent, "patched.apk")
HideAPK.patch(this, apk, patched, packageName, applicationInfo.nonLocalizedLabel)
apk.delete()
patched.renameTo(apk)
} else {
val clz = Info.stub!!.classToComponent["PHOENIX"]!!
PhoenixActivity.rebirth(this, clz)
return
}
}
val receiver = APKInstall.register(this, null, null)
write(APKInstall.openStream(this, false))
subject.intent = receiver.waitIntent()
}
package com.topjohnwu.magisk.core.download
import android.net.Uri
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.ktx.forEach
import com.topjohnwu.magisk.ktx.withStreams
import java.io.InputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
fun InputStream.toModule(file: Uri, installer: InputStream) {
val input = ZipInputStream(buffered())
val output = ZipOutputStream(file.outputStream().buffered())
withStreams(input, output) { zin, zout ->
zout.putNextEntry(ZipEntry("META-INF/"))
zout.putNextEntry(ZipEntry("META-INF/com/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
installer.copyTo(zout)
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
zout.write("#MAGISK\n".toByteArray(charset("UTF-8")))
zin.forEach { entry ->
val path = entry.name
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
zout.putNextEntry(ZipEntry(path))
if (!entry.isDirectory) {
zin.copyTo(zout)
}
}
}
}
}
package com.topjohnwu.magisk.core.download
import android.app.Notification
import android.content.Intent
import android.os.IBinder
import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.ktx.synchronized
import com.topjohnwu.magisk.view.Notifications
import okhttp3.ResponseBody
import java.io.InputStream
open class NotificationService : BaseService() {
private val notifications = HashMap<Int, Notification.Builder>().synchronized()
protected val hasNotifications get() = notifications.isNotEmpty()
protected val service get() = ServiceLocator.networkService
override fun onBind(intent: Intent?): IBinder? = null
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
notifications.forEach { Notifications.mgr.cancel(it.key) }
notifications.clear()
}
protected fun ResponseBody.toProgressStream(subject: Subject): InputStream {
val max = contentLength()
val total = max.toFloat() / 1048576
val id = subject.notifyId
update(id) { it.setContentTitle(subject.title) }
return ProgressInputStream(byteStream()) {
val progress = it.toFloat() / 1048576
update(id) { notification ->
if (max > 0) {
broadcast(progress / total, subject)
notification
.setProgress(max.toInt(), it.toInt(), false)
.setContentText("%.2f / %.2f MB".format(progress, total))
} else {
broadcast(-1f, subject)
notification.setContentText("%.2f MB / ??".format(progress))
}
}
}
}
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
val notification = remove(id)?.also(editor) ?: return -1
val newId = Notifications.nextId()
Notifications.mgr.notify(newId, notification.build())
return newId
}
protected fun notifyFail(subject: Subject) = finalNotify(subject.notifyId) {
broadcast(-2f, subject)
it.setContentText(getString(R.string.download_file_error))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setOngoing(false)
}
protected fun notifyFinish(subject: Subject) = finalNotify(subject.notifyId) {
broadcast(1f, subject)
it.setContentTitle(subject.title)
.setContentText(getString(R.string.download_complete))
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setProgress(0, 0, false)
.setOngoing(false)
.setAutoCancel(true)
subject.pendingIntent(this)?.let { intent -> it.setContentIntent(intent) }
}
private fun create() = Notifications.progress(this, "")
private fun updateForeground() {
if (hasNotifications) {
val (id, notification) = notifications.entries.first()
startForeground(id, notification.build())
} else {
stopForeground(false)
}
}
protected fun update(id: Int, editor: (Notification.Builder) -> Unit = {}) {
val wasEmpty = !hasNotifications
val notification = notifications.getOrPut(id, ::create).also(editor)
if (wasEmpty)
updateForeground()
else
Notifications.mgr.notify(id, notification.build())
}
protected fun remove(id: Int): Notification.Builder? {
val n = notifications.remove(id)?.also { updateForeground() }
Notifications.mgr.cancel(id)
return n
}
companion object {
@JvmStatic
protected val progressBroadcast = MutableLiveData<Pair<Float, Subject>?>()
private fun broadcast(progress: Float, subject: Subject) {
progressBroadcast.postValue(progress to subject)
}
}
}
...@@ -56,7 +56,7 @@ sealed class Subject : Parcelable { ...@@ -56,7 +56,7 @@ sealed class Subject : Parcelable {
} }
@Parcelize @Parcelize
class Manager( class App(
private val json: MagiskJson = Info.remote.magisk, private val json: MagiskJson = Info.remote.magisk,
val stub: StubJson = Info.remote.stub, val stub: StubJson = Info.remote.stub,
override val notifyId: Int = Notifications.nextId() override val notifyId: Int = Notifications.nextId()
......
...@@ -29,7 +29,7 @@ class ManagerInstallDialog : MarkDownDialog() { ...@@ -29,7 +29,7 @@ class ManagerInstallDialog : MarkDownDialog() {
setCancelable(true) setCancelable(true)
setButton(MagiskDialog.ButtonType.POSITIVE) { setButton(MagiskDialog.ButtonType.POSITIVE) {
text = R.string.install text = R.string.install
onClick { DownloadService.start(context, Subject.Manager()) } onClick { DownloadService.start(context, Subject.App()) }
} }
setButton(MagiskDialog.ButtonType.NEGATIVE) { setButton(MagiskDialog.ButtonType.NEGATIVE) {
text = android.R.string.cancel text = android.R.string.cancel
......
...@@ -10,7 +10,7 @@ import com.topjohnwu.magisk.arch.* ...@@ -10,7 +10,7 @@ import com.topjohnwu.magisk.arch.*
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.Subject import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.core.download.Subject.Manager import com.topjohnwu.magisk.core.download.Subject.App
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.databinding.itemBindingOf import com.topjohnwu.magisk.databinding.itemBindingOf
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
...@@ -112,7 +112,7 @@ class HomeViewModel( ...@@ -112,7 +112,7 @@ class HomeViewModel(
}.publish() }.publish()
fun onProgressUpdate(progress: Float, subject: Subject) { fun onProgressUpdate(progress: Float, subject: Subject) {
if (subject is Manager) if (subject is App)
stateManagerProgress = progress.times(100f).roundToInt() stateManagerProgress = progress.times(100f).roundToInt()
} }
......
...@@ -48,7 +48,7 @@ object Notifications { ...@@ -48,7 +48,7 @@ object Notifications {
} }
fun managerUpdate(context: Context) { fun managerUpdate(context: Context) {
val intent = DownloadService.getPendingIntent(context, Subject.Manager()) val intent = DownloadService.getPendingIntent(context, Subject.App())
val builder = updateBuilder(context) val builder = updateBuilder(context)
.setContentTitle(context.getString(R.string.magisk_update_title)) .setContentTitle(context.getString(R.string.magisk_update_title))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment