Commit 820427e9 authored by topjohnwu's avatar topjohnwu

Have some fun with Kotlin Coroutines

parent 89e11c9c
...@@ -21,6 +21,8 @@ import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.* ...@@ -21,6 +21,8 @@ import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
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 io.reactivex.Completable import io.reactivex.Completable
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.koin.core.get import org.koin.core.get
import java.io.File import java.io.File
import kotlin.random.Random.Default.nextInt import kotlin.random.Random.Default.nextInt
...@@ -47,7 +49,9 @@ open class DownloadService : RemoteFileService() { ...@@ -47,7 +49,9 @@ open class DownloadService : RemoteFileService() {
) = when (val conf = subject.configuration) { ) = when (val conf = subject.configuration) {
Uninstall -> FlashFragment.uninstall(subject.file, id) Uninstall -> FlashFragment.uninstall(subject.file, id)
EnvFix -> { EnvFix -> {
remove(id); EnvFixTask(subject.file).exec() remove(id)
GlobalScope.launch { EnvFixTask(subject.file).exec() }
Unit
} }
is Patch -> FlashFragment.patch(subject.file, conf.fileUri, id) is Patch -> FlashFragment.patch(subject.file, conf.fileUri, id)
is Flash -> FlashFragment.flash(subject.file, conf is Secondary, id) is Flash -> FlashFragment.flash(subject.file, conf is Secondary, id)
......
...@@ -2,25 +2,29 @@ package com.topjohnwu.magisk.core.tasks ...@@ -2,25 +2,29 @@ package com.topjohnwu.magisk.core.tasks
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.core.os.postDelayed
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.utils.unzip import com.topjohnwu.magisk.core.utils.unzip
import com.topjohnwu.magisk.extensions.fileName import com.topjohnwu.magisk.extensions.fileName
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.extensions.readUri import com.topjohnwu.magisk.extensions.readUri
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import io.reactivex.Single import com.topjohnwu.superuser.internal.UiThreadHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.KoinComponent
import org.koin.core.inject
import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
abstract class FlashZip( open class FlashZip(
private val mUri: Uri, private val mUri: Uri,
private val console: MutableList<String>, private val console: MutableList<String>,
private val logs: MutableList<String> private val logs: MutableList<String>
) : FlashResultListener { ): KoinComponent {
private val context: Context by inject() val context: Context by inject()
private val installFolder = File(context.cacheDir, "flash").apply { private val installFolder = File(context.cacheDir, "flash").apply {
if (!exists()) mkdirs() if (!exists()) mkdirs()
} }
...@@ -79,19 +83,37 @@ abstract class FlashZip( ...@@ -79,19 +83,37 @@ abstract class FlashZip(
.exec().isSuccess .exec().isSuccess
} }
fun exec() = Single open suspend fun exec() = withContext(Dispatchers.IO) {
.fromCallable { try {
runCatching { if (!flash()) {
flash() console.add("! Installation failed")
}.getOrElse {
it.printStackTrace()
false false
}.apply { } else {
Shell.su("cd /", "rm -rf ${tmpFile.parent} ${Const.TMP_FOLDER_PATH}") true
.submit()
} }
} catch (e: IOException) {
Timber.e(e)
false
} finally {
Shell.su("cd /", "rm -rf ${tmpFile.parent} ${Const.TMP_FOLDER_PATH}").submit()
} }
.subscribeK(onError = { onResult(false) }) { onResult(it) } }
.let { Unit } // ignores result disposable
class Uninstall(
uri: Uri,
console: MutableList<String>,
log: MutableList<String>
) : FlashZip(uri, console, log) {
override suspend fun exec(): Boolean {
val success = super.exec()
if (success) {
UiThreadHandler.handler.postDelayed(3000) {
Shell.su("pm uninstall " + context.packageName).exec()
}
}
return success
}
}
} }
package com.topjohnwu.magisk.core.tasks
import android.content.Context
import android.net.Uri
import androidx.core.os.postDelayed
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler
sealed class Flashing(
uri: Uri,
private val console: MutableList<String>,
log: MutableList<String>,
private val resultListener: FlashResultListener
) : FlashZip(uri, console, log) {
override fun onResult(success: Boolean) {
if (!success) {
console.add("! Installation failed")
}
resultListener.onResult(success)
}
class Install(
uri: Uri,
console: MutableList<String>,
log: MutableList<String>,
resultListener: FlashResultListener
) : Flashing(uri, console, log, resultListener)
class Uninstall(
uri: Uri,
console: MutableList<String>,
log: MutableList<String>,
resultListener: FlashResultListener
) : Flashing(uri, console, log, resultListener) {
private val context: Context by inject()
override fun onResult(success: Boolean) {
if (success) {
UiThreadHandler.handler.postDelayed(3000) {
Shell.su("pm uninstall " + context.packageName).exec()
}
}
super.onResult(success)
}
}
}
...@@ -5,7 +5,6 @@ import android.content.Intent ...@@ -5,7 +5,6 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.os.postDelayed import androidx.core.os.postDelayed
...@@ -26,7 +25,8 @@ import com.topjohnwu.superuser.internal.UiThreadHandler ...@@ -26,7 +25,8 @@ import com.topjohnwu.superuser.internal.UiThreadHandler
import com.topjohnwu.superuser.io.SuFile import com.topjohnwu.superuser.io.SuFile
import com.topjohnwu.superuser.io.SuFileInputStream import com.topjohnwu.superuser.io.SuFileInputStream
import com.topjohnwu.superuser.io.SuFileOutputStream import com.topjohnwu.superuser.io.SuFileOutputStream
import io.reactivex.Single import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.kamranzafar.jtar.TarEntry import org.kamranzafar.jtar.TarEntry
import org.kamranzafar.jtar.TarHeader import org.kamranzafar.jtar.TarHeader
import org.kamranzafar.jtar.TarInputStream import org.kamranzafar.jtar.TarInputStream
...@@ -43,14 +43,7 @@ import java.nio.ByteBuffer ...@@ -43,14 +43,7 @@ import java.nio.ByteBuffer
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
interface FlashResultListener { abstract class MagiskInstallImpl : KoinComponent {
@MainThread
fun onResult(success: Boolean)
}
abstract class MagiskInstallImpl : FlashResultListener, KoinComponent {
protected lateinit var installDir: File protected lateinit var installDir: File
private lateinit var srcBoot: String private lateinit var srcBoot: String
...@@ -388,53 +381,48 @@ abstract class MagiskInstallImpl : FlashResultListener, KoinComponent { ...@@ -388,53 +381,48 @@ abstract class MagiskInstallImpl : FlashResultListener, KoinComponent {
@WorkerThread @WorkerThread
protected abstract fun operations(): Boolean protected abstract fun operations(): Boolean
fun exec() { open suspend fun exec() = withContext(Dispatchers.IO) { operations() }
Single.fromCallable { operations() }.subscribeK { onResult(it) }
}
} }
sealed class MagiskInstaller( sealed class MagiskInstaller(
file: Uri, file: Uri,
console: MutableList<String>, console: MutableList<String>,
logs: MutableList<String>, logs: MutableList<String>
private val resultListener: FlashResultListener
) : MagiskInstallImpl(file, console, logs) { ) : MagiskInstallImpl(file, console, logs) {
override fun onResult(success: Boolean) { override suspend fun exec(): Boolean {
val success = super.exec()
if (success) { if (success) {
console.add("- All done!") console.add("- All done!")
} else { } else {
Shell.sh("rm -rf $installDir").submit() Shell.sh("rm -rf $installDir").submit()
console.add("! Installation failed") console.add("! Installation failed")
} }
resultListener.onResult(success) return success
} }
class Patch( class Patch(
file: Uri, file: Uri,
private val uri: Uri, private val uri: Uri,
console: MutableList<String>, console: MutableList<String>,
logs: MutableList<String>, logs: MutableList<String>
resultListener: FlashResultListener ) : MagiskInstaller(file, console, logs) {
) : MagiskInstaller(file, console, logs, resultListener) {
override fun operations() = doPatchFile(uri) override fun operations() = doPatchFile(uri)
} }
class SecondSlot( class SecondSlot(
file: Uri, file: Uri,
console: MutableList<String>, console: MutableList<String>,
logs: MutableList<String>, logs: MutableList<String>
resultListener: FlashResultListener ) : MagiskInstaller(file, console, logs) {
) : MagiskInstaller(file, console, logs, resultListener) {
override fun operations() = secondSlot() override fun operations() = secondSlot()
} }
class Direct( class Direct(
file: Uri, file: Uri,
console: MutableList<String>, console: MutableList<String>,
logs: MutableList<String>, logs: MutableList<String>
resultListener: FlashResultListener ) : MagiskInstaller(file, console, logs) {
) : MagiskInstaller(file, console, logs, resultListener) {
override fun operations() = direct() override fun operations() = direct()
} }
...@@ -445,7 +433,8 @@ class EnvFixTask( ...@@ -445,7 +433,8 @@ class EnvFixTask(
) : MagiskInstallImpl() { ) : MagiskInstallImpl() {
override fun operations() = fixEnv(zip) override fun operations() = fixEnv(zip)
override fun onResult(success: Boolean) { override suspend fun exec(): Boolean {
val success = super.exec()
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(EnvFixDialog.DISMISS)) LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(EnvFixDialog.DISMISS))
Utils.toast( Utils.toast(
if (success) R.string.reboot_delay_toast else R.string.setup_fail, if (success) R.string.reboot_delay_toast else R.string.setup_fail,
...@@ -453,5 +442,6 @@ class EnvFixTask( ...@@ -453,5 +442,6 @@ class EnvFixTask(
) )
if (success) if (success)
UiThreadHandler.handler.postDelayed(5000) { reboot() } UiThreadHandler.handler.postDelayed(5000) { reboot() }
return success
} }
} }
...@@ -5,11 +5,11 @@ import android.content.res.Resources ...@@ -5,11 +5,11 @@ import android.content.res.Resources
import android.net.Uri import android.net.Uri
import android.view.MenuItem import android.view.MenuItem
import androidx.databinding.ObservableArrayList import androidx.databinding.ObservableArrayList
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.tasks.FlashResultListener import com.topjohnwu.magisk.core.tasks.FlashZip
import com.topjohnwu.magisk.core.tasks.Flashing
import com.topjohnwu.magisk.core.tasks.MagiskInstaller import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.view.Notifications import com.topjohnwu.magisk.core.view.Notifications
import com.topjohnwu.magisk.extensions.* import com.topjohnwu.magisk.extensions.*
...@@ -21,13 +21,14 @@ import com.topjohnwu.magisk.ui.base.diffListOf ...@@ -21,13 +21,14 @@ import com.topjohnwu.magisk.ui.base.diffListOf
import com.topjohnwu.magisk.ui.base.itemBindingOf import com.topjohnwu.magisk.ui.base.itemBindingOf
import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.util.* import java.util.*
class FlashViewModel( class FlashViewModel(
args: FlashFragmentArgs, args: FlashFragmentArgs,
private val resources: Resources private val resources: Resources
) : BaseViewModel(), FlashResultListener { ) : BaseViewModel() {
val showReboot = KObservableField(Shell.rootAccess()) val showReboot = KObservableField(Shell.rootAccess())
val behaviorText = KObservableField(resources.getString(R.string.flashing)) val behaviorText = KObservableField(resources.getString(R.string.flashing))
...@@ -51,30 +52,36 @@ class FlashViewModel( ...@@ -51,30 +52,36 @@ class FlashViewModel(
} }
private fun startFlashing(installer: Uri, uri: Uri?, action: String) { private fun startFlashing(installer: Uri, uri: Uri?, action: String) {
when (action) { viewModelScope.launch {
Const.Value.FLASH_ZIP -> { val result = when (action) {
Flashing.Install(installer, outItems, logItems, this).exec() Const.Value.FLASH_ZIP -> {
} FlashZip(installer, outItems, logItems).exec()
Const.Value.UNINSTALL -> { }
showReboot.value = false Const.Value.UNINSTALL -> {
Flashing.Uninstall(installer, outItems, logItems, this).exec() showReboot.value = false
} FlashZip.Uninstall(installer, outItems, logItems).exec()
Const.Value.FLASH_MAGISK -> { }
MagiskInstaller.Direct(installer, outItems, logItems, this).exec() Const.Value.FLASH_MAGISK -> {
} MagiskInstaller.Direct(installer, outItems, logItems).exec()
Const.Value.FLASH_INACTIVE_SLOT -> { }
MagiskInstaller.SecondSlot(installer, outItems, logItems, this).exec() Const.Value.FLASH_INACTIVE_SLOT -> {
} MagiskInstaller.SecondSlot(installer, outItems, logItems).exec()
Const.Value.PATCH_FILE -> { }
uri ?: return Const.Value.PATCH_FILE -> {
showReboot.value = false uri ?: return@launch
MagiskInstaller.Patch(installer, uri, outItems, logItems, this).exec() showReboot.value = false
MagiskInstaller.Patch(installer, uri, outItems, logItems).exec()
}
else -> {
back()
return@launch
}
} }
else -> back() onResult(result)
} }
} }
override fun onResult(success: Boolean) { private fun onResult(success: Boolean) {
state = if (success) State.LOADED else State.LOADING_FAILED state = if (success) State.LOADED else State.LOADING_FAILED
behaviorText.value = when { behaviorText.value = when {
success -> resources.getString(R.string.done) success -> resources.getString(R.string.done)
......
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