mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-11-27 12:05:30 +00:00
Refactor APKInstall
This commit is contained in:
parent
256ff31d11
commit
668e549208
@ -10,11 +10,9 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageInstaller.Session;
|
||||
import android.content.pm.PackageInstaller.SessionParams;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@ -31,51 +29,6 @@ import io.michaelrocks.paranoid.Obfuscate;
|
||||
@Obfuscate
|
||||
public final class APKInstall {
|
||||
|
||||
private static final String ACTION_SESSION_UPDATE = "ACTION_SESSION_UPDATE";
|
||||
|
||||
// @WorkerThread
|
||||
public static void install(Context context, File apk) {
|
||||
try (var src = new FileInputStream(apk);
|
||||
var out = openStream(context)) {
|
||||
if (out != null)
|
||||
transfer(src, out);
|
||||
} catch (IOException e) {
|
||||
Log.e(APKInstall.class.getSimpleName(), "", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static OutputStream openStream(Context context) {
|
||||
//noinspection InlinedApi
|
||||
var flag = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE;
|
||||
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) {
|
||||
params.setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED);
|
||||
}
|
||||
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 {
|
||||
int size = 8192;
|
||||
var buffer = new byte[size];
|
||||
@ -85,21 +38,37 @@ public final class APKInstall {
|
||||
}
|
||||
}
|
||||
|
||||
public static InstallReceiver register(Context context, String packageName, Runnable onSuccess) {
|
||||
var receiver = new InstallReceiver(packageName, onSuccess);
|
||||
public static Session startSession(Context context) {
|
||||
return startSession(context, null, null);
|
||||
}
|
||||
|
||||
public static Session startSession(Context context, String pkg, Runnable onSuccess) {
|
||||
var receiver = new InstallReceiver(pkg, onSuccess);
|
||||
context = context.getApplicationContext();
|
||||
if (pkg != null) {
|
||||
// If pkg is not null, look for package added event
|
||||
var filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
|
||||
filter.addDataScheme("package");
|
||||
context.registerReceiver(receiver, filter);
|
||||
context.registerReceiver(receiver, new IntentFilter(ACTION_SESSION_UPDATE));
|
||||
}
|
||||
context.registerReceiver(receiver, new IntentFilter(receiver.sessionId));
|
||||
return receiver;
|
||||
}
|
||||
|
||||
public static class InstallReceiver extends BroadcastReceiver {
|
||||
public interface Session {
|
||||
OutputStream openStream(Context context) throws IOException;
|
||||
void install(Context context, File apk) throws IOException;
|
||||
Intent waitIntent();
|
||||
}
|
||||
|
||||
private static class InstallReceiver extends BroadcastReceiver implements Session {
|
||||
private final String packageName;
|
||||
private final Runnable onSuccess;
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
private Intent userAction = null;
|
||||
|
||||
final String sessionId = UUID.randomUUID().toString();
|
||||
|
||||
private InstallReceiver(String packageName, Runnable onSuccess) {
|
||||
this.packageName = packageName;
|
||||
this.onSuccess = onSuccess;
|
||||
@ -113,27 +82,32 @@ public final class APKInstall {
|
||||
return;
|
||||
String pkg = data.getSchemeSpecificPart();
|
||||
if (pkg.equals(packageName)) {
|
||||
if (onSuccess != null)
|
||||
onSuccess.run();
|
||||
context.unregisterReceiver(this);
|
||||
}
|
||||
return;
|
||||
onSuccess(context);
|
||||
}
|
||||
} else if (sessionId.equals(intent.getAction())) {
|
||||
int status = intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID);
|
||||
switch (status) {
|
||||
case STATUS_PENDING_USER_ACTION:
|
||||
userAction = intent.getParcelableExtra(Intent.EXTRA_INTENT);
|
||||
break;
|
||||
case STATUS_SUCCESS:
|
||||
if (onSuccess != null)
|
||||
onSuccess.run();
|
||||
default:
|
||||
context.unregisterReceiver(this);
|
||||
if (packageName == null) {
|
||||
onSuccess(context);
|
||||
}
|
||||
break;
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
private void onSuccess(Context context) {
|
||||
if (onSuccess != null)
|
||||
onSuccess.run();
|
||||
context.getApplicationContext().unregisterReceiver(this);
|
||||
}
|
||||
|
||||
// @WorkerThread @Nullable
|
||||
@Override
|
||||
public Intent waitIntent() {
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
@ -141,5 +115,41 @@ public final class APKInstall {
|
||||
} catch (Exception ignored) {}
|
||||
return userAction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openStream(Context context) throws IOException {
|
||||
// noinspection InlinedApi
|
||||
var flag = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE;
|
||||
var intent = new Intent(sessionId).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) {
|
||||
params.setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED);
|
||||
}
|
||||
var session = installer.openSession(installer.createSession(params));
|
||||
var out = session.openWrite(sessionId, 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void install(Context context, File apk) throws IOException {
|
||||
try (var src = new FileInputStream(apk);
|
||||
var out = openStream(context)) {
|
||||
transfer(src, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,9 +72,9 @@ class DownloadService : NotificationService() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun openApkSession(): OutputStream {
|
||||
private fun APKInstall.Session.open(): OutputStream {
|
||||
Config.showUpdateDone = true
|
||||
return APKInstall.openStream(this)
|
||||
return openStream(this@DownloadService)
|
||||
}
|
||||
|
||||
private suspend fun handleApp(stream: InputStream, subject: Subject.App) {
|
||||
@ -110,9 +110,9 @@ class DownloadService : NotificationService() {
|
||||
apk.delete()
|
||||
|
||||
// Install
|
||||
val receiver = APKInstall.register(this, null, null)
|
||||
patched.inputStream().copyAndClose(openApkSession())
|
||||
subject.intent = receiver.waitIntent()
|
||||
val session = APKInstall.startSession(this)
|
||||
patched.inputStream().copyAndClose(session.open())
|
||||
subject.intent = session.waitIntent()
|
||||
|
||||
patched.delete()
|
||||
} else {
|
||||
@ -131,9 +131,9 @@ class DownloadService : NotificationService() {
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
val receiver = APKInstall.register(this, null, null)
|
||||
writeTee(openApkSession())
|
||||
subject.intent = receiver.waitIntent()
|
||||
val session = APKInstall.startSession(this)
|
||||
writeTee(session.open())
|
||||
subject.intent = session.waitIntent()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.topjohnwu.magisk.core.tasks
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
@ -20,8 +21,7 @@ import com.topjohnwu.magisk.signing.SignApk
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.*
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
@ -108,7 +108,8 @@ object HideAPK {
|
||||
Timber.e(e)
|
||||
stub.createNewFile()
|
||||
val cmd = "\$MAGISKBIN/magiskinit -x manager ${stub.path}"
|
||||
if (!Shell.su(cmd).exec().isSuccess) return false
|
||||
if (!Shell.su(cmd).exec().isSuccess)
|
||||
return false
|
||||
}
|
||||
|
||||
// Generate a new random package name and signature
|
||||
@ -120,20 +121,22 @@ object HideAPK {
|
||||
return false
|
||||
|
||||
// Install and auto launch app
|
||||
val receiver = APKInstall.register(activity, pkg) {
|
||||
val session = APKInstall.startSession(activity, pkg) {
|
||||
launchApp(activity, pkg)
|
||||
}
|
||||
val cmd = "adb_pm_install $repack ${activity.applicationInfo.uid}"
|
||||
if (!Shell.su(cmd).exec().isSuccess) {
|
||||
APKInstall.install(activity, repack)
|
||||
receiver.waitIntent()?.let { activity.startActivity(it) }
|
||||
try {
|
||||
session.install(activity, repack)
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
session.waitIntent()?.let { activity.startActivity(it) }
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
suspend fun hide(activity: Activity, label: String) {
|
||||
val dialog = android.app.ProgressDialog(activity).apply {
|
||||
val dialog = ProgressDialog(activity).apply {
|
||||
setTitle(activity.getString(R.string.hide_app_title))
|
||||
isIndeterminate = true
|
||||
setCancelable(false)
|
||||
@ -148,24 +151,28 @@ object HideAPK {
|
||||
}
|
||||
}
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
@Suppress("DEPRECATION")
|
||||
fun restore(activity: Activity) {
|
||||
val dialog = android.app.ProgressDialog(activity).apply {
|
||||
val dialog = ProgressDialog(activity).apply {
|
||||
setTitle(activity.getString(R.string.restore_img_msg))
|
||||
isIndeterminate = true
|
||||
setCancelable(false)
|
||||
show()
|
||||
}
|
||||
val apk = StubApk.current(activity)
|
||||
val receiver = APKInstall.register(activity, APPLICATION_ID) {
|
||||
val session = APKInstall.startSession(activity, APPLICATION_ID) {
|
||||
launchApp(activity, APPLICATION_ID)
|
||||
dialog.dismiss()
|
||||
}
|
||||
val cmd = "adb_pm_install $apk ${activity.applicationInfo.uid}"
|
||||
Shell.su(cmd).submit(Shell.EXECUTOR) { ret ->
|
||||
if (ret.isSuccess) return@submit
|
||||
APKInstall.install(activity, apk)
|
||||
receiver.waitIntent()?.let { activity.startActivity(it) }
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
session.install(activity, apk)
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
return@launch
|
||||
}
|
||||
session.waitIntent()?.let { activity.startActivity(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +99,7 @@ fun genStubManifest(srcDir: File, outDir: File): String {
|
||||
| <intent-filter>
|
||||
| <action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
| <action android:name="android.intent.action.UID_REMOVED" />
|
||||
| <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
| </intent-filter>
|
||||
| <intent-filter>
|
||||
| <action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
|
@ -26,6 +26,7 @@ import org.json.JSONException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
@ -126,10 +127,15 @@ public class DownloadActivity extends Activity {
|
||||
if (dynLoad) {
|
||||
runOnUiThread(onSuccess);
|
||||
} else {
|
||||
var receiver = APKInstall.register(this, BuildConfig.APPLICATION_ID, onSuccess);
|
||||
APKInstall.install(this, file);
|
||||
Intent intent = receiver.waitIntent();
|
||||
if (intent != null) startActivity(intent);
|
||||
var session = APKInstall.startSession(this);
|
||||
try {
|
||||
session.install(this, file);
|
||||
Intent intent = session.waitIntent();
|
||||
if (intent != null)
|
||||
startActivity(intent);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user