From 10f991b8d0c115c4f99c18cf34e12e6d29ba2205 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Thu, 3 Feb 2022 03:22:55 -0800 Subject: [PATCH] Directly stream APK into install session --- .../topjohnwu/magisk/utils/APKInstall.java | 78 ++++++++++++------- .../magisk/core/download/DownloadService.kt | 6 +- .../magisk/core/download/ManagerHandler.kt | 7 +- .../topjohnwu/magisk/core/download/Subject.kt | 14 ++-- .../topjohnwu/magisk/core/tasks/HideAPK.kt | 4 +- .../topjohnwu/magisk/DownloadActivity.java | 2 +- 6 files changed, 67 insertions(+), 44 deletions(-) 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 418bd5756..490091000 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 @@ -18,9 +18,11 @@ import android.util.Log; import java.io.File; import java.io.FileInputStream; +import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -28,28 +30,50 @@ import io.michaelrocks.paranoid.Obfuscate; @Obfuscate public final class APKInstall { + + private static final String ACTION_SESSION_UPDATE = "ACTION_SESSION_UPDATE"; + // @WorkerThread - public static void installapk(Context context, File apk) { + public static void install(Context context, File apk) { + try (var src = new FileInputStream(apk); + var out = openStream(context, true)) { + if (out != null) + transfer(src, out); + } catch (IOException e) { + Log.e(APKInstall.class.getSimpleName(), "", e); + } + } + + public static OutputStream openStream(Context context, boolean silent) { //noinspection InlinedApi var flag = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE; - var action = APKInstall.class.getName(); - var intent = new Intent(action).setPackage(context.getPackageName()); + var intent = new Intent(ACTION_SESSION_UPDATE).setPackage(context.getPackageName()); var pending = PendingIntent.getBroadcast(context, 0, intent, flag); var installer = context.getPackageManager().getPackageInstaller(); var params = new SessionParams(SessionParams.MODE_FULL_INSTALL); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (silent && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { params.setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED); } - try (Session session = installer.openSession(installer.createSession(params))) { - OutputStream out = session.openWrite(apk.getName(), 0, apk.length()); - try (var in = new FileInputStream(apk); out) { - transfer(in, out); - } - session.commit(pending.getIntentSender()); + try { + Session session = installer.openSession(installer.createSession(params)); + var out = session.openWrite(UUID.randomUUID().toString(), 0, -1); + return new FilterOutputStream(out) { + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + } + @Override + public void close() throws IOException { + super.close(); + session.commit(pending.getIntentSender()); + session.close(); + } + }; } catch (IOException e) { Log.e(APKInstall.class.getSimpleName(), "", e); } + return null; } public static void transfer(InputStream in, OutputStream out) throws IOException { @@ -62,46 +86,47 @@ public final class APKInstall { } public static InstallReceiver register(Context context, String packageName, Runnable onSuccess) { - var receiver = new InstallReceiver(context, packageName, onSuccess); + var receiver = new InstallReceiver(packageName, onSuccess); var filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addDataScheme("package"); context.registerReceiver(receiver, filter); - context.registerReceiver(receiver, new IntentFilter(APKInstall.class.getName())); + context.registerReceiver(receiver, new IntentFilter(ACTION_SESSION_UPDATE)); return receiver; } public static class InstallReceiver extends BroadcastReceiver { - private final Context context; private final String packageName; private final Runnable onSuccess; private final CountDownLatch latch = new CountDownLatch(1); - private Intent intent = null; + private Intent userAction = null; - private InstallReceiver(Context context, String packageName, Runnable onSuccess) { - this.context = context; + private InstallReceiver(String packageName, Runnable onSuccess) { this.packageName = packageName; this.onSuccess = onSuccess; } @Override - public void onReceive(Context c, Intent i) { - if (Intent.ACTION_PACKAGE_ADDED.equals(i.getAction())) { - Uri data = i.getData(); - if (data == null || onSuccess == null) return; + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) { + Uri data = intent.getData(); + if (data == null) + return; String pkg = data.getSchemeSpecificPart(); if (pkg.equals(packageName)) { - onSuccess.run(); + if (onSuccess != null) + onSuccess.run(); context.unregisterReceiver(this); } return; } - int status = i.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID); + int status = intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID); switch (status) { case STATUS_PENDING_USER_ACTION: - intent = i.getParcelableExtra(Intent.EXTRA_INTENT); + userAction = intent.getParcelableExtra(Intent.EXTRA_INTENT); break; case STATUS_SUCCESS: - if (onSuccess != null) onSuccess.run(); + if (onSuccess != null) + onSuccess.run(); default: context.unregisterReceiver(this); } @@ -113,9 +138,8 @@ public final class APKInstall { try { //noinspection ResultOfMethodCallIgnored latch.await(5, TimeUnit.SECONDS); - } catch (Exception ignored) { - } - return intent; + } catch (Exception ignored) {} + return userAction; } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt index 52be18b6f..868d4dd76 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt @@ -70,7 +70,7 @@ class DownloadService : BaseService() { val activity = ActivityTracker.foreground if (activity != null && subject.autoStart) { remove(subject.notifyId) - subject.pendingIntent(activity).send() + subject.pendingIntent(activity)?.send() } else { notifyFinish(subject) } @@ -117,13 +117,13 @@ class DownloadService : BaseService() { private fun notifyFinish(subject: Subject) = finalNotify(subject.notifyId) { broadcast(1f, subject) - it.setContentIntent(subject.pendingIntent(this)) - .setContentTitle(subject.title) + it.setContentTitle(subject.title) .setContentText(getString(R.string.download_complete)) .setSmallIcon(android.R.drawable.stat_sys_download_done) .setProgress(0, 0, false) .setOngoing(false) .setAutoCancel(true) + subject.pendingIntent(this)?.let { intent -> it.setContentIntent(intent) } } private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int { diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/ManagerHandler.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/ManagerHandler.kt index d1fdde339..f5a607e99 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/ManagerHandler.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/ManagerHandler.kt @@ -10,6 +10,7 @@ import com.topjohnwu.magisk.core.tasks.HideAPK import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream import com.topjohnwu.magisk.ktx.copyAndClose import com.topjohnwu.magisk.ktx.writeTo +import com.topjohnwu.magisk.utils.APKInstall import java.io.File import java.io.InputStream import java.io.OutputStream @@ -57,8 +58,10 @@ suspend fun DownloadService.handleAPK(subject: Subject.Manager, stream: InputStr } else { val clz = Info.stub!!.classToComponent["PHOENIX"]!! PhoenixActivity.rebirth(this, clz) + return } - } else { - write(subject.file.outputStream()) } + val receiver = APKInstall.register(this, null, null) + write(APKInstall.openStream(this, false)) + subject.intent = receiver.waitIntent() } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt index 9ba7ce107..42d1405e8 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt @@ -6,7 +6,6 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Parcelable -import androidx.core.net.toFile import androidx.core.net.toUri import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.model.MagiskJson @@ -16,7 +15,6 @@ import com.topjohnwu.magisk.core.utils.MediaStoreUtils import com.topjohnwu.magisk.di.AppContext import com.topjohnwu.magisk.ktx.cachedFile import com.topjohnwu.magisk.ui.flash.FlashFragment -import com.topjohnwu.magisk.utils.APKInstall import com.topjohnwu.magisk.view.Notifications import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @@ -36,7 +34,7 @@ sealed class Subject : Parcelable { abstract val notifyId: Int open val autoStart: Boolean get() = true - abstract fun pendingIntent(context: Context): PendingIntent + abstract fun pendingIntent(context: Context): PendingIntent? @Parcelize class Module( @@ -71,14 +69,12 @@ sealed class Subject : Parcelable { cachedFile("manager.apk") } + @IgnoredOnParcel + var intent: Intent? = null + val externalFile get() = MediaStoreUtils.getFile("$title.apk").uri - override fun pendingIntent(context: Context): PendingIntent { - val receiver = APKInstall.register(context, null, null) - APKInstall.installapk(context, file.toFile()) - val intent = receiver.waitIntent() ?: Intent() - return intent.toPending(context) - } + override fun pendingIntent(context: Context) = intent?.toPending(context) } @SuppressLint("InlinedApi") 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 0b41c5766..d9cdb1d1e 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 @@ -125,7 +125,7 @@ object HideAPK { } val cmd = "adb_pm_install $repack ${activity.applicationInfo.uid}" if (!Shell.su(cmd).exec().isSuccess) { - APKInstall.installapk(activity, repack) + APKInstall.install(activity, repack) receiver.waitIntent()?.let { activity.startActivity(it) } } return true @@ -164,7 +164,7 @@ object HideAPK { val cmd = "adb_pm_install $apk ${activity.applicationInfo.uid}" Shell.su(cmd).submit(Shell.EXECUTOR) { ret -> if (ret.isSuccess) return@submit - APKInstall.installapk(activity, apk) + APKInstall.install(activity, apk) receiver.waitIntent()?.let { activity.startActivity(it) } } } diff --git a/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java b/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java index 10aa06de9..d4400c99a 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java @@ -119,7 +119,7 @@ public class DownloadActivity extends Activity { runOnUiThread(onSuccess); } else { var receiver = APKInstall.register(this, BuildConfig.APPLICATION_ID, onSuccess); - APKInstall.installapk(this, file); + APKInstall.install(this, file); Intent intent = receiver.waitIntent(); if (intent != null) startActivity(intent); }