From 820427e93b0161b1ae8aa5e6a3dea889f5442d1e Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Mon, 6 Jul 2020 22:30:21 -0700 Subject: [PATCH] Have some fun with Kotlin Coroutines --- .../magisk/core/download/DownloadService.kt | 6 +- .../topjohnwu/magisk/core/tasks/FlashZip.kt | 56 +++++++++++++------ .../topjohnwu/magisk/core/tasks/Flashing.kt | 51 ----------------- .../magisk/core/tasks/MagiskInstaller.kt | 44 ++++++--------- .../magisk/ui/flash/FlashViewModel.kt | 53 ++++++++++-------- 5 files changed, 91 insertions(+), 119 deletions(-) delete mode 100644 app/src/main/java/com/topjohnwu/magisk/core/tasks/Flashing.kt 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 7494a0e7b..ce57ccc60 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 @@ -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) diff --git a/app/src/main/java/com/topjohnwu/magisk/core/tasks/FlashZip.kt b/app/src/main/java/com/topjohnwu/magisk/core/tasks/FlashZip.kt index c46fef2d4..91f0570c2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/tasks/FlashZip.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/tasks/FlashZip.kt @@ -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, private val logs: MutableList -) : 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, + log: MutableList + ) : 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 + } + } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/tasks/Flashing.kt b/app/src/main/java/com/topjohnwu/magisk/core/tasks/Flashing.kt deleted file mode 100644 index 99a9fe1e1..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/core/tasks/Flashing.kt +++ /dev/null @@ -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, - log: MutableList, - 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, - log: MutableList, - resultListener: FlashResultListener - ) : Flashing(uri, console, log, resultListener) - - class Uninstall( - uri: Uri, - console: MutableList, - log: MutableList, - 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) - } - } - -} diff --git a/app/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt b/app/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt index 11f485f56..9290195c4 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt @@ -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, - logs: MutableList, - private val resultListener: FlashResultListener + logs: MutableList ) : 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, - logs: MutableList, - resultListener: FlashResultListener - ) : MagiskInstaller(file, console, logs, resultListener) { + logs: MutableList + ) : MagiskInstaller(file, console, logs) { override fun operations() = doPatchFile(uri) } class SecondSlot( file: Uri, console: MutableList, - logs: MutableList, - resultListener: FlashResultListener - ) : MagiskInstaller(file, console, logs, resultListener) { + logs: MutableList + ) : MagiskInstaller(file, console, logs) { override fun operations() = secondSlot() } class Direct( file: Uri, console: MutableList, - logs: MutableList, - resultListener: FlashResultListener - ) : MagiskInstaller(file, console, logs, resultListener) { + logs: MutableList + ) : 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 } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt index d628f0a86..097453665 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt @@ -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)