diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/BaseDownloadService.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/BaseDownloadService.kt new file mode 100644 index 000000000..caab55673 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/BaseDownloadService.kt @@ -0,0 +1,201 @@ +package com.topjohnwu.magisk.core.download + +import android.app.Notification +import android.content.Intent +import android.os.IBinder +import androidx.lifecycle.LifecycleOwner +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.ProgressInputStream +import com.topjohnwu.magisk.data.network.GithubRawServices +import com.topjohnwu.magisk.ktx.checkSum +import com.topjohnwu.magisk.ktx.writeTo +import com.topjohnwu.magisk.view.Notifications +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import okhttp3.ResponseBody +import org.koin.android.ext.android.inject +import org.koin.core.KoinComponent +import timber.log.Timber +import java.io.IOException +import java.io.InputStream +import java.util.* +import kotlin.collections.HashMap +import kotlin.random.Random.Default.nextInt + +abstract class BaseDownloadService : BaseService(), KoinComponent { + + private val hasNotifications get() = notifications.isNotEmpty() + private val notifications = Collections.synchronizedMap(HashMap()) + private val coroutineScope = CoroutineScope(Dispatchers.IO) + + val service: GithubRawServices by inject() + + // -- Service overrides + + override fun onBind(intent: Intent?): IBinder? = null + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + intent?.getParcelableExtra(ARG_URL)?.let { subject -> + update(subject.notifyID()) + coroutineScope.launch { + try { + subject.startDownload() + } catch (e: IOException) { + Timber.e(e) + notifyFail(subject) + } + } + } + return START_REDELIVER_INTENT + } + + 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 DownloadSubject.startDownload() { + val skip = this is DownloadSubject.Magisk && file.exists() && file.checkSum("MD5", magisk.md5) + if (!skip) { + val stream = service.fetchFile(url).toProgressStream(this) + when (this) { + is DownloadSubject.Module -> // Download and process on-the-fly + stream.toModule(file, service.fetchInstaller().byteStream()) + else -> + stream.writeTo(file) + } + } + val newId = notifyFinish(this) + if (ForegroundTracker.hasForeground) + onFinish(this, newId) + if (!hasNotifications) + stopSelf() + } + + private fun ResponseBody.toProgressStream(subject: DownloadSubject): 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)) + } + } + } + } + + // --- Notification managements + + fun DownloadSubject.notifyID() = hashCode() + + private fun notifyFail(subject: DownloadSubject) = lastNotify(subject.notifyID()) { + broadcast(-1f, subject) + it.setContentText(getString(R.string.download_file_error)) + .setSmallIcon(android.R.drawable.stat_notify_error) + .setOngoing(false) + } + + private fun notifyFinish(subject: DownloadSubject) = lastNotify(subject.notifyID()) { + broadcast(1f, subject) + it.setIntent(subject) + .setContentText(getString(R.string.download_complete)) + .setSmallIcon(android.R.drawable.stat_sys_download_done) + .setProgress(0, 0, false) + .setOngoing(false) + .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) + } + } + + // --- Implement custom logic + + protected abstract suspend fun onFinish(subject: DownloadSubject, id: Int) + + protected abstract fun Notification.Builder.setIntent(subject: DownloadSubject) + : Notification.Builder + + // --- + + companion object : KoinComponent { + const val ARG_URL = "arg_url" + + private val progressBroadcast = MutableLiveData>() + + fun observeProgress(owner: LifecycleOwner, callback: (Float, DownloadSubject) -> Unit) { + progressBroadcast.value = null + progressBroadcast.observe(owner) { + val (progress, subject) = it ?: return@observe + callback(progress, subject) + } + } + + private fun broadcast(progress: Float, subject: DownloadSubject) { + progressBroadcast.postValue(progress to subject) + } + } +} 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 0bf730eaa..15e481e14 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 @@ -6,139 +6,84 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent 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.Flash.Secondary import com.topjohnwu.magisk.core.download.DownloadSubject.* import com.topjohnwu.magisk.core.intent 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.utils.APKInstall -import org.koin.core.get -import java.io.File import kotlin.random.Random.Default.nextInt -/* More of a facade for [RemoteFileService], but whatever... */ @SuppressLint("Registered") -open class DownloadService : RemoteFileService() { +open class DownloadService : BaseDownloadService() { private val context get() = this - private val File.type - get() = MimeTypeMap.getSingleton() - .getMimeTypeFromExtension(extension) - ?: "resource/folder" - 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) + override suspend fun onFinish(subject: DownloadSubject, id: Int) = when (subject) { + is Magisk -> subject.onFinish(id) + is Module -> subject.onFinish(id) + is Manager -> subject.onFinish(id) } - private suspend fun onFinished( - subject: Magisk, - id: Int - ) = when (val conf = subject.configuration) { - Uninstall -> FlashFragment.uninstall(subject.file, id) + private suspend fun Magisk.onFinish(id: Int) = when (val conf = configuration) { + Uninstall -> FlashFragment.uninstall(file, id) EnvFix -> { - remove(id) - EnvFixTask(subject.file).exec() + cancel(id) + EnvFixTask(file).exec() Unit } - is Patch -> FlashFragment.patch(subject.file, conf.fileUri, id) - is Flash -> FlashFragment.flash(subject.file, conf is Secondary, id) + is Patch -> FlashFragment.patch(file, conf.fileUri, id) + is Flash -> FlashFragment.flash(file, conf is Secondary, id) else -> Unit } - private fun onFinished( - subject: Module, - id: Int - ) = when (subject.configuration) { - is Flash -> FlashFragment.install(subject.file, id) + private fun Module.onFinish(id: Int) = when (configuration) { + is Flash -> FlashFragment.install(file, id) else -> Unit } - private suspend fun onFinished( - subject: Manager, - id: Int - ) { - handleAPK(subject) - remove(id) - when (subject.configuration) { - is APK.Upgrade -> APKInstall.install(this, subject.file) - is APK.Restore -> Unit - } + private suspend fun Manager.onFinish(id: Int) { + handleAPK(this) + cancel(id) } - // --- + // --- Customize finish notification - override fun Notification.Builder.addActions(subject: DownloadSubject) + override fun Notification.Builder.setIntent(subject: DownloadSubject) = when (subject) { - is Magisk -> addActions(subject) - is Module -> addActions(subject) - is Manager -> addActions(subject) + is Magisk -> setIntent(subject) + is Module -> setIntent(subject) + is Manager -> setIntent(subject) } - private fun Notification.Builder.addActions(subject: Magisk) + private fun Notification.Builder.setIntent(subject: Magisk) = 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)) is Flash -> setContentIntent(FlashFragment.flashIntent(context, subject.file, conf is Secondary)) 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) { - 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)) - else -> this + else -> setContentIntent(Intent()) } - private fun Notification.Builder.addActions(subject: Manager) + private fun Notification.Builder.setIntent(subject: Manager) = when (subject.configuration) { APK.Upgrade -> setContentIntent(APKInstall.installIntent(context, subject.file)) - else -> this + else -> setContentIntent(Intent()) } - @Suppress("ReplaceSingleLineLet") private fun Notification.Builder.setContentIntent(intent: Intent) = setContentIntent( 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 { lateinit var subject: DownloadSubject } @@ -150,7 +95,7 @@ open class DownloadService : RemoteFileService() { val builder = Builder().apply(argBuilder) val intent = app.intent().putExtra(ARG_URL, builder.subject) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (Build.VERSION.SDK_INT >= 26) { app.startForegroundService(intent) } else { app.startService(intent) diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/ManagerUpgrade.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/ManagerUpgrade.kt index 206524dac..9d0598468 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/ManagerUpgrade.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/ManagerUpgrade.kt @@ -12,10 +12,11 @@ import com.topjohnwu.magisk.core.intent import com.topjohnwu.magisk.core.isRunningAsStub import com.topjohnwu.magisk.core.utils.PatchAPK import com.topjohnwu.magisk.ktx.writeTo +import com.topjohnwu.magisk.utils.APKInstall import com.topjohnwu.superuser.Shell 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) return @@ -31,7 +32,7 @@ private fun RemoteFileService.patch(apk: File, id: Int) { patched.renameTo(apk) } -private suspend fun RemoteFileService.upgrade(apk: File, id: Int) { +private suspend fun DownloadService.upgrade(apk: File, id: Int) { if (isRunningAsStub) { // Move to upgrade location apk.copyTo(DynAPK.update(this), overwrite = true) @@ -49,9 +50,10 @@ private suspend fun RemoteFileService.upgrade(apk: File, id: Int) { } else { 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) { it.setProgress(0, 0, true) .setProgress(0, 0, true) @@ -64,8 +66,8 @@ private fun RemoteFileService.restore(apk: File, id: Int) { 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) { - is Upgrade -> upgrade(subject.file, subject.hashCode()) - is Restore -> restore(subject.file, subject.hashCode()) + is Upgrade -> upgrade(subject.file, subject.notifyID()) + is Restore -> restore(subject.file, subject.notifyID()) } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/NotificationService.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/NotificationService.kt deleted file mode 100644 index cb0ed75aa..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/NotificationService.kt +++ /dev/null @@ -1,96 +0,0 @@ -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()) - - 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 -} diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/RemoteFileService.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/RemoteFileService.kt deleted file mode 100644 index 6ceff246f..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/RemoteFileService.kt +++ /dev/null @@ -1,128 +0,0 @@ -package com.topjohnwu.magisk.core.download - -import android.app.Notification -import android.content.Intent -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import com.topjohnwu.magisk.R -import com.topjohnwu.magisk.core.ForegroundTracker -import com.topjohnwu.magisk.core.download.DownloadSubject.Magisk -import com.topjohnwu.magisk.core.download.DownloadSubject.Module -import com.topjohnwu.magisk.core.utils.ProgressInputStream -import com.topjohnwu.magisk.data.network.GithubRawServices -import com.topjohnwu.magisk.ktx.checkSum -import com.topjohnwu.magisk.ktx.writeTo -import com.topjohnwu.magisk.view.Notifications -import kotlinx.coroutines.launch -import okhttp3.ResponseBody -import org.koin.android.ext.android.inject -import org.koin.core.KoinComponent -import timber.log.Timber -import java.io.IOException -import java.io.InputStream - -abstract class RemoteFileService : NotificationService() { - - val service: GithubRawServices by inject() - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - intent?.getParcelableExtra(ARG_URL)?.let { - update(it.hashCode()) - coroutineScope.launch { - try { - start(it) - } catch (e: IOException) { - Timber.e(e) - failNotify(it) - } - } - } - return START_REDELIVER_INTENT - } - - override fun createNotification() = Notifications.progress(this, "") - - // --- - - private suspend fun start(subject: DownloadSubject) { - if (subject !is Magisk || - !subject.file.exists() || - !subject.file.checkSum("MD5", subject.magisk.md5)) { - val stream = service.fetchFile(subject.url).toProgressStream(subject) - when (subject) { - is Module -> - stream.toModule(subject.file, service.fetchInstaller().byteStream()) - else -> - stream.writeTo(subject.file) - } - } - val newId = finishNotify(subject) - if (ForegroundTracker.hasForeground) { - onFinished(subject, newId) - } - } - - private fun ResponseBody.toProgressStream(subject: DownloadSubject): InputStream { - val maxRaw = contentLength() - val max = maxRaw / 1_000_000f - val id = subject.hashCode() - - update(id) { it.setContentTitle(subject.title) } - - return ProgressInputStream(byteStream()) { - val progress = it / 1_000_000f - update(id) { notification -> - if (maxRaw > 0) { - send(progress / max, subject) - notification - .setProgress(maxRaw.toInt(), it.toInt(), false) - .setContentText("%.2f / %.2f MB".format(progress, max)) - } else { - send(-1f, subject) - notification.setContentText("%.2f MB / ??".format(progress)) - } - } - } - } - - private fun failNotify(subject: DownloadSubject) = lastNotify(subject.hashCode()) { - send(0f, subject) - it.setContentText(getString(R.string.download_file_error)) - .setSmallIcon(android.R.drawable.stat_notify_error) - .setOngoing(false) - } - - private fun finishNotify(subject: DownloadSubject) = lastNotify(subject.hashCode()) { - send(1f, subject) - it.addActions(subject) - .setContentText(getString(R.string.download_complete)) - .setSmallIcon(android.R.drawable.stat_sys_download_done) - .setProgress(0, 0, false) - .setOngoing(false) - .setAutoCancel(true) - } - - // --- - - - protected abstract suspend fun onFinished(subject: DownloadSubject, id: Int) - - protected abstract fun Notification.Builder.addActions(subject: DownloadSubject) - : Notification.Builder - - companion object : KoinComponent { - const val ARG_URL = "arg_url" - - private val internalProgressBroadcast = MutableLiveData>() - val progressBroadcast: LiveData> get() = internalProgressBroadcast - - fun send(progress: Float, subject: DownloadSubject) { - internalProgressBroadcast.postValue(progress to subject) - } - - fun reset() { - internalProgressBroadcast.value = null - } - } - -} diff --git a/app/src/main/java/com/topjohnwu/magisk/core/utils/ProgressInputStream.kt b/app/src/main/java/com/topjohnwu/magisk/core/utils/ProgressInputStream.kt index ac0f26cb9..5dfa45a7b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/utils/ProgressInputStream.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/utils/ProgressInputStream.kt @@ -1,6 +1,5 @@ package com.topjohnwu.magisk.core.utils -import com.topjohnwu.superuser.internal.UiThreadHandler import java.io.FilterInputStream import java.io.InputStream @@ -16,7 +15,7 @@ class ProgressInputStream( val cur = System.currentTimeMillis() if (cur - lastUpdate > 1000) { lastUpdate = cur - UiThreadHandler.run { progressEmitter(bytesRead) } + progressEmitter(bytesRead) } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt index 4cf6c3177..5369fa216 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.* import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.BaseUIFragment +import com.topjohnwu.magisk.core.download.BaseDownloadService import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding import com.topjohnwu.magisk.events.RebootEvent import com.topjohnwu.superuser.Shell @@ -18,6 +19,7 @@ class HomeFragment : BaseUIFragment() { super.onStart() activity.title = resources.getString(R.string.section_home) setHasOptionsMenu(true) + BaseDownloadService.observeProgress(this, viewModel::onProgressUpdate) } override fun onCreateView( diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt index 62d9ab683..c1ed39483 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt @@ -8,8 +8,8 @@ import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.* import com.topjohnwu.magisk.core.Config 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.RemoteFileService import com.topjohnwu.magisk.core.model.MagiskJson import com.topjohnwu.magisk.core.model.ManagerJson import com.topjohnwu.magisk.data.repository.MagiskRepository @@ -77,14 +77,6 @@ class HomeViewModel( private var shownDialog = false - init { - RemoteFileService.progressBroadcast.observeForever { - when (it?.second) { - is Manager -> stateManagerProgress = it.first.times(100f).roundToInt() - } - } - } - override fun refresh() = viewModelScope.launch { notifyPropertyChanged(BR.showUninstall) repoMagisk.fetchUpdate()?.apply { @@ -119,6 +111,12 @@ class HomeViewModel( } }.publish() + fun onProgressUpdate(progress: Float, subject: DownloadSubject) { + when (subject) { + is Manager -> stateManagerProgress = progress.times(100f).roundToInt() + } + } + fun onLinkPressed(link: String) = OpenInappLinkEvent(link).publish() fun onDeletePressed() = UninstallDialog().publish() 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 6ecf97ca7..2182416f6 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 @@ -4,6 +4,7 @@ import android.content.Intent import androidx.lifecycle.viewModelScope import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.BaseUIFragment +import com.topjohnwu.magisk.core.download.BaseDownloadService import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding import com.topjohnwu.magisk.events.RequestFileEvent import org.koin.androidx.viewmodel.ext.android.viewModel @@ -24,6 +25,7 @@ class InstallFragment : BaseUIFragment= 100) { - state = State.LOADED - } - } viewModelScope.launch { 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) { step = nextStep } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleFragment.kt index 0bbae71bf..54bfcc83f 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleFragment.kt @@ -12,6 +12,7 @@ import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.arch.ReselectionTarget import com.topjohnwu.magisk.arch.ViewEvent +import com.topjohnwu.magisk.core.download.BaseDownloadService import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding import com.topjohnwu.magisk.events.InstallExternalModuleEvent import com.topjohnwu.magisk.ktx.hideKeyboard @@ -50,6 +51,7 @@ class ModuleFragment : BaseUIFragment super.onStart() setHasOptionsMenu(true) activity.title = resources.getString(R.string.modules) + BaseDownloadService.observeProgress(this, viewModel::onProgressUpdate) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt index 4a7d77a13..227682fbd 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt @@ -2,14 +2,12 @@ package com.topjohnwu.magisk.ui.module import androidx.databinding.Bindable import androidx.databinding.ObservableArrayList -import androidx.lifecycle.Observer import androidx.lifecycle.viewModelScope import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.* import com.topjohnwu.magisk.core.Config 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.tasks.RepoUpdater import com.topjohnwu.magisk.data.database.RepoByNameDao @@ -47,7 +45,7 @@ class ModuleViewModel( private val repoName: RepoByNameDao, private val repoUpdated: RepoByUpdatedDao, private val repoUpdater: RepoUpdater -) : BaseViewModel(), Queryable, Observer> { +) : BaseViewModel(), Queryable { override val queryDelay = 1000L private var queryJob: Job? = null @@ -125,9 +123,6 @@ class ModuleViewModel( // --- init { - RemoteFileService.reset() - RemoteFileService.progressBroadcast.observeForever(this) - itemsInstalled.addOnListChangedCallback( onItemRangeInserted = { _, _, _ -> if (installSectionList.isEmpty()) @@ -152,13 +147,7 @@ class ModuleViewModel( // --- - override fun onCleared() { - super.onCleared() - RemoteFileService.progressBroadcast.removeObserver(this) - } - - override fun onChanged(it: Pair?) { - val (progress, subject) = it ?: return + fun onProgressUpdate(progress: Float, subject: DownloadSubject) { if (subject !is DownloadSubject.Module) return