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