mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-06-12 13:17:39 +02:00
Directly stream APK into install session
This commit is contained in:
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user