1
mirror of https://github.com/topjohnwu/Magisk synced 2024-11-16 00:13:57 +01:00

Directly stream APK into install session

This commit is contained in:
topjohnwu 2022-02-03 03:22:55 -08:00
parent 79620c97d1
commit 10f991b8d0
6 changed files with 67 additions and 44 deletions

View File

@ -18,9 +18,11 @@ import android.util.Log;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FilterOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.UUID;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -28,28 +30,50 @@ import io.michaelrocks.paranoid.Obfuscate;
@Obfuscate @Obfuscate
public final class APKInstall { public final class APKInstall {
private static final String ACTION_SESSION_UPDATE = "ACTION_SESSION_UPDATE";
// @WorkerThread // @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 //noinspection InlinedApi
var flag = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE; var flag = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE;
var action = APKInstall.class.getName(); var intent = new Intent(ACTION_SESSION_UPDATE).setPackage(context.getPackageName());
var intent = new Intent(action).setPackage(context.getPackageName());
var pending = PendingIntent.getBroadcast(context, 0, intent, flag); var pending = PendingIntent.getBroadcast(context, 0, intent, flag);
var installer = context.getPackageManager().getPackageInstaller(); var installer = context.getPackageManager().getPackageInstaller();
var params = new SessionParams(SessionParams.MODE_FULL_INSTALL); 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); params.setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED);
} }
try (Session session = installer.openSession(installer.createSession(params))) { try {
OutputStream out = session.openWrite(apk.getName(), 0, apk.length()); Session session = installer.openSession(installer.createSession(params));
try (var in = new FileInputStream(apk); out) { var out = session.openWrite(UUID.randomUUID().toString(), 0, -1);
transfer(in, out); return new FilterOutputStream(out) {
} @Override
session.commit(pending.getIntentSender()); 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) { } catch (IOException e) {
Log.e(APKInstall.class.getSimpleName(), "", e); Log.e(APKInstall.class.getSimpleName(), "", e);
} }
return null;
} }
public static void transfer(InputStream in, OutputStream out) throws IOException { 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) { 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); var filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package"); filter.addDataScheme("package");
context.registerReceiver(receiver, filter); context.registerReceiver(receiver, filter);
context.registerReceiver(receiver, new IntentFilter(APKInstall.class.getName())); context.registerReceiver(receiver, new IntentFilter(ACTION_SESSION_UPDATE));
return receiver; return receiver;
} }
public static class InstallReceiver extends BroadcastReceiver { public static class InstallReceiver extends BroadcastReceiver {
private final Context context;
private final String packageName; private final String packageName;
private final Runnable onSuccess; private final Runnable onSuccess;
private final CountDownLatch latch = new CountDownLatch(1); private final CountDownLatch latch = new CountDownLatch(1);
private Intent intent = null; private Intent userAction = null;
private InstallReceiver(Context context, String packageName, Runnable onSuccess) { private InstallReceiver(String packageName, Runnable onSuccess) {
this.context = context;
this.packageName = packageName; this.packageName = packageName;
this.onSuccess = onSuccess; this.onSuccess = onSuccess;
} }
@Override @Override
public void onReceive(Context c, Intent i) { public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_PACKAGE_ADDED.equals(i.getAction())) { if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
Uri data = i.getData(); Uri data = intent.getData();
if (data == null || onSuccess == null) return; if (data == null)
return;
String pkg = data.getSchemeSpecificPart(); String pkg = data.getSchemeSpecificPart();
if (pkg.equals(packageName)) { if (pkg.equals(packageName)) {
onSuccess.run(); if (onSuccess != null)
onSuccess.run();
context.unregisterReceiver(this); context.unregisterReceiver(this);
} }
return; return;
} }
int status = i.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID); int status = intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID);
switch (status) { switch (status) {
case STATUS_PENDING_USER_ACTION: case STATUS_PENDING_USER_ACTION:
intent = i.getParcelableExtra(Intent.EXTRA_INTENT); userAction = intent.getParcelableExtra(Intent.EXTRA_INTENT);
break; break;
case STATUS_SUCCESS: case STATUS_SUCCESS:
if (onSuccess != null) onSuccess.run(); if (onSuccess != null)
onSuccess.run();
default: default:
context.unregisterReceiver(this); context.unregisterReceiver(this);
} }
@ -113,9 +138,8 @@ public final class APKInstall {
try { try {
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
latch.await(5, TimeUnit.SECONDS); latch.await(5, TimeUnit.SECONDS);
} catch (Exception ignored) { } catch (Exception ignored) {}
} return userAction;
return intent;
} }
} }
} }

View File

