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.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;
}
}
}

View File

@ -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 {

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.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()
}

View File

@ -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")

View File

@ -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) }
}
}

View File

@ -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);
}