mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-02-21 11:58:29 +00:00
Cleanup DownloadService
This commit is contained in:
parent
10f991b8d0
commit
084e0a73dc
@ -1,71 +1,63 @@
|
|||||||
package com.topjohnwu.magisk.core.download
|
package com.topjohnwu.magisk.core.download
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Notification
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.PendingIntent.*
|
import android.app.PendingIntent.*
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import androidx.core.net.toFile
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.MutableLiveData
|
import com.topjohnwu.magisk.PhoenixActivity
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.StubApk
|
||||||
import com.topjohnwu.magisk.core.ActivityTracker
|
import com.topjohnwu.magisk.core.ActivityTracker
|
||||||
import com.topjohnwu.magisk.core.base.BaseService
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.intent
|
import com.topjohnwu.magisk.core.intent
|
||||||
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||||
import com.topjohnwu.magisk.di.ServiceLocator
|
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||||
import com.topjohnwu.magisk.ktx.synchronized
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.ktx.copyAndClose
|
||||||
import com.topjohnwu.magisk.view.Notifications.mgr
|
import com.topjohnwu.magisk.ktx.forEach
|
||||||
|
import com.topjohnwu.magisk.ktx.withStreams
|
||||||
|
import com.topjohnwu.magisk.ktx.writeTo
|
||||||
|
import com.topjohnwu.magisk.utils.APKInstall
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.ResponseBody
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
class DownloadService : BaseService() {
|
class DownloadService : NotificationService() {
|
||||||
|
|
||||||
private val hasNotifications get() = notifications.isNotEmpty()
|
|
||||||
private val notifications = HashMap<Int, Notification.Builder>().synchronized()
|
|
||||||
private val job = Job()
|
private val job = Job()
|
||||||
|
|
||||||
val service get() = ServiceLocator.networkService
|
|
||||||
|
|
||||||
// -- Service overrides
|
|
||||||
|
|
||||||
override fun onBind(intent: Intent?): IBinder? = null
|
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||||
intent.getParcelableExtra<Subject>(SUBJECT_KEY)?.let { doDownload(it) }
|
intent.getParcelableExtra<Subject>(SUBJECT_KEY)?.let { download(it) }
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
|
||||||
super.onTaskRemoved(rootIntent)
|
|
||||||
notifications.forEach { mgr.cancel(it.key) }
|
|
||||||
notifications.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Download logic
|
private fun download(subject: Subject) {
|
||||||
|
|
||||||
private fun doDownload(subject: Subject) {
|
|
||||||
update(subject.notifyId)
|
update(subject.notifyId)
|
||||||
val coroutineScope = CoroutineScope(job + Dispatchers.IO)
|
val coroutineScope = CoroutineScope(job + Dispatchers.IO)
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
try {
|
try {
|
||||||
val stream = service.fetchFile(subject.url).toProgressStream(subject)
|
val stream = service.fetchFile(subject.url).toProgressStream(subject)
|
||||||
when (subject) {
|
when (subject) {
|
||||||
is Subject.Manager -> handleAPK(subject, stream)
|
is Subject.App -> handleApp(stream, subject)
|
||||||
is Subject.Module -> stream.toModule(subject.file, assets.open("module_installer.sh"))
|
is Subject.Module -> handleModule(stream, subject.file)
|
||||||
}
|
}
|
||||||
val activity = ActivityTracker.foreground
|
val activity = ActivityTracker.foreground
|
||||||
if (activity != null && subject.autoStart) {
|
if (activity != null && subject.autoStart) {
|
||||||
@ -83,88 +75,88 @@ class DownloadService : BaseService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ResponseBody.toProgressStream(subject: Subject): InputStream {
|
private suspend fun handleApp(stream: InputStream, subject: Subject.App) {
|
||||||
val max = contentLength()
|
fun write(output: OutputStream) {
|
||||||
val total = max.toFloat() / 1048576
|
val external = subject.externalFile.outputStream()
|
||||||
val id = subject.notifyId
|
stream.copyAndClose(TeeOutputStream(external, output))
|
||||||
|
}
|
||||||
|
|
||||||
update(id) { it.setContentTitle(subject.title) }
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
return ProgressInputStream(byteStream()) {
|
private fun handleModule(src: InputStream, file: Uri) {
|
||||||
val progress = it.toFloat() / 1048576
|
val input = ZipInputStream(src.buffered())
|
||||||
update(id) { notification ->
|
val output = ZipOutputStream(file.outputStream().buffered())
|
||||||
if (max > 0) {
|
|
||||||
broadcast(progress / total, subject)
|
withStreams(input, output) { zin, zout ->
|
||||||
notification
|
zout.putNextEntry(ZipEntry("META-INF/"))
|
||||||
.setProgress(max.toInt(), it.toInt(), false)
|
zout.putNextEntry(ZipEntry("META-INF/com/"))
|
||||||
.setContentText("%.2f / %.2f MB".format(progress, total))
|
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
|
||||||
} else {
|
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
|
||||||
broadcast(-1f, subject)
|
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
|
||||||
notification.setContentText("%.2f MB / ??".format(progress))
|
assets.open("module_installer.sh").copyTo(zout)
|
||||||
|
|
||||||
|
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
|
||||||
|
zout.write("#MAGISK\n".toByteArray())
|
||||||
|
|
||||||
|
zin.forEach { entry ->
|
||||||
|
val path = entry.name
|
||||||
|
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
|
||||||
|
zout.putNextEntry(ZipEntry(path))
|
||||||
|
if (!entry.isDirectory) {
|
||||||
|
zin.copyTo(zout)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Notification management
|
private class TeeOutputStream(
|
||||||
|
private val o1: OutputStream,
|
||||||
private fun notifyFail(subject: Subject) = finalNotify(subject.notifyId) {
|
private val o2: OutputStream
|
||||||
broadcast(-2f, subject)
|
) : OutputStream() {
|
||||||
it.setContentText(getString(R.string.download_file_error))
|
override fun write(b: Int) {
|
||||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
o1.write(b)
|
||||||
.setOngoing(false)
|
o2.write(b)
|
||||||
}
|
}
|
||||||
|
override fun write(b: ByteArray?, off: Int, len: Int) {
|
||||||
private fun notifyFinish(subject: Subject) = finalNotify(subject.notifyId) {
|
o1.write(b, off, len)
|
||||||
broadcast(1f, subject)
|
o2.write(b, off, len)
|
||||||
it.setContentTitle(subject.title)
|
}
|
||||||
.setContentText(getString(R.string.download_complete))
|
override fun close() {
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
o1.close()
|
||||||
.setProgress(0, 0, false)
|
o2.close()
|
||||||
.setOngoing(false)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
subject.pendingIntent(this)?.let { intent -> it.setContentIntent(intent) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
|
|
||||||
val notification = remove(id)?.also(editor) ?: return -1
|
|
||||||
val newId = Notifications.nextId()
|
|
||||||
mgr.notify(newId, notification.build())
|
|
||||||
return newId
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun create() = Notifications.progress(this, "")
|
|
||||||
|
|
||||||
fun update(id: Int, editor: (Notification.Builder) -> Unit = {}) {
|
|
||||||
val wasEmpty = !hasNotifications
|
|
||||||
val notification = notifications.getOrPut(id, ::create).also(editor)
|
|
||||||
if (wasEmpty)
|
|
||||||
updateForeground()
|
|
||||||
else
|
|
||||||
mgr.notify(id, notification.build())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun remove(id: Int): Notification.Builder? {
|
|
||||||
val n = notifications.remove(id)?.also { updateForeground() }
|
|
||||||
mgr.cancel(id)
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateForeground() {
|
|
||||||
if (hasNotifications) {
|
|
||||||
val (id, notification) = notifications.entries.first()
|
|
||||||
startForeground(id, notification.build())
|
|
||||||
} else {
|
|
||||||
stopForeground(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val SUBJECT_KEY = "download_subject"
|
private const val SUBJECT_KEY = "subject"
|
||||||
private const val REQUEST_CODE = 1
|
private const val REQUEST_CODE = 1
|
||||||
|
|
||||||
private val progressBroadcast = MutableLiveData<Pair<Float, Subject>?>()
|
|
||||||
|
|
||||||
fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {
|
fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {
|
||||||
progressBroadcast.value = null
|
progressBroadcast.value = null
|
||||||
progressBroadcast.observe(owner) {
|
progressBroadcast.observe(owner) {
|
||||||
@ -173,10 +165,6 @@ class DownloadService : BaseService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun broadcast(progress: Float, subject: Subject) {
|
|
||||||
progressBroadcast.postValue(progress to subject)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun intent(context: Context, subject: Subject) =
|
private fun intent(context: Context, subject: Subject) =
|
||||||
context.intent<DownloadService>().putExtra(SUBJECT_KEY, subject)
|
context.intent<DownloadService>().putExtra(SUBJECT_KEY, subject)
|
||||||
|
|
||||||
@ -200,5 +188,4 @@ class DownloadService : BaseService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,113 @@
|
|||||||
|
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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() {
|
|||||||
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.*
|
|||||||
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(
|
|||||||
}.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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user