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.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() {
) = 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)

View File

@ -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(
.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
}
}
} }

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.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
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
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 {
@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(
) : 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(
) )
if (success) if (success)
UiThreadHandler.handler.postDelayed(5000) { reboot() } UiThreadHandler.handler.postDelayed(5000) { reboot() }
return success
} }
} }

View File

@ -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
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(
} }
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
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 -> { onResult(result)
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()
} }
} }
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)