mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-11-27 20:15:29 +00:00
Stream and process module zips
This commit is contained in:
parent
746a1d8d59
commit
8ca188f4d4
@ -65,9 +65,7 @@
|
||||
|
||||
<!-- Service -->
|
||||
|
||||
<service android:name="a.j" />
|
||||
<service
|
||||
android:name="a.k"
|
||||
<service android:name="a.j"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- Hardcode GMS version -->
|
||||
|
@ -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 */
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
package a
|
||||
|
||||
import com.topjohnwu.magisk.model.download.DownloadService
|
||||
|
||||
class k : DownloadService()
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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<App>().protectedContext }
|
||||
single(SUTimeout) { get<Context>(Protected).getSharedPreferences("su_timeout", 0) }
|
||||
single { PreferenceManager.getDefaultSharedPreferences(get<Context>(Protected)) }
|
||||
|
||||
factory { (subject: DownloadSubject) -> ModuleTransformer(get(), subject) }
|
||||
}
|
@ -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<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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<ModuleTransformer> { 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 ->
|
||||
|
@ -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<HomeViewModel, FragmentMagiskBinding>(),
|
||||
|
||||
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) {
|
||||
|
@ -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)
|
||||
fun Intent.chooser(title: String = "Pick an app") = Intent.createChooser(this, title)
|
||||
|
||||
fun Context.cachedFile(name: String) = File(cacheDir, name)
|
||||
|
@ -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 <In : InputStream, Out : OutputStream> withStreams(
|
||||
inStream: In,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user