From 8ca188f4d4142cecb3452abda0ae095acdaf3df3 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Sat, 20 Jul 2019 21:04:06 -0700 Subject: [PATCH] Stream and process module zips --- app/src/main/AndroidManifest.xml | 4 +- app/src/main/java/a/j.java | 4 +- app/src/main/java/a/k.kt | 5 - .../java/com/topjohnwu/magisk/ClassMap.kt | 4 +- .../topjohnwu/magisk/di/ApplicationModule.kt | 4 - .../model/download/DownloadModuleService.java | 156 ------------------ .../magisk/model/download/ModuleProcessor.kt | 44 +++++ .../model/download/ModuleTransformer.kt | 68 -------- .../model/download/RemoteFileService.kt | 28 ++-- .../topjohnwu/magisk/ui/home/HomeFragment.kt | 4 +- .../com/topjohnwu/magisk/utils/XAndroid.kt | 6 +- .../java/com/topjohnwu/magisk/utils/XJava.kt | 10 +- .../com/topjohnwu/magisk/utils/XNetwork.kt | 32 +++- 13 files changed, 99 insertions(+), 270 deletions(-) delete mode 100644 app/src/main/java/a/k.kt delete mode 100644 app/src/main/java/com/topjohnwu/magisk/model/download/DownloadModuleService.java create mode 100644 app/src/main/java/com/topjohnwu/magisk/model/download/ModuleProcessor.kt delete mode 100644 app/src/main/java/com/topjohnwu/magisk/model/download/ModuleTransformer.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3416c06b7..0512f7bb9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -65,9 +65,7 @@ - - diff --git a/app/src/main/java/a/j.java b/app/src/main/java/a/j.java index 937100012..a7be03859 100644 --- a/app/src/main/java/a/j.java +++ b/app/src/main/java/a/j.java @@ -1,7 +1,7 @@ package a; -import com.topjohnwu.magisk.model.download.DownloadModuleService; +import com.topjohnwu.magisk.model.download.DownloadService; -public class j extends DownloadModuleService { +public class j extends DownloadService { /* stub */ } diff --git a/app/src/main/java/a/k.kt b/app/src/main/java/a/k.kt deleted file mode 100644 index 6d450e761..000000000 --- a/app/src/main/java/a/k.kt +++ /dev/null @@ -1,5 +0,0 @@ -package a - -import com.topjohnwu.magisk.model.download.DownloadService - -class k : DownloadService() \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/ClassMap.kt b/app/src/main/java/com/topjohnwu/magisk/ClassMap.kt index 5f5f6afd7..2d8aef096 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ClassMap.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ClassMap.kt @@ -1,6 +1,5 @@ package com.topjohnwu.magisk -import com.topjohnwu.magisk.model.download.DownloadModuleService import com.topjohnwu.magisk.model.download.DownloadService import com.topjohnwu.magisk.model.receiver.GeneralReceiver import com.topjohnwu.magisk.model.update.UpdateCheckService @@ -17,8 +16,7 @@ object ClassMap { FlashActivity::class.java to a.f::class.java, UpdateCheckService::class.java to a.g::class.java, GeneralReceiver::class.java to a.h::class.java, - DownloadModuleService::class.java to a.j::class.java, - DownloadService::class.java to a.k::class.java, + DownloadService::class.java to a.j::class.java, SuRequestActivity::class.java to a.m::class.java ) diff --git a/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt b/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt index 817cbf188..ebdfd1025 100644 --- a/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt +++ b/app/src/main/java/com/topjohnwu/magisk/di/ApplicationModule.kt @@ -4,8 +4,6 @@ import android.content.Context import androidx.preference.PreferenceManager import com.skoumal.teanity.rxbus.RxBus import com.topjohnwu.magisk.App -import com.topjohnwu.magisk.model.download.ModuleTransformer -import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import org.koin.dsl.module @@ -17,6 +15,4 @@ val applicationModule = module { factory(Protected) { get().protectedContext } single(SUTimeout) { get(Protected).getSharedPreferences("su_timeout", 0) } single { PreferenceManager.getDefaultSharedPreferences(get(Protected)) } - - factory { (subject: DownloadSubject) -> ModuleTransformer(get(), subject) } } \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/model/download/DownloadModuleService.java b/app/src/main/java/com/topjohnwu/magisk/model/download/DownloadModuleService.java deleted file mode 100644 index ceb3ea494..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/model/download/DownloadModuleService.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.topjohnwu.magisk.model.download; - -import android.app.PendingIntent; -import android.app.Service; -import android.content.Intent; -import android.net.Uri; -import android.os.IBinder; - -import androidx.annotation.Nullable; - -import com.topjohnwu.magisk.App; -import com.topjohnwu.magisk.ClassMap; -import com.topjohnwu.magisk.Const; -import com.topjohnwu.magisk.model.entity.Repo; -import com.topjohnwu.magisk.ui.flash.FlashActivity; -import com.topjohnwu.magisk.view.Notifications; -import com.topjohnwu.magisk.view.ProgressNotification; -import com.topjohnwu.net.Networking; -import com.topjohnwu.superuser.Shell; -import com.topjohnwu.superuser.ShellUtils; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -public class DownloadModuleService extends Service { - - private List notifications; - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public void onCreate() { - notifications = new ArrayList<>(); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Shell.EXECUTOR.execute(() -> { - Repo repo = intent.getParcelableExtra("repo"); - boolean install = intent.getBooleanExtra("install", false); - dlProcessInstall(repo, install); - }); - return START_REDELIVER_INTENT; - } - - @Override - public synchronized void onTaskRemoved(Intent rootIntent) { - for (ProgressNotification n : notifications) { - Notifications.mgr.cancel(n.hashCode()); - } - notifications.clear(); - } - - private synchronized void addNotification(ProgressNotification n) { - if (notifications.isEmpty()) { - // Start foreground - startForeground(n.hashCode(), n.getNotification()); - } - notifications.add(n); - } - - private synchronized void removeNotification(ProgressNotification n) { - notifications.remove(n); - if (notifications.isEmpty()) { - // No more tasks, stop service - stopForeground(true); - stopSelf(); - } else { - // Pick another notification as our foreground notification - n = notifications.get(0); - startForeground(n.hashCode(), n.getNotification()); - } - } - - private void dlProcessInstall(Repo repo, boolean install) { - File output = new File(Const.EXTERNAL_PATH, repo.getDownloadFilename()); - ProgressNotification progress = new ProgressNotification(output.getName()); - addNotification(progress); - try { - InputStream in = Networking.get(repo.getZipUrl()) - .setDownloadProgressListener(progress) - .execForInputStream().getResult(); - OutputStream out = new BufferedOutputStream(new FileOutputStream(output)); - processZip(in, out); - Intent intent = new Intent(this, ClassMap.get(FlashActivity.class)); - intent.setData(Uri.fromFile(output)) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - .putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP); - synchronized (getApplication()) { - if (install && App.foreground() != null && - !(App.foreground() instanceof FlashActivity)) { - /* Only start flashing if there is a foreground activity and the - * user is not also flashing another module at the same time */ - App.foreground().startActivity(intent); - } else { - /* Or else we preset a notification notifying that we are done */ - PendingIntent pi = PendingIntent.getActivity(this, progress.hashCode(), intent, - PendingIntent.FLAG_UPDATE_CURRENT); - progress.dlDone(pi); - } - } - } catch (Exception e) { - e.printStackTrace(); - progress.dlFail(); - } - removeNotification(progress); - } - - private void processZip(InputStream in, OutputStream out) - throws IOException { - try (ZipInputStream zin = new ZipInputStream(in); - ZipOutputStream zout = new ZipOutputStream(out)) { - - // Inject latest module-installer.sh as update-binary - zout.putNextEntry(new ZipEntry("META-INF/")); - zout.putNextEntry(new ZipEntry("META-INF/com/")); - zout.putNextEntry(new ZipEntry("META-INF/com/google/")); - zout.putNextEntry(new ZipEntry("META-INF/com/google/android/")); - zout.putNextEntry(new ZipEntry("META-INF/com/google/android/update-binary")); - try (InputStream update_bin = Networking.get(Const.Url.MODULE_INSTALLER) - .execForInputStream().getResult()) { - ShellUtils.pump(update_bin, zout); - } - zout.putNextEntry(new ZipEntry("META-INF/com/google/android/updater-script")); - zout.write("#MAGISK\n".getBytes("UTF-8")); - - int off = -1; - ZipEntry entry; - while ((entry = zin.getNextEntry()) != null) { - if (off < 0) - off = entry.getName().indexOf('/') + 1; - String path = entry.getName().substring(off); - if (path.isEmpty()) - continue; - if (path.startsWith("META-INF")) - continue; - zout.putNextEntry(new ZipEntry(path)); - if (!entry.isDirectory()) - ShellUtils.pump(zin, zout); - } - } - } -} diff --git a/app/src/main/java/com/topjohnwu/magisk/model/download/ModuleProcessor.kt b/app/src/main/java/com/topjohnwu/magisk/model/download/ModuleProcessor.kt new file mode 100644 index 000000000..0d1a23cb6 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/model/download/ModuleProcessor.kt @@ -0,0 +1,44 @@ +package com.topjohnwu.magisk.model.download + +import com.topjohnwu.magisk.utils.withStreams +import java.io.File +import java.io.InputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream + +fun InputStream.toModule(file: File, installer: InputStream) { + + val input = ZipInputStream(buffered()) + val output = ZipOutputStream(file.outputStream().buffered()) + + withStreams(input, output) { zin, zout -> + zout.putNextEntry(ZipEntry("META-INF/")) + zout.putNextEntry(ZipEntry("META-INF/com/")) + 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")) + installer.copyTo(zout) + + zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script")) + zout.write("#MAGISK\n".toByteArray(charset("UTF-8"))) + + var off = -1 + var entry: ZipEntry? = zin.nextEntry + while (entry != null) { + if (off < 0) { + off = entry.name.indexOf('/') + 1 + } + + val path = entry.name.substring(off) + if (path.isNotEmpty() && !path.startsWith("META-INF")) { + zout.putNextEntry(ZipEntry(path)) + if (!entry.isDirectory) { + zin.copyTo(zout) + } + } + + entry = zin.nextEntry + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/model/download/ModuleTransformer.kt b/app/src/main/java/com/topjohnwu/magisk/model/download/ModuleTransformer.kt deleted file mode 100644 index d1dbd7ef9..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/model/download/ModuleTransformer.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.topjohnwu.magisk.model.download - -import android.content.Context -import com.topjohnwu.magisk.model.entity.internal.DownloadSubject -import com.topjohnwu.magisk.utils.cachedFile -import com.topjohnwu.magisk.utils.withStreams -import java.io.File -import java.util.zip.ZipEntry -import java.util.zip.ZipInputStream -import java.util.zip.ZipOutputStream - -class ModuleTransformer( - private val context: Context, - subject: DownloadSubject -) { - - private val destination = context.cachedFile(subject.fileName) - - fun inject(file: File, installer: File): File { - return injectInternal(move(file), installer) - } - - // --- - - private fun injectInternal(file: File, installer: File): File { - val input = ZipInputStream(file.inputStream()) - val output = ZipOutputStream(destination.outputStream()) - - withStreams(input, output) { zin, zout -> - zout.putNextEntry(ZipEntry("META-INF/")) - zout.putNextEntry(ZipEntry("META-INF/com/")) - 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")) - installer.inputStream().copyTo(zout).also { zout.flush() } - - zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script")) - zout.write("#MAGISK\n".toByteArray(charset("UTF-8"))) - - var off = -1 - var entry: ZipEntry? = zin.nextEntry - while (entry != null) { - if (off < 0) { - off = entry.name.indexOf('/') + 1 - } - - val path = entry.name.substring(off) - if (path.isNotEmpty() && !path.startsWith("META-INF")) { - zout.putNextEntry(ZipEntry(path)) - if (!entry.isDirectory) { - zin.copyTo(zout).also { zout.flush() } - } - } - - entry = zin.nextEntry - } - } - - file.delete() - - return destination - } - - private fun move(file: File) = context.cachedFile("temp").apply { - file.renameTo(this) - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt b/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt index ac77544eb..31c7d7f60 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt @@ -9,18 +9,19 @@ import com.topjohnwu.magisk.R import com.topjohnwu.magisk.data.repository.FileRepository import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.* +import com.topjohnwu.magisk.utils.ProgInputStream +import com.topjohnwu.magisk.utils.cachedFile import com.topjohnwu.magisk.utils.firstMap -import com.topjohnwu.magisk.utils.writeToCachedFile +import com.topjohnwu.magisk.utils.writeTo import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.superuser.ShellUtils import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import okhttp3.ResponseBody -import org.koin.android.ext.android.get import org.koin.android.ext.android.inject -import org.koin.core.parameter.parametersOf import timber.log.Timber import java.io.File +import java.io.InputStream abstract class RemoteFileService : NotificationService() { @@ -73,19 +74,14 @@ abstract class RemoteFileService : NotificationService() { } private fun download(subject: DownloadSubject) = repo.downloadFile(subject.url) - .map { it.toFile(subject.hashCode(), subject.fileName) } + .map { it.toStream(subject.hashCode()) } .map { - when (subject) { - is Module -> { - update(subject.hashCode()) { - it.setContentText(getString(R.string.download_module)) - .setProgress(0, 0, true) - } - - get { parametersOf(subject) } - .inject(it, startInternal(Installer).blockingGet()) + cachedFile(subject.fileName).apply { + when (subject) { + is Module -> it.toModule(this, + repo.downloadFile(Const.Url.MODULE_INSTALLER).blockingGet().byteStream()) + else -> it.writeTo(this) } - else -> it } } @@ -95,11 +91,11 @@ abstract class RemoteFileService : NotificationService() { .firstOrNull { it == name } ?.let { File(this, it) } - private fun ResponseBody.toFile(id: Int, name: String): File { + private fun ResponseBody.toStream(id: Int): InputStream { val maxRaw = contentLength() val max = maxRaw / 1_000_000f - return writeToCachedFile(this@RemoteFileService, name) { + return ProgInputStream(byteStream()) { val progress = it / 1_000_000f update(id) { notification -> diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt index 200e9d8a7..5f81cd5da 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt @@ -11,7 +11,7 @@ import com.topjohnwu.magisk.model.events.* import com.topjohnwu.magisk.ui.base.MagiskActivity import com.topjohnwu.magisk.ui.base.MagiskFragment import com.topjohnwu.magisk.utils.ISafetyNetHelper -import com.topjohnwu.magisk.utils.copyTo +import com.topjohnwu.magisk.utils.writeTo import com.topjohnwu.magisk.utils.inject import com.topjohnwu.magisk.view.MarkDownWindow import com.topjohnwu.magisk.view.dialogs.* @@ -73,7 +73,7 @@ class HomeFragment : MagiskFragment(), private fun downloadSafetyNet(requiresUserInput: Boolean = true) { fun download() = magiskRepo.fetchSafetynet() - .map { it.byteStream().copyTo(EXT_FILE) } + .map { it.byteStream().writeTo(EXT_FILE) } .subscribeK { updateSafetyNet(true) } if (!requiresUserInput) { diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/XAndroid.kt b/app/src/main/java/com/topjohnwu/magisk/utils/XAndroid.kt index beab751ea..a7272c2c9 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/XAndroid.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/XAndroid.kt @@ -95,10 +95,12 @@ fun File.provide(context: Context = get()): Uri { } fun File.mv(destination: File) { - inputStream().copyTo(destination) + inputStream().writeTo(destination) deleteRecursively() } fun String.toFile() = File(this) -fun Intent.chooser(title: String = "Pick an app") = Intent.createChooser(this, title) \ No newline at end of file +fun Intent.chooser(title: String = "Pick an app") = Intent.createChooser(this, title) + +fun Context.cachedFile(name: String) = File(cacheDir, name) diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/XJava.kt b/app/src/main/java/com/topjohnwu/magisk/utils/XJava.kt index cbcab88bc..41a7b8b94 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/XJava.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/XJava.kt @@ -2,8 +2,6 @@ package com.topjohnwu.magisk.utils import android.net.Uri import androidx.core.net.toFile -import org.kamranzafar.jtar.TarInputStream -import org.kamranzafar.jtar.TarOutputStream import java.io.File import java.io.InputStream import java.io.OutputStream @@ -18,12 +16,10 @@ fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) { } } -fun Uri.copyTo(file: File) = toFile().copyTo(file) -fun InputStream.copyTo(file: File) = - withStreams(this, file.outputStream()) { reader, writer -> reader.copyTo(writer) } +fun Uri.writeTo(file: File) = toFile().copyTo(file) -fun File.tarInputStream() = TarInputStream(inputStream()) -fun File.tarOutputStream() = TarOutputStream(this) +fun InputStream.writeTo(file: File) = + withStreams(this, file.outputStream()) { reader, writer -> reader.copyTo(writer) } inline fun withStreams( inStream: In, diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/XNetwork.kt b/app/src/main/java/com/topjohnwu/magisk/utils/XNetwork.kt index 591ae5187..e86229166 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/XNetwork.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/XNetwork.kt @@ -1,8 +1,10 @@ package com.topjohnwu.magisk.utils import android.content.Context +import com.topjohnwu.superuser.internal.UiThreadHandler import okhttp3.ResponseBody import java.io.File +import java.io.FilterInputStream import java.io.InputStream import java.io.OutputStream @@ -32,8 +34,6 @@ inline fun InputStream.writeTo(output: OutputStream, progress: (Long) -> Unit = fun ResponseBody.writeToString() = string() -fun Context.cachedFile(name: String) = File(cacheDir, name) - inline fun InputStream.copyToWithProgress( out: OutputStream, progressEmitter: (Long) -> Unit, @@ -49,4 +49,32 @@ inline fun InputStream.copyToWithProgress( progressEmitter(bytesCopied) } return bytesCopied +} + +class ProgInputStream(base: InputStream, + val progressEmitter: (Long) -> Unit = {}) : FilterInputStream(base) { + + private var bytesRead : Long = 0 + + override fun read(): Int { + val b = read() + if (b >= 0) { + bytesRead++ + UiThreadHandler.run { progressEmitter(bytesRead) } + } + return b + } + + override fun read(b: ByteArray): Int { + return read(b, 0, b.size) + } + + override fun read(b: ByteArray, off: Int, len: Int): Int { + val sz = super.read(b, off, len) + if (sz > 0) { + bytesRead += sz + UiThreadHandler.run { progressEmitter(bytesRead) } + } + return sz + } } \ No newline at end of file