From 668e54920832b109c994ef558fc8010bc1a75870 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Sun, 13 Feb 2022 19:54:59 -0800 Subject: [PATCH] Refactor APKInstall --- .../topjohnwu/magisk/utils/APKInstall.java | 150 ++++++++++-------- .../magisk/core/download/DownloadService.kt | 16 +- .../topjohnwu/magisk/core/tasks/HideAPK.kt | 39 +++-- buildSrc/src/main/java/Codegen.kt | 1 + .../topjohnwu/magisk/DownloadActivity.java | 14 +- 5 files changed, 122 insertions(+), 98 deletions(-) diff --git a/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java b/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java index 5f30fa285..6e4729be5 100644 --- a/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java +++ b/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java @@ -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); - var filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); - filter.addDataScheme("package"); - context.registerReceiver(receiver, filter); - context.registerReceiver(receiver, new IntentFilter(ACTION_SESSION_UPDATE)); + 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(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); + onSuccess(context); } - return; + } 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 (packageName == null) { + onSuccess(context); + } + break; + } + latch.countDown(); } - 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); - } - 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); + } + } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt index 091023bbb..1e202bc41 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt @@ -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() } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt b/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt index d9cdb1d1e..931add738 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt @@ -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) } } } } diff --git a/buildSrc/src/main/java/Codegen.kt b/buildSrc/src/main/java/Codegen.kt index f2a8abd9c..1d375cff6 100644 --- a/buildSrc/src/main/java/Codegen.kt +++ b/buildSrc/src/main/java/Codegen.kt @@ -99,6 +99,7 @@ fun genStubManifest(srcDir: File, outDir: File): String { | | | + | | | | diff --git a/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java b/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java index 840f253cf..efdcac50c 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java @@ -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(); + } } }); }