Have some fun with Kotlin Coroutines

This commit is contained in:
topjohnwu 2020-07-06 22:30:21 -07:00
parent 89e11c9cc8
commit 820427e93b
5 changed files with 91 additions and 119 deletions

View File

@ -21,6 +21,8 @@ import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.APKInstall
import io.reactivex.Completable
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.koin.core.get
import java.io.File
import kotlin.random.Random.Default.nextInt
@ -47,7 +49,9 @@ open class DownloadService : RemoteFileService() {
) = when (val conf = subject.configuration) {
Uninstall -> FlashFragment.uninstall(subject.file, id)
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 Flash -> FlashFragment.flash(subject.file, conf is Secondary, id)

View File

@ -2,25 +2,29 @@ package com.topjohnwu.magisk.core.tasks
import android.content.Context
import android.net.Uri
import androidx.core.os.postDelayed
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.utils.unzip
import com.topjohnwu.magisk.extensions.fileName
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.extensions.readUri
import com.topjohnwu.magisk.extensions.subscribeK
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.FileNotFoundException
import java.io.IOException
abstract class FlashZip(
open class FlashZip(
private val mUri: Uri,
private val console: 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 {
if (!exists()) mkdirs()
}
@ -79,19 +83,37 @@ abstract class FlashZip(
.exec().isSuccess
}
fun exec() = Single
.fromCallable {
runCatching {
flash()
}.getOrElse {
it.printStackTrace()
open suspend fun exec() = withContext(Dispatchers.IO) {
try {
if (!flash()) {
console.add("! Installation failed")
false
}.apply {
Shell.su("cd /", "rm -rf ${tmpFile.parent} ${Const.TMP_FOLDER_PATH}")
.submit()
} else {
true
}
} 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
}
}
}

View File

@ -1,51 +0,0 @@
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)
}
}
}

View File

@ -5,7 +5,6 @@ import android.content.Intent
import android.net.Uri
import android.os.Build
import android.widget.Toast
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import androidx.core.net.toUri
import androidx.core.os.postDelayed
@ -26,7 +25,8 @@ import com.topjohnwu.superuser.internal.UiThreadHandler
import com.topjohnwu.superuser.io.SuFile
import com.topjohnwu.superuser.io.SuFileInputStream
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.TarHeader
import org.kamranzafar.jtar.TarInputStream
@ -43,14 +43,7 @@ import java.nio.ByteBuffer
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
interface FlashResultListener {
@MainThread
fun onResult(success: Boolean)
}
abstract class MagiskInstallImpl : FlashResultListener, KoinComponent {
abstract class MagiskInstallImpl : KoinComponent {
protected lateinit var installDir: File
private lateinit var srcBoot: String
@ -388,53 +381,48 @@ abstract class MagiskInstallImpl : FlashResultListener, KoinComponent {
@WorkerThread
protected abstract fun operations(): Boolean
fun exec() {
Single.fromCallable { operations() }.subscribeK { onResult(it) }
}
open suspend fun exec() = withContext(Dispatchers.IO) { operations() }
}
sealed class MagiskInstaller(
file: Uri,
console: MutableList<String>,
logs: MutableList<String>,
private val resultListener: FlashResultListener
logs: MutableList<String>
) : MagiskInstallImpl(file, console, logs) {
override fun onResult(success: Boolean) {
override suspend fun exec(): Boolean {
val success = super.exec()
if (success) {
console.add("- All done!")
} else {
Shell.sh("rm -rf $installDir").submit()
console.add("! Installation failed")
}
resultListener.onResult(success)
return success
}
class Patch(
file: Uri,
private val uri: Uri,
console: MutableList<String>,
logs: MutableList<String>,
resultListener: FlashResultListener
) : MagiskInstaller(file, console, logs, resultListener) {
logs: MutableList<String>
) : MagiskInstaller(file, console, logs) {
override fun operations() = doPatchFile(uri)
}
class SecondSlot(
file: Uri,
console: MutableList<String>,
logs: MutableList<String>,
resultListener: FlashResultListener
) : MagiskInstaller(file, console, logs, resultListener) {
logs: MutableList<String>
) : MagiskInstaller(file, console, logs) {
override fun operations() = secondSlot()
}
class Direct(
file: Uri,
console: MutableList<String>,
logs: MutableList<String>,
resultListener: FlashResultListener
) : MagiskInstaller(file, console, logs, resultListener) {
logs: MutableList<String>
) : MagiskInstaller(file, console, logs) {
override fun operations() = direct()
}
@ -445,7 +433,8 @@ class EnvFixTask(
) : MagiskInstallImpl() {
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))
Utils.toast(
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
@ -453,5 +442,6 @@ class EnvFixTask(
)
if (success)
UiThreadHandler.handler.postDelayed(5000) { reboot() }
return success
}
}

View File

@ -5,11 +5,11 @@ import android.content.res.Resources
import android.net.Uri
import android.view.MenuItem
import androidx.databinding.ObservableArrayList
import androidx.lifecycle.viewModelScope
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.FlashZip
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.view.Notifications
import com.topjohnwu.magisk.extensions.*
@ -21,13 +21,14 @@ import com.topjohnwu.magisk.ui.base.diffListOf
import com.topjohnwu.magisk.ui.base.itemBindingOf
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch
import java.io.File
import java.util.*
class FlashViewModel(
args: FlashFragmentArgs,
private val resources: Resources
) : BaseViewModel(), FlashResultListener {
) : BaseViewModel() {
val showReboot = KObservableField(Shell.rootAccess())
val behaviorText = KObservableField(resources.getString(R.string.flashing))
@ -51,30 +52,36 @@ class FlashViewModel(
}
private fun startFlashing(installer: Uri, uri: Uri?, action: String) {
when (action) {
Const.Value.FLASH_ZIP -> {
Flashing.Install(installer, outItems, logItems, this).exec()
viewModelScope.launch {
val result = when (action) {
Const.Value.FLASH_ZIP -> {
FlashZip(installer, outItems, logItems).exec()
}
Const.Value.UNINSTALL -> {
showReboot.value = false
FlashZip.Uninstall(installer, outItems, logItems).exec()
}
Const.Value.FLASH_MAGISK -> {
MagiskInstaller.Direct(installer, outItems, logItems).exec()
}
Const.Value.FLASH_INACTIVE_SLOT -> {
MagiskInstaller.SecondSlot(installer, outItems, logItems).exec()
}
Const.Value.PATCH_FILE -> {
uri ?: return@launch
showReboot.value = false
MagiskInstaller.Patch(installer, uri, outItems, logItems).exec()
}
else -> {
back()
return@launch
}
}
Const.Value.UNINSTALL -> {
showReboot.value = false
Flashing.Uninstall(installer, outItems, logItems, this).exec()
}
Const.Value.FLASH_MAGISK -> {
MagiskInstaller.Direct(installer, outItems, logItems, this).exec()
}
Const.Value.FLASH_INACTIVE_SLOT -> {
MagiskInstaller.SecondSlot(installer, outItems, logItems, this).exec()
}
Const.Value.PATCH_FILE -> {
uri ?: return
showReboot.value = false
MagiskInstaller.Patch(installer, uri, outItems, logItems, this).exec()
}
else -> back()
onResult(result)
}
}
override fun onResult(success: Boolean) {
private fun onResult(success: Boolean) {
state = if (success) State.LOADED else State.LOADING_FAILED
behaviorText.value = when {
success -> resources.getString(R.string.done)