Commit fc05f377 authored by topjohnwu's avatar topjohnwu

Update env fix handling logic

parent 5c0e8638
......@@ -9,15 +9,18 @@ import android.os.Build
import android.webkit.MimeTypeMap
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.tasks.EnvFixTask
import com.topjohnwu.magisk.extensions.chooser
import com.topjohnwu.magisk.extensions.exists
import com.topjohnwu.magisk.extensions.provide
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.legacy.flash.FlashActivity
import com.topjohnwu.magisk.model.entity.internal.Configuration.*
import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
import com.topjohnwu.magisk.utils.APKInstall
import io.reactivex.Completable
import org.koin.core.get
import java.io.File
import kotlin.random.Random.Default.nextInt
......@@ -43,6 +46,7 @@ open class DownloadService : RemoteFileService() {
id: Int
) = when (val conf = subject.configuration) {
Uninstall -> FlashActivity.uninstall(this, subject.file, id)
EnvFix -> { remove(id); EnvFixTask(subject.file).exec() }
is Patch -> FlashActivity.patch(this, subject.file, conf.fileUri, id)
is Flash -> FlashActivity.flash(this, subject.file, conf is Secondary, id)
else -> Unit
......@@ -60,12 +64,16 @@ open class DownloadService : RemoteFileService() {
subject: Manager,
id: Int
) {
Completable.fromAction {
handleAPK(subject)
}.subscribeK {
remove(id)
when (subject.configuration) {
is APK.Upgrade -> APKInstall.install(this, subject.file)
is APK.Restore -> Unit
}
}
}
// ---
......
......@@ -20,8 +20,8 @@ private fun RemoteFileService.patch(apk: File, id: Int) {
if (packageName == BuildConfig.APPLICATION_ID)
return
update(id) { notification ->
notification.setProgress(0, 0, true)
update(id) {
it.setProgress(0, 0, true)
.setProgress(0, 0, true)
.setContentTitle(getString(R.string.hide_manager_title))
.setContentText("")
......@@ -53,8 +53,8 @@ private fun RemoteFileService.upgrade(apk: File, id: Int) {
}
private fun RemoteFileService.restore(apk: File, id: Int) {
update(id) { notification ->
notification.setProgress(0, 0, true)
update(id) {
it.setProgress(0, 0, true)
.setProgress(0, 0, true)
.setContentTitle(getString(R.string.restore_img_msg))
.setContentText("")
......@@ -65,8 +65,8 @@ private fun RemoteFileService.restore(apk: File, id: Int) {
Shell.su("pm install $apk && pm uninstall $packageName").exec()
}
fun RemoteFileService.handleAPK(subject: DownloadSubject.Manager)
= when (subject.configuration) {
fun RemoteFileService.handleAPK(subject: DownloadSubject.Manager) =
when (subject.configuration) {
is Upgrade -> upgrade(subject.file, subject.hashCode())
is Restore -> restore(subject.file, subject.hashCode())
}
......@@ -7,16 +7,14 @@ import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.core.view.Notifications
import org.koin.core.KoinComponent
import java.util.*
import kotlin.collections.HashMap
import kotlin.random.Random.Default.nextInt
abstract class NotificationService : BaseService(), KoinComponent {
abstract val defaultNotification: Notification.Builder
private val hasNotifications get() = notifications.isNotEmpty()
private val notifications =
Collections.synchronizedMap(mutableMapOf<Int, Notification.Builder>())
private val notifications = Collections.synchronizedMap(HashMap<Int, Notification.Builder>())
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
......@@ -24,22 +22,23 @@ abstract class NotificationService : BaseService(), KoinComponent {
notifications.clear()
}
abstract fun createNotification(): Notification.Builder
// --
fun update(
id: Int,
body: (Notification.Builder) -> Unit = {}
) {
val notification = notifications.getOrPut(id) { defaultNotification }
notify(id, notification.also(body).build())
if (notifications.size == 1) {
val wasEmpty = notifications.isEmpty()
val notification = notifications.getOrPut(id, ::createNotification).also(body)
if (wasEmpty)
updateForeground()
}
else
notify(id, notification.build())
}
protected fun finishNotify(
protected fun lastNotify(
id: Int,
editBody: (Notification.Builder) -> Notification.Builder? = { null }
) : Int {
......@@ -57,6 +56,11 @@ abstract class NotificationService : BaseService(), KoinComponent {
return newId
}
protected fun remove(id: Int) = notifications.remove(id).also {
cancel(id)
updateForeground()
}
// ---
private fun notify(id: Int, notification: Notification) {
......@@ -67,17 +71,14 @@ abstract class NotificationService : BaseService(), KoinComponent {
Notifications.mgr.cancel(id)
}
protected fun remove(id: Int) = notifications.remove(id).also {
cancel(id)
updateForeground()
}
private fun updateForeground() {
if (hasNotifications)
startForeground(notifications.keys.first(), notifications.values.first().build())
else
if (hasNotifications) {
val first = notifications.entries.first()
startForeground(first.key, first.value.build())
} else {
stopForeground(true)
}
}
// --
......
......@@ -14,7 +14,8 @@ import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.writeTo
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Magisk
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Module
import com.topjohnwu.superuser.ShellUtils
import io.reactivex.Completable
import okhttp3.ResponseBody
......@@ -27,19 +28,17 @@ abstract class RemoteFileService : NotificationService() {
val service: GithubRawServices by inject()
override val defaultNotification
get() = Notifications.progress(this, "")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.getParcelableExtra<DownloadSubject>(ARG_URL)?.let { start(it) }
return START_REDELIVER_INTENT
}
override fun createNotification() = Notifications.progress(this, "")
// ---
private fun start(subject: DownloadSubject) = checkExisting(subject)
.onErrorResumeNext { download(subject) }
.doOnSubscribe { update(subject.hashCode()) { it.setContentTitle(subject.title) } }
.subscribeK(onError = {
Timber.e(it)
failNotify(subject)
......@@ -52,16 +51,14 @@ abstract class RemoteFileService : NotificationService() {
private fun checkExisting(subject: DownloadSubject) = Completable.fromAction {
check(subject is Magisk) { "Download cache is disabled" }
subject.file.also {
check(it.exists() && ShellUtils.checkSum("MD5", it, subject.magisk.md5)) {
check(subject.file.exists() &&
ShellUtils.checkSum("MD5", subject.file, subject.magisk.md5)) {
"The given file does not match checksum"
}
}
}
private fun download(subject: DownloadSubject) = service.fetchFile(subject.url)
.map { it.toStream(subject.hashCode(), subject) }
.map { it.toProgressStream(subject) }
.flatMapCompletable { stream ->
when (subject) {
is Module -> service.fetchInstaller()
......@@ -69,14 +66,14 @@ abstract class RemoteFileService : NotificationService() {
.ignoreElement()
else -> Completable.fromAction { stream.writeTo(subject.file) }
}
}.doOnComplete {
if (subject is Manager)
handleAPK(subject)
}
private fun ResponseBody.toStream(id: Int, subject: DownloadSubject): InputStream {
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
......@@ -94,14 +91,14 @@ abstract class RemoteFileService : NotificationService() {
}
}
private fun failNotify(subject: DownloadSubject) = finishNotify(subject.hashCode()) {
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) = finishNotify(subject.hashCode()) {
private fun finishNotify(subject: DownloadSubject) = lastNotify(subject.hashCode()) {
send(1f, subject)
it.addActions(subject)
.setContentText(getString(R.string.download_complete))
......
package com.topjohnwu.magisk.core.tasks
import androidx.annotation.MainThread
interface FlashResultListener {
@MainThread
fun onResult(success: Boolean)
}
......@@ -18,7 +18,7 @@ abstract class FlashZip(
private val mUri: Uri,
private val console: MutableList<String>,
private val logs: MutableList<String>
) {
) : FlashResultListener {
private val context: Context by inject()
private val installFolder = File(context.cacheDir, "flash").apply {
......@@ -94,6 +94,4 @@ abstract class FlashZip(
.subscribeK(onError = { onResult(false) }) { onResult(it) }
.let { Unit } // ignores result disposable
protected abstract fun onResult(success: Boolean)
}
package com.topjohnwu.magisk.model.flash
package com.topjohnwu.magisk.core.tasks
import android.content.Context
import android.net.Uri
import androidx.core.os.postDelayed
import com.topjohnwu.magisk.core.tasks.FlashZip
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler
......@@ -28,16 +27,7 @@ sealed class Flashing(
console: MutableList<String>,
log: MutableList<String>,
resultListener: FlashResultListener
) : Flashing(uri, console, log, resultListener) {
override fun onResult(success: Boolean) {
if (success) {
//Utils.loadModules()
}
super.onResult(success)
}
}
) : Flashing(uri, console, log, resultListener)
class Uninstall(
uri: Uri,
......
......@@ -12,13 +12,13 @@ import androidx.databinding.ObservableArrayList
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.tasks.FlashResultListener
import com.topjohnwu.magisk.core.tasks.Flashing
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.extensions.*
import com.topjohnwu.magisk.model.binding.BindingAdapter
import com.topjohnwu.magisk.model.entity.recycler.ConsoleItem
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.model.flash.FlashResultListener
import com.topjohnwu.magisk.model.flash.Flashing
import com.topjohnwu.magisk.model.flash.Patching
import com.topjohnwu.magisk.ui.base.BaseViewModel
import com.topjohnwu.magisk.ui.base.diffListOf
import com.topjohnwu.magisk.ui.base.itemBindingOf
......@@ -60,26 +60,26 @@ class FlashViewModel(
Const.Value.UNINSTALL -> Flashing
.Uninstall(installer, outItems, logItems, this)
.exec()
Const.Value.FLASH_MAGISK -> Patching
Const.Value.FLASH_MAGISK -> MagiskInstaller
.Direct(installer, outItems, logItems, this)
.exec()
Const.Value.FLASH_INACTIVE_SLOT -> Patching
Const.Value.FLASH_INACTIVE_SLOT -> MagiskInstaller
.SecondSlot(installer, outItems, logItems, this)
.exec()
Const.Value.PATCH_FILE -> Patching
.File(installer, uri ?: return, outItems, logItems, this)
Const.Value.PATCH_FILE -> MagiskInstaller
.Patch(installer, uri ?: return, outItems, logItems, this)
.exec()
}
}
override fun onResult(isSuccess: Boolean) {
state = if (isSuccess) State.LOADED else State.LOADING_FAILED
override fun onResult(success: Boolean) {
state = if (success) State.LOADED else State.LOADING_FAILED
behaviorText.value = when {
isSuccess -> resources.getString(R.string.done)
success -> resources.getString(R.string.done)
else -> resources.getString(R.string.failure)
}
if (isSuccess) {
if (success) {
Handler().postDelayed(500) {
showRestartTitle.value = true
}
......
......@@ -31,6 +31,9 @@ sealed class Configuration : Parcelable {
@Parcelize
object Uninstall : Configuration()
@Parcelize
object EnvFix : Configuration()
@Parcelize
data class Patch(val fileUri: Uri) : Configuration()
......
......@@ -20,7 +20,7 @@ sealed class DownloadSubject : Parcelable {
open val title: String get() = file.name
@Parcelize
data class Module(
class Module(
val module: Repo,
val configuration: Configuration
) : DownloadSubject() {
......@@ -33,7 +33,7 @@ sealed class DownloadSubject : Parcelable {
}
@Parcelize
data class Manager(
class Manager(
val configuration: Configuration.APK
) : DownloadSubject() {
......@@ -53,13 +53,13 @@ sealed class DownloadSubject : Parcelable {
}
sealed class Magisk : DownloadSubject() {
abstract class Magisk : DownloadSubject() {
abstract val configuration: Configuration
val magisk: MagiskJson = Info.remote.magisk
@Parcelize
data class Flash(
private class DownloadInternal(
override val configuration: Configuration
) : Magisk() {
override val url: String get() = magisk.link
......@@ -72,8 +72,8 @@ sealed class DownloadSubject : Parcelable {
}
@Parcelize
class Uninstall : Magisk() {
override val configuration: Configuration get() = Configuration.Uninstall
private class Uninstall : Magisk() {
override val configuration get() = Configuration.Uninstall
override val url: String get() = Info.remote.uninstaller.link
@IgnoredOnParcel
......@@ -83,8 +83,8 @@ sealed class DownloadSubject : Parcelable {
}
@Parcelize
class Download : Magisk() {
override val configuration: Configuration get() = Configuration.Download
private class Download : Magisk() {
override val configuration get() = Configuration.Download
override val url: String get() = magisk.link
@IgnoredOnParcel
......@@ -97,7 +97,8 @@ sealed class DownloadSubject : Parcelable {
operator fun invoke(configuration: Configuration) = when (configuration) {
Configuration.Download -> Download()
Configuration.Uninstall -> Uninstall()
else -> Flash(configuration)
Configuration.EnvFix, is Configuration.Flash -> DownloadInternal(configuration)
else -> throw IllegalArgumentException()
}
}
......
package com.topjohnwu.magisk.model.events.dialog
import android.content.DialogInterface
import android.widget.Toast
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.utils.Utils
import com.topjohnwu.magisk.extensions.reboot
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.model.entity.internal.Configuration.EnvFix
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Magisk
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.superuser.internal.UiThreadHandler
import org.koin.core.KoinComponent
class EnvFixDialog : DialogEvent() {
......@@ -21,14 +22,16 @@ class EnvFixDialog : DialogEvent() {
onClick {
dialog.applyTitle(R.string.setup_title)
.applyMessage(R.string.setup_msg)
.applyButton(MagiskDialog.ButtonType.POSITIVE) {
title = ""
}
.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
title = ""
}
.resetButtons()
.cancellable(false)
fixEnv(it)
val lbm = LocalBroadcastManager.getInstance(dialog.context)
lbm.registerReceiver(object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
dialog.dismiss()
lbm.unregisterReceiver(this)
}
}, IntentFilter(DISMISS))
DownloadService(dialog.context) { subject = Magisk(EnvFix) }
}
}
.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
......@@ -36,20 +39,7 @@ class EnvFixDialog : DialogEvent() {
}
.let { Unit }
private fun fixEnv(dialog: DialogInterface) {
object : MagiskInstaller(), KoinComponent {
override fun operations() = fixEnv()
override fun onResult(success: Boolean) {
dialog.dismiss()
Utils.toast(
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
Toast.LENGTH_LONG
)
if (success)
UiThreadHandler.handler.postDelayed({ reboot() }, 5000)
companion object {
const val DISMISS = "com.topjohnwu.magisk.ENV_DONE"
}
}.exec()
}
}
package com.topjohnwu.magisk.model.flash
interface FlashResultListener {
fun onResult(isSuccess: Boolean)
}
\ No newline at end of file
package com.topjohnwu.magisk.model.flash
import android.net.Uri
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.superuser.Shell
sealed class Patching(
file: Uri,
private val console: MutableList<String>,
logs: MutableList<String>,
private val resultListener: FlashResultListener
) : MagiskInstaller(file, console, logs) {
override fun onResult(success: Boolean) {
if (success) {
console.add("- All done!")
} else {
Shell.sh("rm -rf $installDir").submit()
console.add("! Installation failed")
}
resultListener.onResult(success)
}
class File(
file: Uri,
private val uri: Uri,
console: MutableList<String>,
logs: MutableList<String>,
resultListener: FlashResultListener
) : Patching(file, console, logs, resultListener) {
override fun operations() = doPatchFile(uri)
}
class SecondSlot(
file: Uri,
console: MutableList<String>,
logs: MutableList<String>,
resultListener: FlashResultListener
) : Patching(file, console, logs, resultListener) {
override fun operations() = secondSlot()
}
class Direct(
file: Uri,
console: MutableList<String>,
logs: MutableList<String>,
resultListener: FlashResultListener
) : Patching(file, console, logs, resultListener) {
override fun operations() = direct()
}
}
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