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:
parent
79620c97d1
commit
10f991b8d0
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user