Commit abc54571 authored by topjohnwu's avatar topjohnwu

Cleanup DownloadService

parent 4b238a9c
...@@ -2,17 +2,20 @@ package com.topjohnwu.magisk.core.download ...@@ -2,17 +2,20 @@ package com.topjohnwu.magisk.core.download
import android.app.Notification import android.app.Notification
import android.content.Intent import android.content.Intent
import androidx.lifecycle.LiveData import android.os.IBinder
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.core.ForegroundTracker import com.topjohnwu.magisk.core.ForegroundTracker
import com.topjohnwu.magisk.core.download.DownloadSubject.Magisk import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.core.download.DownloadSubject.Module
import com.topjohnwu.magisk.core.utils.ProgressInputStream import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.data.network.GithubRawServices import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.ktx.checkSum import com.topjohnwu.magisk.ktx.checkSum
import com.topjohnwu.magisk.ktx.writeTo import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.ResponseBody import okhttp3.ResponseBody
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
...@@ -20,81 +23,105 @@ import org.koin.core.KoinComponent ...@@ -20,81 +23,105 @@ import org.koin.core.KoinComponent
import timber.log.Timber import timber.log.Timber
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.util.*
import kotlin.collections.HashMap
import kotlin.random.Random.Default.nextInt
abstract class RemoteFileService : NotificationService() { abstract class BaseDownloadService : BaseService(), KoinComponent {
private val hasNotifications get() = notifications.isNotEmpty()
private val notifications = Collections.synchronizedMap(HashMap<Int, Notification.Builder>())
private val coroutineScope = CoroutineScope(Dispatchers.IO)
val service: GithubRawServices by inject() val service: GithubRawServices by inject()
// -- 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<DownloadSubject>(ARG_URL)?.let { intent?.getParcelableExtra<DownloadSubject>(ARG_URL)?.let { subject ->
update(it.hashCode()) update(subject.notifyID())
coroutineScope.launch { coroutineScope.launch {
try { try {
start(it) subject.startDownload()
} catch (e: IOException) { } catch (e: IOException) {
Timber.e(e) Timber.e(e)
failNotify(it) notifyFail(subject)
} }
} }
} }
return START_REDELIVER_INTENT return START_REDELIVER_INTENT
} }
override fun createNotification() = Notifications.progress(this, "") override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
notifications.forEach { cancel(it.key) }
notifications.clear()
}
// --- override fun onDestroy() {
super.onDestroy()
coroutineScope.cancel()
}
// -- Download logic
private suspend fun start(subject: DownloadSubject) { private suspend fun DownloadSubject.startDownload() {
if (subject !is Magisk || val skip = this is DownloadSubject.Magisk && file.exists() && file.checkSum("MD5", magisk.md5)
!subject.file.exists() || if (!skip) {
!subject.file.checkSum("MD5", subject.magisk.md5)) { val stream = service.fetchFile(url).toProgressStream(this)
val stream = service.fetchFile(subject.url).toProgressStream(subject) when (this) {
when (subject) { is DownloadSubject.Module -> // Download and process on-the-fly
is Module -> stream.toModule(file, service.fetchInstaller().byteStream())
stream.toModule(subject.file, service.fetchInstaller().byteStream())
else -> else ->
stream.writeTo(subject.file) stream.writeTo(file)
} }
} }
val newId = finishNotify(subject) val newId = notifyFinish(this)
if (ForegroundTracker.hasForeground) { if (ForegroundTracker.hasForeground)
onFinished(subject, newId) onFinish(this, newId)
} if (!hasNotifications)
stopSelf()
} }
private fun ResponseBody.toProgressStream(subject: DownloadSubject): InputStream { private fun ResponseBody.toProgressStream(subject: DownloadSubject): InputStream {
val maxRaw = contentLength() val max = contentLength()
val max = maxRaw / 1_000_000f val total = max.toFloat() / 1048576
val id = subject.hashCode() val id = subject.notifyID()
update(id) { it.setContentTitle(subject.title) } update(id) { it.setContentTitle(subject.title) }
return ProgressInputStream(byteStream()) { return ProgressInputStream(byteStream()) {
val progress = it / 1_000_000f val progress = it.toFloat() / 1048576
update(id) { notification -> update(id) { notification ->
if (maxRaw > 0) { if (max > 0) {
send(progress / max, subject) broadcast(progress / total, subject)
notification notification
.setProgress(maxRaw.toInt(), it.toInt(), false) .setProgress(max.toInt(), it.toInt(), false)
.setContentText("%.2f / %.2f MB".format(progress, max)) .setContentText("%.2f / %.2f MB".format(progress, total))
} else { } else {
send(-1f, subject) broadcast(-1f, subject)
notification.setContentText("%.2f MB / ??".format(progress)) notification.setContentText("%.2f MB / ??".format(progress))
} }
} }
} }
} }
private fun failNotify(subject: DownloadSubject) = lastNotify(subject.hashCode()) { // --- Notification managements
send(0f, subject)
fun DownloadSubject.notifyID() = hashCode()
private fun notifyFail(subject: DownloadSubject) = lastNotify(subject.notifyID()) {
broadcast(-1f, subject)
it.setContentText(getString(R.string.download_file_error)) it.setContentText(getString(R.string.download_file_error))
.setSmallIcon(android.R.drawable.stat_notify_error) .setSmallIcon(android.R.drawable.stat_notify_error)
.setOngoing(false) .setOngoing(false)
} }
private fun finishNotify(subject: DownloadSubject) = lastNotify(subject.hashCode()) { private fun notifyFinish(subject: DownloadSubject) = lastNotify(subject.notifyID()) {
send(1f, subject) broadcast(1f, subject)
it.addActions(subject) it.setIntent(subject)
.setContentText(getString(R.string.download_complete)) .setContentText(getString(R.string.download_complete))
.setSmallIcon(android.R.drawable.stat_sys_download_done) .setSmallIcon(android.R.drawable.stat_sys_download_done)
.setProgress(0, 0, false) .setProgress(0, 0, false)
...@@ -102,27 +129,73 @@ abstract class RemoteFileService : NotificationService() { ...@@ -102,27 +129,73 @@ abstract class RemoteFileService : NotificationService() {
.setAutoCancel(true) .setAutoCancel(true)
} }
// --- 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
notify(id, notification.build())
}
private fun lastNotify(
id: Int,
editor: (Notification.Builder) -> Notification.Builder? = { null }
) : Int {
val notification = remove(id)?.run(editor) ?: return -1
val newId: Int = nextInt()
notify(newId, notification.build())
return newId
}
private fun remove(id: Int) = notifications.remove(id)?.also {
updateForeground()
cancel(id)
}
private fun notify(id: Int, notification: Notification) {
Notifications.mgr.notify(id, notification)
}
protected fun cancel(id: Int) {
Notifications.mgr.cancel(id)
}
private fun updateForeground() {
if (hasNotifications) {
val (id, notification) = notifications.entries.first()
startForeground(id, notification.build())
} else {
stopForeground(false)
}
}
protected abstract suspend fun onFinished(subject: DownloadSubject, id: Int) // --- Implement custom logic
protected abstract fun Notification.Builder.addActions(subject: DownloadSubject) protected abstract suspend fun onFinish(subject: DownloadSubject, id: Int)
protected abstract fun Notification.Builder.setIntent(subject: DownloadSubject)
: Notification.Builder : Notification.Builder
// ---
companion object : KoinComponent { companion object : KoinComponent {
const val ARG_URL = "arg_url" const val ARG_URL = "arg_url"
private val internalProgressBroadcast = MutableLiveData<Pair<Float, DownloadSubject>>() private val progressBroadcast = MutableLiveData<Pair<Float, DownloadSubject>>()
val progressBroadcast: LiveData<Pair<Float, DownloadSubject>> get() = internalProgressBroadcast
fun send(progress: Float, subject: DownloadSubject) { fun observeProgress(owner: LifecycleOwner, callback: (Float, DownloadSubject) -> Unit) {
internalProgressBroadcast.postValue(progress to subject) progressBroadcast.value = null
progressBroadcast.observe(owner) {
val (progress, subject) = it ?: return@observe
callback(progress, subject)
}
} }
fun reset() { private fun broadcast(progress: Float, subject: DownloadSubject) {
internalProgressBroadcast.value = null progressBroadcast.postValue(progress to subject)
} }
} }
} }
...@@ -6,139 +6,84 @@ import android.app.PendingIntent ...@@ -6,139 +6,84 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.webkit.MimeTypeMap
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.download.Configuration.* import com.topjohnwu.magisk.core.download.Configuration.*
import com.topjohnwu.magisk.core.download.Configuration.Flash.Secondary import com.topjohnwu.magisk.core.download.Configuration.Flash.Secondary
import com.topjohnwu.magisk.core.download.DownloadSubject.* import com.topjohnwu.magisk.core.download.DownloadSubject.*
import com.topjohnwu.magisk.core.intent import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.tasks.EnvFixTask import com.topjohnwu.magisk.core.tasks.EnvFixTask
import com.topjohnwu.magisk.ktx.chooser
import com.topjohnwu.magisk.ktx.exists
import com.topjohnwu.magisk.ktx.provide
import com.topjohnwu.magisk.ui.flash.FlashFragment import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.APKInstall import com.topjohnwu.magisk.utils.APKInstall
import org.koin.core.get
import java.io.File
import kotlin.random.Random.Default.nextInt import kotlin.random.Random.Default.nextInt
/* More of a facade for [RemoteFileService], but whatever... */
@SuppressLint("Registered") @SuppressLint("Registered")
open class DownloadService : RemoteFileService() { open class DownloadService : BaseDownloadService() {
private val context get() = this private val context get() = this
private val File.type
get() = MimeTypeMap.getSingleton() override suspend fun onFinish(subject: DownloadSubject, id: Int) = when (subject) {
.getMimeTypeFromExtension(extension) is Magisk -> subject.onFinish(id)
?: "resource/folder" is Module -> subject.onFinish(id)
is Manager -> subject.onFinish(id)
override suspend fun onFinished(subject: DownloadSubject, id: Int) = when (subject) {
is Magisk -> onFinished(subject, id)
is Module -> onFinished(subject, id)
is Manager -> onFinished(subject, id)
} }
private suspend fun onFinished( private suspend fun Magisk.onFinish(id: Int) = when (val conf = configuration) {
subject: Magisk, Uninstall -> FlashFragment.uninstall(file, id)
id: Int
) = when (val conf = subject.configuration) {
Uninstall -> FlashFragment.uninstall(subject.file, id)
EnvFix -> { EnvFix -> {
remove(id) cancel(id)
EnvFixTask(subject.file).exec() EnvFixTask(file).exec()
Unit Unit
} }
is Patch -> FlashFragment.patch(subject.file, conf.fileUri, id) is Patch -> FlashFragment.patch(file, conf.fileUri, id)
is Flash -> FlashFragment.flash(subject.file, conf is Secondary, id) is Flash -> FlashFragment.flash(file, conf is Secondary, id)
else -> Unit else -> Unit
} }
private fun onFinished( private fun Module.onFinish(id: Int) = when (configuration) {
subject: Module, is Flash -> FlashFragment.install(file, id)
id: Int
) = when (subject.configuration) {
is Flash -> FlashFragment.install(subject.file, id)
else -> Unit else -> Unit
} }
private suspend fun onFinished( private suspend fun Manager.onFinish(id: Int) {
subject: Manager, handleAPK(this)
id: Int cancel(id)
) {
handleAPK(subject)
remove(id)
when (subject.configuration) {
is APK.Upgrade -> APKInstall.install(this, subject.file)
is APK.Restore -> Unit
}
} }
// --- // --- Customize finish notification
override fun Notification.Builder.addActions(subject: DownloadSubject) override fun Notification.Builder.setIntent(subject: DownloadSubject)
= when (subject) { = when (subject) {
is Magisk -> addActions(subject) is Magisk -> setIntent(subject)
is Module -> addActions(subject) is Module -> setIntent(subject)
is Manager -> addActions(subject) is Manager -> setIntent(subject)
} }
private fun Notification.Builder.addActions(subject: Magisk) private fun Notification.Builder.setIntent(subject: Magisk)
= when (val conf = subject.configuration) { = when (val conf = subject.configuration) {
Download -> apply {
fileIntent(subject.file.parentFile!!)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_parent, it.chooser()) }
fileIntent(subject.file)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_self, it.chooser()) }
}
Uninstall -> setContentIntent(FlashFragment.uninstallIntent(context, subject.file)) Uninstall -> setContentIntent(FlashFragment.uninstallIntent(context, subject.file))
is Flash -> setContentIntent(FlashFragment.flashIntent(context, subject.file, conf is Secondary)) is Flash -> setContentIntent(FlashFragment.flashIntent(context, subject.file, conf is Secondary))
is Patch -> setContentIntent(FlashFragment.patchIntent(context, subject.file, conf.fileUri)) is Patch -> setContentIntent(FlashFragment.patchIntent(context, subject.file, conf.fileUri))
else -> this else -> setContentIntent(Intent())
} }
private fun Notification.Builder.addActions(subject: Module) private fun Notification.Builder.setIntent(subject: Module)
= when (subject.configuration) { = when (subject.configuration) {
Download -> this.apply {
fileIntent(subject.file.parentFile!!)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_parent, it.chooser()) }
fileIntent(subject.file)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_self, it.chooser()) }
}
is Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file)) is Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file))
else -> this else -> setContentIntent(Intent())
} }
private fun Notification.Builder.addActions(subject: Manager) private fun Notification.Builder.setIntent(subject: Manager)
= when (subject.configuration) { = when (subject.configuration) {
APK.Upgrade -> setContentIntent(APKInstall.installIntent(context, subject.file)) APK.Upgrade -> setContentIntent(APKInstall.installIntent(context, subject.file))
else -> this else -> setContentIntent(Intent())
} }
@Suppress("ReplaceSingleLineLet")
private fun Notification.Builder.setContentIntent(intent: Intent) = private fun Notification.Builder.setContentIntent(intent: Intent) =
setContentIntent( setContentIntent(
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT) PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
) )
@Suppress("ReplaceSingleLineLet")
private fun Notification.Builder.addAction(icon: Int, title: Int, intent: Intent) =
addAction(icon, getString(title),
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
)
// --- // ---
private fun fileIntent(file: File): Intent {
return Intent(Intent.ACTION_VIEW)
.setDataAndType(file.provide(this), file.type)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
class Builder { class Builder {
lateinit var subject: DownloadSubject lateinit var subject: DownloadSubject
} }
...@@ -150,7 +95,7 @@ open class DownloadService : RemoteFileService() { ...@@ -150,7 +95,7 @@ open class DownloadService : RemoteFileService() {
val builder = Builder().apply(argBuilder) val builder = Builder().apply(argBuilder)
val intent = app.intent<DownloadService>().putExtra(ARG_URL, builder.subject) val intent = app.intent<DownloadService>().putExtra(ARG_URL, builder.subject)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= 26) {
app.startForegroundService(intent) app.startForegroundService(intent)
} else { } else {
app.startService(intent) app.startService(intent)
......
...@@ -12,10 +12,11 @@ import com.topjohnwu.magisk.core.intent ...@@ -12,10 +12,11 @@ import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.isRunningAsStub import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.utils.PatchAPK import com.topjohnwu.magisk.core.utils.PatchAPK
import com.topjohnwu.magisk.ktx.writeTo import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import java.io.File import java.io.File
private fun RemoteFileService.patch(apk: File, id: Int) { private fun DownloadService.patch(apk: File, id: Int) {
if (packageName == BuildConfig.APPLICATION_ID) if (packageName == BuildConfig.APPLICATION_ID)
return return
...@@ -31,7 +32,7 @@ private fun RemoteFileService.patch(apk: File, id: Int) { ...@@ -31,7 +32,7 @@ private fun RemoteFileService.patch(apk: File, id: Int) {
patched.renameTo(apk) patched.renameTo(apk)
} }
private suspend fun RemoteFileService.upgrade(apk: File, id: Int) { private suspend fun DownloadService.upgrade(apk: File, id: Int) {
if (isRunningAsStub) { if (isRunningAsStub) {
// Move to upgrade location // Move to upgrade location
apk.copyTo(DynAPK.update(this), overwrite = true) apk.copyTo(DynAPK.update(this), overwrite = true)
...@@ -49,9 +50,10 @@ private suspend fun RemoteFileService.upgrade(apk: File, id: Int) { ...@@ -49,9 +50,10 @@ private suspend fun RemoteFileService.upgrade(apk: File, id: Int) {
} else { } else {
patch(apk, id) patch(apk, id)
} }
APKInstall.install(this, apk)
} }
private fun RemoteFileService.restore(apk: File, id: Int) { private fun DownloadService.restore(apk: File, id: Int) {
update(id) { update(id) {
it.setProgress(0, 0, true) it.setProgress(0, 0, true)
.setProgress(0, 0, true) .setProgress(0, 0, true)
...@@ -64,8 +66,8 @@ private fun RemoteFileService.restore(apk: File, id: Int) { ...@@ -64,8 +66,8 @@ private fun RemoteFileService.restore(apk: File, id: Int) {
Shell.su("pm install $apk && pm uninstall $packageName").exec() Shell.su("pm install $apk && pm uninstall $packageName").exec()
} }
suspend fun RemoteFileService.handleAPK(subject: DownloadSubject.Manager) = suspend fun DownloadService.handleAPK(subject: DownloadSubject.Manager) =
when (subject.configuration) { when (subject.configuration) {
is Upgrade -> upgrade(subject.file, subject.hashCode()) is Upgrade -> upgrade(subject.file, subject.notifyID())
is Restore -> restore(subject.file, subject.hashCode()) is Restore -> restore(subject.file, subject.notifyID())
} }
package com.topjohnwu.magisk.core.download
import android.app.Notification
import android.content.Intent
import android.os.IBinder
import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import org.koin.core.KoinComponent
import java.util.*
import kotlin.collections.HashMap
import kotlin.random.Random.Default.nextInt
abstract class NotificationService : BaseService(), KoinComponent {
private val hasNotifications get() = notifications.isNotEmpty()
private val notifications = Collections.synchronizedMap(HashMap<Int, Notification.Builder>())
val coroutineScope = CoroutineScope(Dispatchers.IO)
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
notifications.forEach { cancel(it.key) }
notifications.clear()
}
override fun onDestroy() {
super.onDestroy()
coroutineScope.cancel()
}
abstract fun createNotification(): Notification.Builder
// --
fun update(
id: Int,
body: (Notification.Builder) -> Unit = {}
) {
val wasEmpty = notifications.isEmpty()
val notification = notifications.getOrPut(id, ::createNotification).also(body)
if (wasEmpty)
updateForeground()
else
notify(id, notification.build())
}
protected fun lastNotify(
id: Int,
editBody: (Notification.Builder) -> Notification.Builder? = { null }
) : Int {
val currentNotification = remove(id)?.run(editBody)
var newId = -1
currentNotification?.let {
newId = nextInt(Int.MAX_VALUE)
notify(newId, it.build())
}
if (!hasNotifications) {
stopSelf()
}
return newId
}
protected fun remove(id: Int) = notifications.remove(id).also {
cancel(id)
updateForeground()
}
// ---
private fun notify(id: Int, notification: Notification) {
Notifications.mgr.notify(id, notification)
}
private fun cancel(id: Int) {
Notifications.mgr.cancel(id)
}
private fun updateForeground() {
if (hasNotifications) {
val first = notifications.entries.first()
startForeground(first.key, first.value.build())
} else {
stopForeground(true)
}
}
// --
override fun onBind(p0: Intent?): IBinder? = null
}
package com.topjohnwu.magisk.core.utils package com.topjohnwu.magisk.core.utils
import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.FilterInputStream import java.io.FilterInputStream
import java.io.InputStream import java.io.InputStream
...@@ -16,7 +15,7 @@ class ProgressInputStream( ...@@ -16,7 +15,7 @@ class ProgressInputStream(
val cur = System.currentTimeMillis() val cur = System.currentTimeMillis()
if (cur - lastUpdate > 1000) { if (cur - lastUpdate > 1000) {
lastUpdate = cur lastUpdate = cur
UiThreadHandler.run { progressEmitter(bytesRead) } progressEmitter(bytesRead)
} }
} }
......
...@@ -4,6 +4,7 @@ import android.os.Bundle ...@@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.* import android.view.*
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.core.download.BaseDownloadService
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
import com.topjohnwu.magisk.events.RebootEvent import com.topjohnwu.magisk.events.RebootEvent
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
...@@ -18,6 +19,7 @@ class HomeFragment : BaseUIFragment<HomeViewModel, FragmentHomeMd2Binding>() { ...@@ -18,6 +19,7 @@ class HomeFragment : BaseUIFragment<HomeViewModel, FragmentHomeMd2Binding>() {
super.onStart() super.onStart()
activity.title = resources.getString(R.string.section_home) activity.title = resources.getString(R.string.section_home)
setHasOptionsMenu(true) setHasOptionsMenu(true)
BaseDownloadService.observeProgress(this, viewModel::onProgressUpdate)
} }
override fun onCreateView( override fun onCreateView(
......
...@@ -8,8 +8,8 @@ import com.topjohnwu.magisk.R ...@@ -8,8 +8,8 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.* 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.DownloadSubject
import com.topjohnwu.magisk.core.download.DownloadSubject.Manager import com.topjohnwu.magisk.core.download.DownloadSubject.Manager
import com.topjohnwu.magisk.core.download.RemoteFileService
import com.topjohnwu.magisk.core.model.MagiskJson import com.topjohnwu.magisk.core.model.MagiskJson
import com.topjohnwu.magisk.core.model.ManagerJson import com.topjohnwu.magisk.core.model.ManagerJson
import com.topjohnwu.magisk.data.repository.MagiskRepository import com.topjohnwu.magisk.data.repository.MagiskRepository
...@@ -77,14 +77,6 @@ class HomeViewModel( ...@@ -77,14 +77,6 @@ class HomeViewModel(
private var shownDialog = false private var shownDialog = false
init {
RemoteFileService.progressBroadcast.observeForever {
when (it?.second) {
is Manager -> stateManagerProgress = it.first.times(100f).roundToInt()
}
}
}
override fun refresh() = viewModelScope.launch { override fun refresh() = viewModelScope.launch {
notifyPropertyChanged(BR.showUninstall) notifyPropertyChanged(BR.showUninstall)
repoMagisk.fetchUpdate()?.apply { repoMagisk.fetchUpdate()?.apply {
...@@ -119,6 +111,12 @@ class HomeViewModel( ...@@ -119,6 +111,12 @@ class HomeViewModel(
} }
}.publish() }.publish()
fun onProgressUpdate(progress: Float, subject: DownloadSubject) {
when (subject) {
is Manager -> stateManagerProgress = progress.times(100f).roundToInt()
}
}
fun onLinkPressed(link: String) = OpenInappLinkEvent(link).publish() fun onLinkPressed(link: String) = OpenInappLinkEvent(link).publish()
fun onDeletePressed() = UninstallDialog().publish() fun onDeletePressed() = UninstallDialog().publish()
......
...@@ -4,6 +4,7 @@ import android.content.Intent ...@@ -4,6 +4,7 @@ import android.content.Intent
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.core.download.BaseDownloadService
import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding
import com.topjohnwu.magisk.events.RequestFileEvent import com.topjohnwu.magisk.events.RequestFileEvent
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
...@@ -24,6 +25,7 @@ class InstallFragment : BaseUIFragment<InstallViewModel, FragmentInstallMd2Bindi ...@@ -24,6 +25,7 @@ class InstallFragment : BaseUIFragment<InstallViewModel, FragmentInstallMd2Bindi
// Allow markwon to run in viewmodel scope // Allow markwon to run in viewmodel scope
binding.releaseNotes.tag = viewModel.viewModelScope binding.releaseNotes.tag = viewModel.viewModelScope
BaseDownloadService.observeProgress(this, viewModel::onProgressUpdate)
} }
} }
...@@ -11,7 +11,6 @@ import com.topjohnwu.magisk.core.Info ...@@ -11,7 +11,6 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.Configuration import com.topjohnwu.magisk.core.download.Configuration
import com.topjohnwu.magisk.core.download.DownloadService import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.DownloadSubject import com.topjohnwu.magisk.core.download.DownloadSubject
import com.topjohnwu.magisk.core.download.RemoteFileService
import com.topjohnwu.magisk.data.repository.StringRepository import com.topjohnwu.magisk.data.repository.StringRepository
import com.topjohnwu.magisk.events.RequestFileEvent import com.topjohnwu.magisk.events.RequestFileEvent
import com.topjohnwu.magisk.events.dialog.SecondSlotWarningDialog import com.topjohnwu.magisk.events.dialog.SecondSlotWarningDialog
...@@ -60,22 +59,21 @@ class InstallViewModel( ...@@ -60,22 +59,21 @@ class InstallViewModel(
set(value) = set(value, field, { field = it }, BR.notes) set(value) = set(value, field, { field = it }, BR.notes)
init { init {
RemoteFileService.reset()
RemoteFileService.progressBroadcast.observeForever {
val (progress, subject) = it ?: return@observeForever
if (subject !is DownloadSubject.Magisk) {
return@observeForever
}
this.progress = progress.times(100).roundToInt()
if (this.progress >= 100) {
state = State.LOADED
}
}
viewModelScope.launch { viewModelScope.launch {
notes = stringRepo.getString(Info.remote.magisk.note) notes = stringRepo.getString(Info.remote.magisk.note)
} }
} }
fun onProgressUpdate(progress: Float, subject: DownloadSubject) {
if (subject !is DownloadSubject.Magisk) {
return
}
this.progress = progress.times(100).roundToInt()
if (this.progress >= 100) {
state = State.LOADED
}
}
fun step(nextStep: Int) { fun step(nextStep: Int) {
step = nextStep step = nextStep
} }
......
...@@ -12,6 +12,7 @@ import com.topjohnwu.magisk.R ...@@ -12,6 +12,7 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.arch.ReselectionTarget import com.topjohnwu.magisk.arch.ReselectionTarget
import com.topjohnwu.magisk.arch.ViewEvent import com.topjohnwu.magisk.arch.ViewEvent
import com.topjohnwu.magisk.core.download.BaseDownloadService
import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding
import com.topjohnwu.magisk.events.InstallExternalModuleEvent import com.topjohnwu.magisk.events.InstallExternalModuleEvent
import com.topjohnwu.magisk.ktx.hideKeyboard import com.topjohnwu.magisk.ktx.hideKeyboard
...@@ -50,6 +51,7 @@ class ModuleFragment : BaseUIFragment<ModuleViewModel, FragmentModuleMd2Binding> ...@@ -50,6 +51,7 @@ class ModuleFragment : BaseUIFragment<ModuleViewModel, FragmentModuleMd2Binding>
super.onStart() super.onStart()
setHasOptionsMenu(true) setHasOptionsMenu(true)
activity.title = resources.getString(R.string.modules) activity.title = resources.getString(R.string.modules)
BaseDownloadService.observeProgress(this, viewModel::onProgressUpdate)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
......
...@@ -2,14 +2,12 @@ package com.topjohnwu.magisk.ui.module ...@@ -2,14 +2,12 @@ package com.topjohnwu.magisk.ui.module
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.databinding.ObservableArrayList import androidx.databinding.ObservableArrayList
import androidx.lifecycle.Observer
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.* import com.topjohnwu.magisk.arch.*
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.download.DownloadSubject import com.topjohnwu.magisk.core.download.DownloadSubject
import com.topjohnwu.magisk.core.download.RemoteFileService
import com.topjohnwu.magisk.core.model.module.Module import com.topjohnwu.magisk.core.model.module.Module
import com.topjohnwu.magisk.core.tasks.RepoUpdater import com.topjohnwu.magisk.core.tasks.RepoUpdater
import com.topjohnwu.magisk.data.database.RepoByNameDao import com.topjohnwu.magisk.data.database.RepoByNameDao
...@@ -47,7 +45,7 @@ class ModuleViewModel( ...@@ -47,7 +45,7 @@ class ModuleViewModel(
private val repoName: RepoByNameDao, private val repoName: RepoByNameDao,
private val repoUpdated: RepoByUpdatedDao, private val repoUpdated: RepoByUpdatedDao,
private val repoUpdater: RepoUpdater private val repoUpdater: RepoUpdater
) : BaseViewModel(), Queryable, Observer<Pair<Float, DownloadSubject>> { ) : BaseViewModel(), Queryable {
override val queryDelay = 1000L override val queryDelay = 1000L
private var queryJob: Job? = null private var queryJob: Job? = null
...@@ -125,9 +123,6 @@ class ModuleViewModel( ...@@ -125,9 +123,6 @@ class ModuleViewModel(
// --- // ---
init { init {
RemoteFileService.reset()
RemoteFileService.progressBroadcast.observeForever(this)
itemsInstalled.addOnListChangedCallback( itemsInstalled.addOnListChangedCallback(
onItemRangeInserted = { _, _, _ -> onItemRangeInserted = { _, _, _ ->
if (installSectionList.isEmpty()) if (installSectionList.isEmpty())
...@@ -152,13 +147,7 @@ class ModuleViewModel( ...@@ -152,13 +147,7 @@ class ModuleViewModel(
// --- // ---
override fun onCleared() { fun onProgressUpdate(progress: Float, subject: DownloadSubject) {
super.onCleared()
RemoteFileService.progressBroadcast.removeObserver(this)
}
override fun onChanged(it: Pair<Float, DownloadSubject>?) {
val (progress, subject) = it ?: return
if (subject !is DownloadSubject.Module) if (subject !is DownloadSubject.Module)
return return
......
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