diff --git a/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java b/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java index 47383aa05..181146e1d 100644 --- a/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java +++ b/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java @@ -67,8 +67,6 @@ public final class APKInstall { public interface Session { // @WorkerThread OutputStream openStream(Context context) throws IOException; - // @WorkerThread - void install(Context context, File apk) throws IOException; // @WorkerThread @Nullable Intent waitIntent(); } @@ -167,13 +165,5 @@ public final class APKInstall { } }; } - - @Override - public void install(Context context, File apk) throws IOException { - try (var src = new FileInputStream(apk); - var out = openStream(context)) { - transfer(src, out); - } - } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/Config.kt b/app/src/main/java/com/topjohnwu/magisk/core/Config.kt index a6effec65..dd1d25180 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/Config.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/Config.kt @@ -12,8 +12,11 @@ import com.topjohnwu.magisk.core.repository.DBConfig import com.topjohnwu.magisk.core.repository.PreferenceConfig import com.topjohnwu.magisk.core.utils.refreshLocale import com.topjohnwu.magisk.ui.theme.Theme +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.runBlocking import java.io.File +import java.io.IOException object Config : PreferenceConfig, DBConfig { @@ -171,8 +174,14 @@ object Config : PreferenceConfig, DBConfig { fun load(pkg: String?) { // Only try to load prefs when fresh install and a previous package name is set - if (pkg != null && prefs.all.isEmpty()) runCatching { - context.contentResolver.openInputStream(Provider.preferencesUri(pkg))?.writeTo(prefsFile) + if (pkg != null && prefs.all.isEmpty()) { + runBlocking { + try { + context.contentResolver + .openInputStream(Provider.preferencesUri(pkg)) + ?.writeTo(prefsFile, dispatcher = Dispatchers.Unconfined) + } catch (ignored: IOException) {} + } return } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadEngine.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadEngine.kt index 69e0c19eb..25834f462 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadEngine.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadEngine.kt @@ -28,6 +28,7 @@ import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.intent import com.topjohnwu.magisk.core.isRunningAsStub import com.topjohnwu.magisk.core.ktx.copyAndClose +import com.topjohnwu.magisk.core.ktx.copyAll import com.topjohnwu.magisk.core.ktx.forEach import com.topjohnwu.magisk.core.ktx.selfLaunchIntent import com.topjohnwu.magisk.core.ktx.set @@ -178,7 +179,7 @@ class DownloadEngine( notifyFinish(subject) } subject.postDownload?.invoke() - } catch (e: Exception) { + } catch (e: IOException) { Timber.e(e) notifyFail(subject) } @@ -269,8 +270,8 @@ class DownloadEngine( return n } - private fun handleApp(stream: InputStream, subject: Subject.App) { - fun writeTee(output: OutputStream) { + private suspend fun handleApp(stream: InputStream, subject: Subject.App) { + suspend fun writeTee(output: OutputStream) { val uri = MediaStoreUtils.getFile("${subject.title}.apk").uri val external = uri.outputStream() stream.copyAndClose(TeeOutputStream(external, output)) @@ -326,7 +327,7 @@ class DownloadEngine( } } - private fun handleModule(src: InputStream, file: Uri) { + private suspend fun handleModule(src: InputStream, file: Uri) { val input = ZipInputStream(src.buffered()) val output = ZipOutputStream(file.outputStream().buffered()) @@ -336,7 +337,7 @@ class DownloadEngine( zout.putNextEntry(ZipEntry("META-INF/com/google/")) zout.putNextEntry(ZipEntry("META-INF/com/google/android/")) zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary")) - context.assets.open("module_installer.sh").copyTo(zout) + context.assets.open("module_installer.sh").copyAll(zout) zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script")) zout.write("#MAGISK\n".toByteArray()) @@ -346,7 +347,7 @@ class DownloadEngine( if (path.isNotEmpty() && !path.startsWith("META-INF")) { zout.putNextEntry(ZipEntry(path)) if (!entry.isDirectory) { - zin.copyTo(zout) + zin.copyAll(zout) } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/ktx/XJVM.kt b/app/src/main/java/com/topjohnwu/magisk/core/ktx/XJVM.kt index c95945b04..0c70309b0 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/ktx/XJVM.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/ktx/XJVM.kt @@ -2,10 +2,15 @@ package com.topjohnwu.magisk.core.ktx import androidx.collection.SparseArrayCompat import com.topjohnwu.magisk.core.utils.currentLocale +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.isActive +import kotlinx.coroutines.withContext import java.io.File +import java.io.IOException import java.io.InputStream import java.io.OutputStream import java.lang.reflect.Field @@ -35,9 +40,38 @@ inline fun withStreams( } } -fun InputStream.copyAndClose(out: OutputStream) = withStreams(this, out) { i, o -> i.copyTo(o) } +@Throws(IOException::class) +suspend fun InputStream.copyAll( + out: OutputStream, + bufferSize: Int = DEFAULT_BUFFER_SIZE, + dispatcher: CoroutineDispatcher = Dispatchers.IO +): Long { + return withContext(dispatcher) { + var bytesCopied: Long = 0 + val buffer = ByteArray(bufferSize) + var bytes = read(buffer) + while (isActive && bytes >= 0) { + out.write(buffer, 0, bytes) + bytesCopied += bytes + bytes = read(buffer) + } + bytesCopied + } +} -fun InputStream.writeTo(file: File) = copyAndClose(file.outputStream()) +@Throws(IOException::class) +suspend inline fun InputStream.copyAndClose( + out: OutputStream, + bufferSize: Int = DEFAULT_BUFFER_SIZE, + dispatcher: CoroutineDispatcher = Dispatchers.IO +) = withStreams(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) } + +@Throws(IOException::class) +suspend inline fun InputStream.writeTo( + file: File, + bufferSize: Int = DEFAULT_BUFFER_SIZE, + dispatcher: CoroutineDispatcher = Dispatchers.IO +) = copyAndClose(file.outputStream(), bufferSize, dispatcher) operator fun SparseArrayCompat.set(key: Int, value: E) { put(key, value) 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 303c9a8bd..46d87853a 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 @@ -26,7 +26,7 @@ open class FlashZip( private lateinit var zipFile: File @Throws(IOException::class) - private fun flash(): Boolean { + private suspend fun flash(): Boolean { installDir.deleteRecursively() installDir.mkdirs() @@ -47,13 +47,13 @@ open class FlashZip( } } - val isValid = runCatching { + val isValid = try { zipFile.unzip(installDir, "META-INF/com/google/android", true) val script = File(installDir, "updater-script") script.readText().contains("#MAGISK") - }.getOrElse { + } catch (e: IOException) { console.add("! Unzip error") - throw it + throw e } if (!isValid) { diff --git a/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt b/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt index cd4f82d64..3f1ef51bd 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt @@ -12,6 +12,7 @@ import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Provider import com.topjohnwu.magisk.core.ktx.await +import com.topjohnwu.magisk.core.ktx.copyAndClose import com.topjohnwu.magisk.core.ktx.toast import com.topjohnwu.magisk.core.ktx.writeTo import com.topjohnwu.magisk.core.utils.AXML @@ -168,7 +169,7 @@ object HideAPK { activity.finish() } - private fun patchAndHide(activity: Activity, label: String, onFailure: Runnable): Boolean { + private suspend fun patchAndHide(activity: Activity, label: String, onFailure: Runnable): Boolean { val stub = File(activity.cacheDir, "stub.apk") try { activity.assets.open("stub.apk").writeTo(stub) @@ -195,7 +196,7 @@ object HideAPK { if (Shell.cmd(cmd).exec().isSuccess) return true try { - session.install(activity, repack) + repack.inputStream().copyAndClose(session.openStream(activity)) } catch (e: IOException) { Timber.e(e) return false @@ -244,7 +245,7 @@ object HideAPK { if (Shell.cmd(cmd).await().isSuccess) return val success = withContext(Dispatchers.IO) { try { - session.install(activity, apk) + apk.inputStream().copyAndClose(session.openStream(activity)) } catch (e: IOException) { Timber.e(e) return@withContext false 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 0302c4573..a565d3978 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 @@ -18,6 +18,7 @@ import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.isRunningAsStub import com.topjohnwu.magisk.core.ktx.copyAndClose +import com.topjohnwu.magisk.core.ktx.copyAll import com.topjohnwu.magisk.core.ktx.reboot import com.topjohnwu.magisk.core.ktx.toast import com.topjohnwu.magisk.core.ktx.writeTo @@ -93,7 +94,7 @@ abstract class MagiskInstallImpl protected constructor( return true } - private fun extractFiles(): Boolean { + private suspend fun extractFiles(): Boolean { console.add("- Device platform: ${Const.CPU_ABI}") console.add("- Installing: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") @@ -174,7 +175,7 @@ abstract class MagiskInstallImpl protected constructor( return true } - private fun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyTo(it) } + private suspend fun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyAll(it) } private fun newTarEntry(name: String, size: Long): TarEntry { console.add("-- Writing: $name") @@ -191,7 +192,7 @@ abstract class MagiskInstallImpl protected constructor( private class NoBootException : IOException() @Throws(IOException::class) - private fun processTar(tarIn: TarInputStream, tarOut: TarOutputStream): ExtendedFile { + private suspend fun processTar(tarIn: TarInputStream, tarOut: TarOutputStream): ExtendedFile { console.add("- Processing tar file") lateinit var entry: TarEntry @@ -228,7 +229,7 @@ abstract class MagiskInstallImpl protected constructor( } else { console.add("-- Copying: ${entry.name}") tarOut.putNextEntry(entry) - tarIn.copyTo(tarOut, bufferSize = 1024 * 1024) + tarIn.copyAll(tarOut, bufferSize = 1024 * 1024) } } @@ -236,10 +237,10 @@ abstract class MagiskInstallImpl protected constructor( val initBoot = installDir.getChildFile("init_boot.img") val recovery = installDir.getChildFile("recovery.img") - fun ExtendedFile.copyToTar() { + suspend fun ExtendedFile.copyToTar() { newInputStream().use { tarOut.putNextEntry(newTarEntry(name, length())) - it.copyTo(tarOut) + it.copyAll(tarOut) } delete() } @@ -273,7 +274,7 @@ abstract class MagiskInstallImpl protected constructor( } @Throws(IOException::class) - private fun processZip(zipIn: ZipInputStream): ExtendedFile { + private suspend fun processZip(zipIn: ZipInputStream): ExtendedFile { console.add("- Processing zip file") val boot = installDir.getChildFile("boot.img") val initBoot = installDir.getChildFile("init_boot.img") @@ -373,7 +374,7 @@ abstract class MagiskInstallImpl protected constructor( } } - private fun handleFile(uri: Uri): Boolean { + private suspend fun handleFile(uri: Uri): Boolean { val outStream: OutputStream val outFile: MediaStoreUtils.UriFile @@ -510,7 +511,7 @@ abstract class MagiskInstallImpl protected constructor( private fun flashBoot() = "direct_install $installDir $srcBoot".sh().isSuccess - private fun postOTA(): Boolean { + private suspend fun postOTA(): Boolean { try { val bootctl = File.createTempFile("bootctl", null, context.cacheDir) context.assets.open("bootctl").writeTo(bootctl) @@ -534,14 +535,14 @@ abstract class MagiskInstallImpl protected constructor( private fun String.fsh() = ShellUtils.fastCmd(shell, this) private fun Array.fsh() = ShellUtils.fastCmd(shell, *this) - protected fun patchFile(file: Uri) = extractFiles() && handleFile(file) + protected suspend fun patchFile(file: Uri) = extractFiles() && handleFile(file) - protected fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot() + protected suspend fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot() - protected fun secondSlot() = + protected suspend fun secondSlot() = findSecondary() && extractFiles() && patchBoot() && flashBoot() && postOTA() - protected fun fixEnv() = extractFiles() && "fix_env $installDir".sh().isSuccess + protected suspend fun fixEnv() = extractFiles() && "fix_env $installDir".sh().isSuccess protected fun uninstall() = "run_uninstaller $AppApkPath".sh().isSuccess diff --git a/app/src/main/java/com/topjohnwu/magisk/core/utils/MediaStoreUtils.kt b/app/src/main/java/com/topjohnwu/magisk/core/utils/MediaStoreUtils.kt index e2acff636..5ad0a565d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/utils/MediaStoreUtils.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/utils/MediaStoreUtils.kt @@ -15,9 +15,6 @@ import com.topjohnwu.magisk.core.di.AppContext import java.io.File import java.io.FileNotFoundException import java.io.IOException -import java.io.OutputStream -import java.security.MessageDigest -import kotlin.experimental.and @Suppress("DEPRECATION") object MediaStoreUtils { @@ -120,24 +117,6 @@ object MediaStoreUtils { return this.toString() } - fun Uri.checkSum(alg: String, reference: String) = runCatching { - this.inputStream().use { - val digest = MessageDigest.getInstance(alg) - it.copyTo(object : OutputStream() { - override fun write(b: Int) { - digest.update(b.toByte()) - } - - override fun write(b: ByteArray, off: Int, len: Int) { - digest.update(b, off, len) - } - }) - val sb = StringBuilder() - digest.digest().forEach { b -> sb.append("%02x".format(b and 0xff.toByte())) } - sb.toString() == reference - } - }.getOrElse { false } - interface UriFile { val uri: Uri fun delete(): Boolean diff --git a/app/src/main/java/com/topjohnwu/magisk/core/utils/ShellInit.kt b/app/src/main/java/com/topjohnwu/magisk/core/utils/ShellInit.kt index 383b08b9c..74fb1e31b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/utils/ShellInit.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/utils/ShellInit.kt @@ -13,6 +13,8 @@ import com.topjohnwu.magisk.core.ktx.rawResource import com.topjohnwu.magisk.core.ktx.writeTo import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.ShellUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import java.io.File import java.util.jar.JarFile @@ -34,7 +36,9 @@ class ShellInit : Shell.Initializer() { val bb = jar.getJarEntry("lib/${Const.CPU_ABI}/libbusybox.so") localBB = context.deviceProtectedContext.cachedFile("busybox") localBB.delete() - jar.getInputStream(bb).writeTo(localBB) + runBlocking { + jar.getInputStream(bb).writeTo(localBB, dispatcher = Dispatchers.Unconfined) + } localBB.setExecutable(true) } else { localBB = File(context.applicationInfo.nativeLibraryDir, "libbusybox.so") diff --git a/app/src/main/java/com/topjohnwu/magisk/core/utils/ZipUtils.kt b/app/src/main/java/com/topjohnwu/magisk/core/utils/ZipUtils.kt index 2eee01a97..1f2bc68e8 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/utils/ZipUtils.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/utils/ZipUtils.kt @@ -1,5 +1,6 @@ package com.topjohnwu.magisk.core.utils +import com.topjohnwu.magisk.core.ktx.copyAll import java.io.File import java.io.IOException import java.io.InputStream @@ -7,14 +8,14 @@ import java.util.zip.ZipEntry import java.util.zip.ZipInputStream @Throws(IOException::class) -fun File.unzip(folder: File, path: String = "", junkPath: Boolean = false) { +suspend fun File.unzip(folder: File, path: String = "", junkPath: Boolean = false) { inputStream().buffered().use { it.unzip(folder, path, junkPath) } } @Throws(IOException::class) -fun InputStream.unzip(folder: File, path: String, junkPath: Boolean) { +suspend fun InputStream.unzip(folder: File, path: String, junkPath: Boolean) { try { val zin = ZipInputStream(this) var entry: ZipEntry @@ -34,7 +35,7 @@ fun InputStream.unzip(folder: File, path: String, junkPath: Boolean) { if (!it.exists()) it.mkdirs() } - dest.outputStream().use { out -> zin.copyTo(out) } + dest.outputStream().use { out -> zin.copyAll(out) } } } catch (e: IllegalArgumentException) { throw IOException(e)