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.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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user