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 -->
|
||||||
|
|
||||||
<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 -->
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
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 */
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package a
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.model.download.DownloadService
|
|
||||||
|
|
||||||
class k : DownloadService()
|
|
@ -1,6 +1,5 @@
|
|||||||
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 {
|
|||||||
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
|
|||||||
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 {
|
|||||||
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) }
|
|
||||||
}
|
}
|
@ -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.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() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
cachedFile(subject.fileName).apply {
|
||||||
when (subject) {
|
when (subject) {
|
||||||
is Module -> {
|
is Module -> it.toModule(this,
|
||||||
update(subject.hashCode()) {
|
repo.downloadFile(Const.Url.MODULE_INSTALLER).blockingGet().byteStream())
|
||||||
it.setContentText(getString(R.string.download_module))
|
else -> it.writeTo(this)
|
||||||
.setProgress(0, 0, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get<ModuleTransformer> { parametersOf(subject) }
|
|
||||||
.inject(it, startInternal(Installer).blockingGet())
|
|
||||||
}
|
|
||||||
else -> it
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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.*
|
|||||||
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>(),
|
|||||||
|
|
||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
fun Context.cachedFile(name: String) = File(cacheDir, name)
|
||||||
|
@ -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) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
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 =
|
|||||||
|
|
||||||
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,
|
||||||
@ -50,3 +50,31 @@ inline fun InputStream.copyToWithProgress(
|
|||||||
}
|
}
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user