mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-11-23 10:05:23 +00:00
parent
5a49bd3ac9
commit
baa19f0ccf
@ -8,6 +8,7 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
|
||||
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29"
|
||||
|
@ -1,303 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.michaelrocks.paranoid.Obfuscate;
|
||||
|
||||
/**
|
||||
* Modified from androidx.core.content.FileProvider
|
||||
*/
|
||||
@Obfuscate
|
||||
public class FileProvider extends ContentProvider {
|
||||
private static final String[] COLUMNS = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
|
||||
|
||||
private static final File DEVICE_ROOT = new File("/");
|
||||
|
||||
private static final HashMap<String, PathStrategy> sCache = new HashMap<>();
|
||||
|
||||
private PathStrategy mStrategy;
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachInfo(Context context, ProviderInfo info) {
|
||||
super.attachInfo(context, info);
|
||||
|
||||
if (info.exported) {
|
||||
throw new SecurityException("Provider must not be exported");
|
||||
}
|
||||
if (!info.grantUriPermissions) {
|
||||
throw new SecurityException("Provider must grant uri permissions");
|
||||
}
|
||||
|
||||
mStrategy = getPathStrategy(context, info.authority.split(";")[0]);
|
||||
}
|
||||
|
||||
public static Uri getUriForFile(Context context, String authority, File file) {
|
||||
final PathStrategy strategy = getPathStrategy(context, authority);
|
||||
return strategy.getUriForFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
final File file = mStrategy.getFileForUri(uri);
|
||||
|
||||
if (projection == null) {
|
||||
projection = COLUMNS;
|
||||
}
|
||||
|
||||
String[] cols = new String[projection.length];
|
||||
Object[] values = new Object[projection.length];
|
||||
int i = 0;
|
||||
for (String col : projection) {
|
||||
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
|
||||
cols[i] = OpenableColumns.DISPLAY_NAME;
|
||||
values[i++] = file.getName();
|
||||
} else if (OpenableColumns.SIZE.equals(col)) {
|
||||
cols[i] = OpenableColumns.SIZE;
|
||||
values[i++] = file.length();
|
||||
}
|
||||
}
|
||||
|
||||
cols = copyOf(cols, i);
|
||||
values = copyOf(values, i);
|
||||
|
||||
final MatrixCursor cursor = new MatrixCursor(cols, 1);
|
||||
cursor.addRow(values);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
final File file = mStrategy.getFileForUri(uri);
|
||||
|
||||
final int lastDot = file.getName().lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
final String extension = file.getName().substring(lastDot + 1);
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mime != null) {
|
||||
return mime;
|
||||
}
|
||||
}
|
||||
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
throw new UnsupportedOperationException("No external inserts");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException("No external updates");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
final File file = mStrategy.getFileForUri(uri);
|
||||
return file.delete() ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
final File file = mStrategy.getFileForUri(uri);
|
||||
final int fileMode = modeToMode(mode);
|
||||
return ParcelFileDescriptor.open(file, fileMode);
|
||||
}
|
||||
|
||||
private static PathStrategy getPathStrategy(Context context, String authority) {
|
||||
PathStrategy strat;
|
||||
synchronized (sCache) {
|
||||
strat = sCache.get(authority);
|
||||
if (strat == null) {
|
||||
strat = createPathStrategy(context, authority);
|
||||
sCache.put(authority, strat);
|
||||
}
|
||||
}
|
||||
return strat;
|
||||
}
|
||||
|
||||
private static PathStrategy createPathStrategy(Context context, String authority) {
|
||||
final SimplePathStrategy strat = new SimplePathStrategy(authority);
|
||||
|
||||
strat.addRoot("root_files", buildPath(DEVICE_ROOT, "."));
|
||||
strat.addRoot("internal_files", buildPath(context.getFilesDir(), "."));
|
||||
strat.addRoot("cache_files", buildPath(context.getCacheDir(), "."));
|
||||
strat.addRoot("external_files", buildPath(Environment.getExternalStorageDirectory(), "."));
|
||||
|
||||
File[] externalFilesDirs = context.getExternalFilesDirs(null);
|
||||
if (externalFilesDirs.length > 0) {
|
||||
strat.addRoot("external_file_files", buildPath(externalFilesDirs[0], "."));
|
||||
}
|
||||
File[] externalCacheDirs = context.getExternalCacheDirs();
|
||||
if (externalCacheDirs.length > 0) {
|
||||
strat.addRoot("external_cache_files", buildPath(externalCacheDirs[0], "."));
|
||||
}
|
||||
File[] externalMediaDirs = context.getExternalMediaDirs();
|
||||
if (externalMediaDirs.length > 0) {
|
||||
strat.addRoot("external_media_files", buildPath(externalMediaDirs[0], "."));
|
||||
}
|
||||
|
||||
return strat;
|
||||
}
|
||||
|
||||
interface PathStrategy {
|
||||
Uri getUriForFile(File file);
|
||||
|
||||
File getFileForUri(Uri uri);
|
||||
}
|
||||
|
||||
static class SimplePathStrategy implements PathStrategy {
|
||||
private final String mAuthority;
|
||||
private final HashMap<String, File> mRoots = new HashMap<>();
|
||||
|
||||
SimplePathStrategy(String authority) {
|
||||
mAuthority = authority;
|
||||
}
|
||||
|
||||
void addRoot(String name, File root) {
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
throw new IllegalArgumentException("Name must not be empty");
|
||||
}
|
||||
|
||||
try {
|
||||
root = root.getCanonicalFile();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to resolve canonical path for " + root, e);
|
||||
}
|
||||
|
||||
mRoots.put(name, root);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getUriForFile(File file) {
|
||||
String path;
|
||||
try {
|
||||
path = file.getCanonicalPath();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
|
||||
}
|
||||
|
||||
Map.Entry<String, File> mostSpecific = null;
|
||||
for (Map.Entry<String, File> root : mRoots.entrySet()) {
|
||||
final String rootPath = root.getValue().getPath();
|
||||
if (path.startsWith(rootPath) && (mostSpecific == null
|
||||
|| rootPath.length() > mostSpecific.getValue().getPath().length())) {
|
||||
mostSpecific = root;
|
||||
}
|
||||
}
|
||||
|
||||
if (mostSpecific == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to find configured root that contains " + path);
|
||||
}
|
||||
|
||||
final String rootPath = mostSpecific.getValue().getPath();
|
||||
if (rootPath.endsWith("/")) {
|
||||
path = path.substring(rootPath.length());
|
||||
} else {
|
||||
path = path.substring(rootPath.length() + 1);
|
||||
}
|
||||
|
||||
path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
|
||||
return new Uri.Builder().scheme("content")
|
||||
.authority(mAuthority).encodedPath(path).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFileForUri(Uri uri) {
|
||||
String path = uri.getEncodedPath();
|
||||
|
||||
final int splitIndex = path.indexOf('/', 1);
|
||||
final String tag = Uri.decode(path.substring(1, splitIndex));
|
||||
path = Uri.decode(path.substring(splitIndex + 1));
|
||||
|
||||
final File root = mRoots.get(tag);
|
||||
if (root == null) {
|
||||
throw new IllegalArgumentException("Unable to find configured root for " + uri);
|
||||
}
|
||||
|
||||
File file = new File(root, path);
|
||||
try {
|
||||
file = file.getCanonicalFile();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
|
||||
}
|
||||
|
||||
if (!file.getPath().startsWith(root.getPath())) {
|
||||
throw new SecurityException("Resolved path jumped beyond configured root");
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
private static int modeToMode(String mode) {
|
||||
int modeBits;
|
||||
if ("r".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
|
||||
} else if ("w".equals(mode) || "wt".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
|
||||
| ParcelFileDescriptor.MODE_CREATE
|
||||
| ParcelFileDescriptor.MODE_TRUNCATE;
|
||||
} else if ("wa".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
|
||||
| ParcelFileDescriptor.MODE_CREATE
|
||||
| ParcelFileDescriptor.MODE_APPEND;
|
||||
} else if ("rw".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
|
||||
| ParcelFileDescriptor.MODE_CREATE;
|
||||
} else if ("rwt".equals(mode)) {
|
||||
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
|
||||
| ParcelFileDescriptor.MODE_CREATE
|
||||
| ParcelFileDescriptor.MODE_TRUNCATE;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid mode: " + mode);
|
||||
}
|
||||
return modeBits;
|
||||
}
|
||||
|
||||
private static File buildPath(File base, String... segments) {
|
||||
File cur = base;
|
||||
for (String segment : segments) {
|
||||
if (segment != null) {
|
||||
cur = new File(cur, segment);
|
||||
}
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
private static String[] copyOf(String[] original, int newLength) {
|
||||
final String[] result = new String[newLength];
|
||||
System.arraycopy(original, 0, result, 0, newLength);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Object[] copyOf(Object[] original, int newLength) {
|
||||
final Object[] result = new Object[newLength];
|
||||
System.arraycopy(original, 0, result, 0, newLength);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,51 +1,121 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import static android.content.pm.PackageInstaller.EXTRA_STATUS;
|
||||
import static android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID;
|
||||
import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION;
|
||||
import static android.content.pm.PackageInstaller.STATUS_SUCCESS;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
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 com.topjohnwu.magisk.FileProvider;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.michaelrocks.paranoid.Obfuscate;
|
||||
|
||||
@Obfuscate
|
||||
public class APKInstall {
|
||||
public final class APKInstall {
|
||||
// @WorkerThread
|
||||
public static void installapk(Context context, File apk) {
|
||||
//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 pending = PendingIntent.getBroadcast(context, 0, intent, flag);
|
||||
|
||||
public static Intent installIntent(Context c, File apk) {
|
||||
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
intent.setData(FileProvider.getUriForFile(c, c.getPackageName() + ".provider", apk));
|
||||
} else {
|
||||
//noinspection ResultOfMethodCallIgnored SetWorldReadable
|
||||
apk.setReadable(true, false);
|
||||
intent.setData(Uri.fromFile(apk));
|
||||
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))) {
|
||||
OutputStream out = session.openWrite(apk.getName(), 0, apk.length());
|
||||
try (var in = new FileInputStream(apk); out) {
|
||||
transfer(in, out);
|
||||
}
|
||||
session.commit(pending.getIntentSender());
|
||||
} catch (IOException e) {
|
||||
Log.e(APKInstall.class.getSimpleName(), "", e);
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static void install(Context c, File apk) {
|
||||
c.startActivity(installIntent(c, apk));
|
||||
public static void transfer(InputStream in, OutputStream out) throws IOException {
|
||||
int size = 8192;
|
||||
var buffer = new byte[size];
|
||||
int read;
|
||||
while ((read = in.read(buffer, 0, size)) >= 0) {
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerInstallReceiver(Context c, BroadcastReceiver r) {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
||||
public static InstallReceiver register(Context context, String packageName, Runnable onSuccess) {
|
||||
var receiver = new InstallReceiver(context, packageName, onSuccess);
|
||||
var filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
|
||||
filter.addDataScheme("package");
|
||||
c.getApplicationContext().registerReceiver(r, filter);
|
||||
context.registerReceiver(receiver, filter);
|
||||
context.registerReceiver(receiver, new IntentFilter(APKInstall.class.getName()));
|
||||
return receiver;
|
||||
}
|
||||
|
||||
public static void installHideResult(Activity c, File apk) {
|
||||
Intent intent = installIntent(c, apk);
|
||||
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
|
||||
c.startActivityForResult(intent, 0); // Ignore result, use install 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 InstallReceiver(Context context, String packageName, Runnable onSuccess) {
|
||||
this.context = context;
|
||||
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;
|
||||
String pkg = data.getSchemeSpecificPart();
|
||||
if (pkg.equals(packageName)) {
|
||||
onSuccess.run();
|
||||
context.unregisterReceiver(this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
int status = i.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID);
|
||||
switch (status) {
|
||||
case STATUS_PENDING_USER_ACTION:
|
||||
intent = i.getParcelableExtra(Intent.EXTRA_INTENT);
|
||||
break;
|
||||
case STATUS_SUCCESS:
|
||||
if (onSuccess != null) onSuccess.run();
|
||||
default:
|
||||
context.unregisterReceiver(this);
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
// @WorkerThread @Nullable
|
||||
public Intent waitIntent() {
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
latch.await(5, TimeUnit.SECONDS);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,20 @@
|
||||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.pm.ProviderInfo
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.ParcelFileDescriptor.MODE_READ_ONLY
|
||||
import com.topjohnwu.magisk.FileProvider
|
||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||
import java.io.File
|
||||
|
||||
open class Provider : FileProvider() {
|
||||
class Provider : ContentProvider() {
|
||||
|
||||
override fun attachInfo(context: Context, info: ProviderInfo?) {
|
||||
override fun attachInfo(context: Context, info: ProviderInfo) {
|
||||
super.attachInfo(context.wrap(), info)
|
||||
}
|
||||
|
||||
@ -36,4 +38,11 @@ open class Provider : FileProvider() {
|
||||
fun PREFS_URI(pkg: String) =
|
||||
Uri.Builder().scheme("content").authority("$pkg.provider").path("prefs_file").build()
|
||||
}
|
||||
|
||||
override fun onCreate() = true
|
||||
override fun getType(uri: Uri): String? = null
|
||||
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?) = 0
|
||||
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?) = 0
|
||||
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? = null
|
||||
}
|
||||
|
@ -73,8 +73,12 @@ sealed class Subject : Parcelable {
|
||||
|
||||
val externalFile get() = MediaStoreUtils.getFile("$title.apk").uri
|
||||
|
||||
override fun pendingIntent(context: Context) =
|
||||
APKInstall.installIntent(context, file.toFile()).toPending(context)
|
||||
override fun pendingIntent(context: Context): PendingIntent {
|
||||
val receiver = APKInstall.register(context, null, null)
|
||||
APKInstall.installapk(context, file.toFile())
|
||||
val intent = receiver.waitIntent() ?: Intent()
|
||||
return intent.toPending(context)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.topjohnwu.magisk.core.tasks
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
@ -27,7 +26,6 @@ import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.lang.ref.WeakReference
|
||||
import java.security.SecureRandom
|
||||
|
||||
object HideAPK {
|
||||
@ -41,8 +39,6 @@ object HideAPK {
|
||||
const val MAX_LABEL_LENGTH = 32
|
||||
|
||||
private val svc get() = ServiceLocator.networkService
|
||||
private val Context.APK_URI get() = Provider.APK_URI(packageName)
|
||||
private val Context.PREFS_URI get() = Provider.PREFS_URI(packageName)
|
||||
|
||||
private fun genPackageName(): String {
|
||||
val random = SecureRandom()
|
||||
@ -92,35 +88,16 @@ object HideAPK {
|
||||
return true
|
||||
}
|
||||
|
||||
private class WaitPackageReceiver(
|
||||
private val pkg: String,
|
||||
activity: Activity
|
||||
) : BroadcastReceiver() {
|
||||
|
||||
private val activity = WeakReference(activity)
|
||||
|
||||
private fun launchApp(): Unit = activity.get()?.run {
|
||||
val intent = packageManager.getLaunchIntentForPackage(pkg) ?: return
|
||||
Config.suManager = if (pkg == APPLICATION_ID) "" else pkg
|
||||
grantUriPermission(pkg, APK_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
grantUriPermission(pkg, PREFS_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.putExtra(Const.Key.PREV_PKG, packageName)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
} ?: Unit
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action ?: return) {
|
||||
Intent.ACTION_PACKAGE_REPLACED, Intent.ACTION_PACKAGE_ADDED -> {
|
||||
val newPkg = intent.data?.encodedSchemeSpecificPart.orEmpty()
|
||||
if (newPkg == pkg) {
|
||||
context.unregisterReceiver(this)
|
||||
launchApp()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchApp(activity: Activity, pkg: String) {
|
||||
val intent = activity.packageManager.getLaunchIntentForPackage(pkg) ?: return
|
||||
Config.suManager = if (pkg == APPLICATION_ID) "" else pkg
|
||||
val self = activity.packageName
|
||||
val flag = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
activity.grantUriPermission(pkg, Provider.APK_URI(self), flag)
|
||||
activity.grantUriPermission(pkg, Provider.PREFS_URI(self), flag)
|
||||
intent.putExtra(Const.Key.PREV_PKG, self)
|
||||
activity.startActivity(intent)
|
||||
activity.finish()
|
||||
}
|
||||
|
||||
private suspend fun patchAndHide(activity: Activity, label: String): Boolean {
|
||||
@ -141,9 +118,14 @@ object HideAPK {
|
||||
return false
|
||||
|
||||
// Install and auto launch app
|
||||
APKInstall.registerInstallReceiver(activity, WaitPackageReceiver(pkg, activity))
|
||||
if (!Shell.su("adb_pm_install $repack").exec().isSuccess)
|
||||
APKInstall.installHideResult(activity, repack)
|
||||
val receiver = APKInstall.register(activity, pkg) {
|
||||
launchApp(activity, pkg)
|
||||
}
|
||||
val cmd = "adb_pm_install $repack ${activity.applicationInfo.uid}"
|
||||
if (!Shell.su(cmd).exec().isSuccess) {
|
||||
APKInstall.installapk(activity, repack)
|
||||
receiver.waitIntent()?.let { activity.startActivity(it) }
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -157,8 +139,8 @@ object HideAPK {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
patchAndHide(activity, label)
|
||||
}
|
||||
dialog.dismiss()
|
||||
if (!result) {
|
||||
dialog.dismiss()
|
||||
Utils.toast(R.string.failure, Toast.LENGTH_LONG)
|
||||
}
|
||||
}
|
||||
@ -171,11 +153,15 @@ object HideAPK {
|
||||
show()
|
||||
}
|
||||
val apk = DynAPK.current(activity)
|
||||
APKInstall.registerInstallReceiver(activity, WaitPackageReceiver(APPLICATION_ID, activity))
|
||||
Shell.su("adb_pm_install $apk").submit {
|
||||
val receiver = APKInstall.register(activity, APPLICATION_ID) {
|
||||
launchApp(activity, APPLICATION_ID)
|
||||
dialog.dismiss()
|
||||
if (!it.isSuccess)
|
||||
APKInstall.installHideResult(activity, apk)
|
||||
}
|
||||
val cmd = "adb_pm_install $apk ${activity.applicationInfo.uid}"
|
||||
Shell.su(cmd).submit(Shell.EXECUTOR) { ret ->
|
||||
if (ret.isSuccess) return@submit
|
||||
APKInstall.installapk(activity, apk)
|
||||
receiver.waitIntent()?.let { activity.startActivity(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -129,9 +129,11 @@ adb_pm_install() {
|
||||
local tmp=/data/local/tmp/temp.apk
|
||||
cp -f "$1" $tmp
|
||||
chmod 644 $tmp
|
||||
su 2000 -c pm install $tmp || pm install $tmp
|
||||
su 2000 -c pm install $tmp || pm install $tmp || su 1000 -c pm install $tmp
|
||||
local res=$?
|
||||
rm -f $tmp
|
||||
# Note: change this will kill self
|
||||
[ $res != 0 ] && appops set "$2" REQUEST_INSTALL_PACKAGES allow
|
||||
return $res
|
||||
}
|
||||
|
||||
|
@ -105,7 +105,7 @@ fun genStubManifest(srcDir: File, outDir: File): String {
|
||||
|
||||
cmpList.add(Component(
|
||||
"com.topjohnwu.magisk.core.Provider",
|
||||
"FileProvider",
|
||||
"dummy.DummyProvider",
|
||||
"""
|
||||
|<provider
|
||||
| android:name="%s"
|
||||
|
1
stub/proguard-rules.pro
vendored
1
stub/proguard-rules.pro
vendored
@ -29,5 +29,4 @@
|
||||
-allowaccessmodification
|
||||
-keepclassmembers class com.topjohnwu.magisk.dummy.* { <init>(); }
|
||||
-keepclassmembers class com.topjohnwu.magisk.DownloadActivity { <init>(); }
|
||||
-keepclassmembers class com.topjohnwu.magisk.FileProvider { <init>(); }
|
||||
-keepclassmembers class com.topjohnwu.magisk.DelegateRootService { <init>(); }
|
||||
|
@ -12,6 +12,7 @@ import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
@ -79,6 +80,7 @@ public class DownloadActivity extends Activity {
|
||||
|
||||
private void error(Throwable e) {
|
||||
Log.e(getClass().getSimpleName(), "", e);
|
||||
Toast.makeText(themed, e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
@ -111,22 +113,22 @@ public class DownloadActivity extends Activity {
|
||||
|
||||
private void dlAPK() {
|
||||
dialog = ProgressDialog.show(themed, getString(dling), getString(dling) + " " + APP_NAME, true);
|
||||
Runnable onSuccess = () -> {
|
||||
dialog.dismiss();
|
||||
Toast.makeText(themed, relaunch_app, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
};
|
||||
// Download and upgrade the app
|
||||
File apk = dynLoad ? DynAPK.current(this) : new File(getCacheDir(), "manager.apk");
|
||||
request(apkLink).setExecutor(AsyncTask.THREAD_POOL_EXECUTOR).getAsFile(apk, file -> {
|
||||
if (dynLoad) {
|
||||
DynLoad.setup(this);
|
||||
runOnUiThread(() -> {
|
||||
dialog.dismiss();
|
||||
Toast.makeText(themed, relaunch_app, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
});
|
||||
onSuccess.run();
|
||||
} else {
|
||||
runOnUiThread(() -> {
|
||||
dialog.dismiss();
|
||||
APKInstall.install(this, file);
|
||||
finish();
|
||||
});
|
||||
var receiver = APKInstall.register(this, BuildConfig.APPLICATION_ID, onSuccess);
|
||||
APKInstall.installapk(this, file);
|
||||
Intent intent = receiver.waitIntent();
|
||||
if (intent != null) startActivity(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -141,15 +143,10 @@ public class DownloadActivity extends Activity {
|
||||
InputStream is = new CipherInputStream(new ByteArrayInputStream(Bytes.res()), cipher);
|
||||
try (InputStream gzip = new GZIPInputStream(is);
|
||||
OutputStream out = new FileOutputStream(apk)) {
|
||||
byte[] buf = new byte[4096];
|
||||
for (int read; (read = gzip.read(buf)) >= 0;) {
|
||||
out.write(buf, 0, read);
|
||||
}
|
||||
APKInstall.transfer(gzip, out);
|
||||
}
|
||||
DynAPK.addAssetPath(getResources().getAssets(), apk.getPath());
|
||||
} catch (Exception e) {
|
||||
// Should not happen
|
||||
e.printStackTrace();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user