@ -70,7 +70,7 @@ class DownloadService : BaseService() {
val activity = ActivityTracker.foreground val activity = ActivityTracker.foreground
if (activity != null && subject.autoStart) { if (activity != null && subject.autoStart) {
remove(subject.notifyId) remove(subject.notifyId)
subject.pendingIntent(activity).send() subject.pendingIntent(activity)?.send()
} else { } else {
notifyFinish(subject) notifyFinish(subject)
} }
@ -117,13 +117,13 @@ class DownloadService : BaseService() {
private fun notifyFinish(subject: Subject) = finalNotify(subject.notifyId) { private fun notifyFinish(subject: Subject) = finalNotify(subject.notifyId) {
broadcast(1f, subject) broadcast(1f, subject)
it.setContentIntent(subject.pendingIntent(this)) it.setContentTitle(subject.title)
.setContentTitle(subject.title)
.setContentText(getString(R.string.download_complete)) .setContentText(getString(R.string.download_complete))
.setSmallIcon(android.R.drawable.stat_sys_download_done) .setSmallIcon(android.R.drawable.stat_sys_download_done)
.setProgress(0, 0, false) .setProgress(0, 0, false)
.setOngoing(false) .setOngoing(false)
.setAutoCancel(true) .setAutoCancel(true)
subject.pendingIntent(this)?.let { intent -> it.setContentIntent(intent) }
} }
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int { private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {

View File

@ -10,6 +10,7 @@ import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.ktx.copyAndClose import com.topjohnwu.magisk.ktx.copyAndClose
import com.topjohnwu.magisk.ktx.writeTo import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.utils.APKInstall
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
@ -57,8 +58,10 @@ suspend fun DownloadService.handleAPK(subject: Subject.Manager, stream: InputStr
} else { } else {
val clz = Info.stub!!.classToComponent["PHOENIX"]!! val clz = Info.stub!!.classToComponent["PHOENIX"]!!
PhoenixActivity.rebirth(this, clz) 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()
} }

View File

@ -6,7 +6,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Parcelable import android.os.Parcelable
import androidx.core.net.toFile
import androidx.core.net.toUri import androidx.core.net.toUri
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.model.MagiskJson 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.di.AppContext
import com.topjohnwu.magisk.ktx.cachedFile import com.topjohnwu.magisk.ktx.cachedFile
import com.topjohnwu.magisk.ui.flash.FlashFragment import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -36,7 +34,7 @@ sealed class Subject : Parcelable {
abstract val notifyId: Int abstract val notifyId: Int
open val autoStart: Boolean get() = true open val autoStart: Boolean get() = true
abstract fun pendingIntent(context: Context): PendingIntent abstract fun pendingIntent(context: Context): PendingIntent?
@Parcelize @Parcelize
class Module( class Module(
@ -71,14 +69,12 @@ sealed class Subject : Parcelable {
cachedFile("manager.apk") cachedFile("manager.apk")
} }
@IgnoredOnParcel
var intent: Intent? = null
val externalFile get() = MediaStoreUtils.getFile("$title.apk").uri val externalFile get() = MediaStoreUtils.getFile("$title.apk").uri
override fun pendingIntent(context: Context): PendingIntent { override fun pendingIntent(context: Context) = intent?.toPending(context)
val receiver = APKInstall.register(context, null, null)
APKInstall.installapk(context, file.toFile())
val intent = receiver.waitIntent() ?: Intent()
return intent.toPending(context)
}
} }
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")

View File

@ -125,7 +125,7 @@ object HideAPK {
} }
val cmd = "adb_pm_install $repack ${activity.applicationInfo.uid}" val cmd = "adb_pm_install $repack ${activity.applicationInfo.uid}"
if (!Shell.su(cmd).exec().isSuccess) { if (!Shell.su(cmd).exec().isSuccess) {
APKInstall.installapk(activity, repack) APKInstall.install(activity, repack)
receiver.waitIntent()?.let { activity.startActivity(it) } receiver.waitIntent()?.let { activity.startActivity(it) }
} }
return true return true
@ -164,7 +164,7 @@ object HideAPK {
val cmd = "adb_pm_install $apk ${activity.applicationInfo.uid}" val cmd = "adb_pm_install $apk ${activity.applicationInfo.uid}"
Shell.su(cmd).submit(Shell.EXECUTOR) { ret -> Shell.su(cmd).submit(Shell.EXECUTOR) { ret ->
if (ret.isSuccess) return@submit if (ret.isSuccess) return@submit
APKInstall.installapk(activity, apk) APKInstall.install(activity, apk)
receiver.waitIntent()?.let { activity.startActivity(it) } receiver.waitIntent()?.let { activity.startActivity(it) }
} }
} }

View File

@ -119,7 +119,7 @@ public class DownloadActivity extends Activity {
runOnUiThread(onSuccess); runOnUiThread(onSuccess);
} else { } else {
var receiver = APKInstall.register(this, BuildConfig.APPLICATION_ID, onSuccess); var receiver = APKInstall.register(this, BuildConfig.APPLICATION_ID, onSuccess);
APKInstall.installapk(this, file); APKInstall.install(this, file);
Intent intent = receiver.waitIntent(); Intent intent = receiver.waitIntent();
if (intent != null) startActivity(intent); if (intent != null) startActivity(intent);
} }