Commit 8ca188f4 authored by topjohnwu's avatar topjohnwu

Stream and process module zips

parent 746a1d8d
...@@ -65,9 +65,7 @@ ...@@ -65,9 +65,7 @@
<!-- Service --> <!-- Service -->
<service android:name="a.j" /> <service android:name="a.j"
<service
android:name="a.k"
android:exported="false" /> android:exported="false" />
<!-- Hardcode GMS version --> <!-- Hardcode GMS version -->
......
package a; 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 */ /* stub */
} }
package a
import com.topjohnwu.magisk.model.download.DownloadService
class k : DownloadService()
\ No newline at end of file
package com.topjohnwu.magisk package com.topjohnwu.magisk
import com.topjohnwu.magisk.model.download.DownloadModuleService
import com.topjohnwu.magisk.model.download.DownloadService import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.receiver.GeneralReceiver import com.topjohnwu.magisk.model.receiver.GeneralReceiver
import com.topjohnwu.magisk.model.update.UpdateCheckService import com.topjohnwu.magisk.model.update.UpdateCheckService
...@@ -17,8 +16,7 @@ object ClassMap { ...@@ -17,8 +16,7 @@ object ClassMap {
FlashActivity::class.java to a.f::class.java, FlashActivity::class.java to a.f::class.java,
UpdateCheckService::class.java to a.g::class.java, UpdateCheckService::class.java to a.g::class.java,
GeneralReceiver::class.java to a.h::class.java, GeneralReceiver::class.java to a.h::class.java,
DownloadModuleService::class.java to a.j::class.java, DownloadService::class.java to a.j::class.java,
DownloadService::class.java to a.k::class.java,
SuRequestActivity::class.java to a.m::class.java SuRequestActivity::class.java to a.m::class.java
) )
......
...@@ -4,8 +4,6 @@ import android.content.Context ...@@ -4,8 +4,6 @@ import android.content.Context
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.skoumal.teanity.rxbus.RxBus import com.skoumal.teanity.rxbus.RxBus
import com.topjohnwu.magisk.App 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 import org.koin.dsl.module
...@@ -17,6 +15,4 @@ val applicationModule = module { ...@@ -17,6 +15,4 @@ val applicationModule = module {
factory(Protected) { get<App>().protectedContext } factory(Protected) { get<App>().protectedContext }
single(SUTimeout) { get<Context>(Protected).getSharedPreferences("su_timeout", 0) } single(SUTimeout) { get<Context>(Protected).getSharedPreferences("su_timeout", 0) }
single { PreferenceManager.getDefaultSharedPreferences(get<Context>(Protected)) } single { PreferenceManager.getDefaultSharedPreferences(get<Context>(Protected)) }
factory { (subject: DownloadSubject) -> ModuleTransformer(get(), subject) }
} }
\ No newline at end of file
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<ProgressNotification> 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);
}
}
}
}
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
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
...@@ -9,18 +9,19 @@ import com.topjohnwu.magisk.R ...@@ -9,18 +9,19 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.repository.FileRepository 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.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.firstMap
import com.topjohnwu.magisk.utils.writeToCachedFile import com.topjohnwu.magisk.utils.writeTo
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.ShellUtils
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import okhttp3.ResponseBody import okhttp3.ResponseBody
import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.core.parameter.parametersOf
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.InputStream
abstract class RemoteFileService : NotificationService() { abstract class RemoteFileService : NotificationService() {
...@@ -73,19 +74,14 @@ abstract class RemoteFileService : NotificationService() { ...@@ -73,19 +74,14 @@ abstract class RemoteFileService : NotificationService() {
} }
private fun download(subject: DownloadSubject) = repo.downloadFile(subject.url) private fun download(subject: DownloadSubject) = repo.downloadFile(subject.url)
.map { it.toFile(subject.hashCode(), subject.fileName) } .map { it.toStream(subject.hashCode()) }
.map { .map {
when (subject) { cachedFile(subject.fileName).apply {
is Module -> { when (subject) {
update(subject.hashCode()) { is Module -> it.toModule(this,
it.setContentText(getString(R.string.download_module)) repo.downloadFile(Const.Url.MODULE_INSTALLER).blockingGet().byteStream())
.setProgress(0, 0, true) else -> it.writeTo(this)
}
get<ModuleTransformer> { parametersOf(subject) }
.inject(it, startInternal(Installer).blockingGet())
} }
else -> it
} }
} }
...@@ -95,11 +91,11 @@ abstract class RemoteFileService : NotificationService() { ...@@ -95,11 +91,11 @@ abstract class RemoteFileService : NotificationService() {
.firstOrNull { it == name } .firstOrNull { it == name }
?.let { File(this, it) } ?.let { File(this, it) }
private fun ResponseBody.toFile(id: Int, name: String): File { private fun ResponseBody.toStream(id: Int): InputStream {
val maxRaw = contentLength() val maxRaw = contentLength()
val max = maxRaw / 1_000_000f val max = maxRaw / 1_000_000f
return writeToCachedFile(this@RemoteFileService, name) { return ProgInputStream(byteStream()) {
val progress = it / 1_000_000f val progress = it / 1_000_000f
update(id) { notification -> update(id) { notification ->
......
...@@ -11,7 +11,7 @@ import com.topjohnwu.magisk.model.events.* ...@@ -11,7 +11,7 @@ import com.topjohnwu.magisk.model.events.*
import com.topjohnwu.magisk.ui.base.MagiskActivity import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.ui.base.MagiskFragment import com.topjohnwu.magisk.ui.base.MagiskFragment
import com.topjohnwu.magisk.utils.ISafetyNetHelper 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.utils.inject
import com.topjohnwu.magisk.view.MarkDownWindow import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.magisk.view.dialogs.* import com.topjohnwu.magisk.view.dialogs.*
...@@ -73,7 +73,7 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(), ...@@ -73,7 +73,7 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
private fun downloadSafetyNet(requiresUserInput: Boolean = true) { private fun downloadSafetyNet(requiresUserInput: Boolean = true) {
fun download() = magiskRepo.fetchSafetynet() fun download() = magiskRepo.fetchSafetynet()
.map { it.byteStream().copyTo(EXT_FILE) } .map { it.byteStream().writeTo(EXT_FILE) }
.subscribeK { updateSafetyNet(true) } .subscribeK { updateSafetyNet(true) }
if (!requiresUserInput) { if (!requiresUserInput) {
......
...@@ -95,10 +95,12 @@ fun File.provide(context: Context = get()): Uri { ...@@ -95,10 +95,12 @@ fun File.provide(context: Context = get()): Uri {
} }
fun File.mv(destination: File) { fun File.mv(destination: File) {
inputStream().copyTo(destination) inputStream().writeTo(destination)
deleteRecursively() deleteRecursively()
} }
fun String.toFile() = File(this) fun String.toFile() = File(this)
fun Intent.chooser(title: String = "Pick an app") = Intent.createChooser(this, title) fun Intent.chooser(title: String = "Pick an app") = Intent.createChooser(this, title)
\ No newline at end of file
fun Context.cachedFile(name: String) = File(cacheDir, name)
...@@ -2,8 +2,6 @@ package com.topjohnwu.magisk.utils ...@@ -2,8 +2,6 @@ package com.topjohnwu.magisk.utils
import android.net.Uri import android.net.Uri
import androidx.core.net.toFile import androidx.core.net.toFile
import org.kamranzafar.jtar.TarInputStream
import org.kamranzafar.jtar.TarOutputStream
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
...@@ -18,12 +16,10 @@ fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) { ...@@ -18,12 +16,10 @@ fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
} }
} }
fun Uri.copyTo(file: File) = toFile().copyTo(file) fun Uri.writeTo(file: File) = toFile().copyTo(file)
fun InputStream.copyTo(file: File) =
withStreams(this, file.outputStream()) { reader, writer -> reader.copyTo(writer) }
fun File.tarInputStream() = TarInputStream(inputStream()) fun InputStream.writeTo(file: File) =
fun File.tarOutputStream() = TarOutputStream(this) withStreams(this, file.outputStream()) { reader, writer -> reader.copyTo(writer) }
inline fun <In : InputStream, Out : OutputStream> withStreams( inline fun <In : InputStream, Out : OutputStream> withStreams(
inStream: In, inStream: In,
......
package com.topjohnwu.magisk.utils package com.topjohnwu.magisk.utils
import android.content.Context import android.content.Context
import com.topjohnwu.superuser.internal.UiThreadHandler
import okhttp3.ResponseBody import okhttp3.ResponseBody
import java.io.File import java.io.File
import java.io.FilterInputStream
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
...@@ -32,8 +34,6 @@ inline fun InputStream.writeTo(output: OutputStream, progress: (Long) -> Unit = ...@@ -32,8 +34,6 @@ inline fun InputStream.writeTo(output: OutputStream, progress: (Long) -> Unit =
fun ResponseBody.writeToString() = string() fun ResponseBody.writeToString() = string()
fun Context.cachedFile(name: String) = File(cacheDir, name)
inline fun InputStream.copyToWithProgress( inline fun InputStream.copyToWithProgress(
out: OutputStream, out: OutputStream,
progressEmitter: (Long) -> Unit, progressEmitter: (Long) -> Unit,
...@@ -49,4 +49,32 @@ inline fun InputStream.copyToWithProgress( ...@@ -49,4 +49,32 @@ inline fun InputStream.copyToWithProgress(
progressEmitter(bytesCopied) progressEmitter(bytesCopied)
} }
return 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment