Rewrite app installation

Fix  #4960
This commit is contained in:
南宫雪珊 2021-12-14 21:20:29 +08:00 committed by GitHub
parent 5a49bd3ac9
commit baa19f0ccf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 160 additions and 395 deletions

View File

@ -8,6 +8,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" /> <uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" android:maxSdkVersion="29"

View File

@ -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;
}
}

View File

@ -1,51 +1,121 @@
package com.topjohnwu.magisk.utils; 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.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.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.util.Log;
import com.topjohnwu.magisk.FileProvider;
import java.io.File; 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; import io.michaelrocks.paranoid.Obfuscate;
@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) { var installer = context.getPackageManager().getPackageInstaller();
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE); var params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); params.setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED);
if (Build.VERSION.SDK_INT >= 24) { }
intent.setData(FileProvider.getUriForFile(c, c.getPackageName() + ".provider", apk)); try (Session session = installer.openSession(installer.createSession(params))) {
} else { OutputStream out = session.openWrite(apk.getName(), 0, apk.length());
//noinspection ResultOfMethodCallIgnored SetWorldReadable try (var in = new FileInputStream(apk); out) {
apk.setReadable(true, false); transfer(in, out);
intent.setData(Uri.fromFile(apk)); }
session.commit(pending.getIntentSender());
} catch (IOException e) {
Log.e(APKInstall.class.getSimpleName(), "", e);
} }
return intent;
} }
public static void install(Context c, File apk) { public static void transfer(InputStream in, OutputStream out) throws IOException {
c.startActivity(installIntent(c, apk)); 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) { public static InstallReceiver register(Context context, String packageName, Runnable onSuccess) {
IntentFilter filter = new IntentFilter(); var receiver = new InstallReceiver(context, packageName, onSuccess);
filter.addAction(Intent.ACTION_PACKAGE_REPLACED); var filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package"); 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) { public static class InstallReceiver extends BroadcastReceiver {
Intent intent = installIntent(c, apk); private final Context context;
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true); private final String packageName;
c.startActivityForResult(intent, 0); // Ignore result, use install receiver 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;
}
} }
} }

View File

@ -1,18 +1,20 @@
package com.topjohnwu.magisk.core package com.topjohnwu.magisk.core
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.pm.ProviderInfo import android.content.pm.ProviderInfo
import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.os.ParcelFileDescriptor.MODE_READ_ONLY import android.os.ParcelFileDescriptor.MODE_READ_ONLY
import com.topjohnwu.magisk.FileProvider
import com.topjohnwu.magisk.core.su.SuCallbackHandler import com.topjohnwu.magisk.core.su.SuCallbackHandler
import java.io.File 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) super.attachInfo(context.wrap(), info)
} }
@ -36,4 +38,11 @@ open class Provider : FileProvider() {
fun PREFS_URI(pkg: String) = fun PREFS_URI(pkg: String) =
Uri.Builder().scheme("content").authority("$pkg.provider").path("prefs_file").build() 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
} }

View File

@ -73,8 +73,12 @@ sealed class Subject : Parcelable {
val externalFile get() = MediaStoreUtils.getFile("$title.apk").uri val externalFile get() = MediaStoreUtils.getFile("$title.apk").uri
override fun pendingIntent(context: Context) = override fun pendingIntent(context: Context): PendingIntent {
APKInstall.installIntent(context, file.toFile()).toPending(context) val receiver = APKInstall.register(context, null, null)
APKInstall.installapk(context, file.toFile())
val intent = receiver.waitIntent() ?: Intent()
return intent.toPending(context)
}
} }
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")

View File

@ -1,7 +1,6 @@
package com.topjohnwu.magisk.core.tasks package com.topjohnwu.magisk.core.tasks
import android.app.Activity import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.widget.Toast import android.widget.Toast
@ -27,7 +26,6 @@ import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.lang.ref.WeakReference
import java.security.SecureRandom import java.security.SecureRandom
object HideAPK { object HideAPK {
@ -41,8 +39,6 @@ object HideAPK {
const val MAX_LABEL_LENGTH = 32 const val MAX_LABEL_LENGTH = 32
private val svc get() = ServiceLocator.networkService 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 { private fun genPackageName(): String {
val random = SecureRandom() val random = SecureRandom()
@ -92,35 +88,16 @@ object HideAPK {
return true return true
} }
private class WaitPackageReceiver( private fun launchApp(activity: Activity, pkg: String) {
private val pkg: String, val intent = activity.packageManager.getLaunchIntentForPackage(pkg) ?: return
activity: Activity Config.suManager = if (pkg == APPLICATION_ID) "" else pkg
) : BroadcastReceiver() { val self = activity.packageName
val flag = Intent.FLAG_GRANT_READ_URI_PERMISSION
private val activity = WeakReference(activity) activity.grantUriPermission(pkg, Provider.APK_URI(self), flag)
activity.grantUriPermission(pkg, Provider.PREFS_URI(self), flag)
private fun launchApp(): Unit = activity.get()?.run { intent.putExtra(Const.Key.PREV_PKG, self)
val intent = packageManager.getLaunchIntentForPackage(pkg) ?: return activity.startActivity(intent)
Config.suManager = if (pkg == APPLICATION_ID) "" else pkg activity.finish()
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 suspend fun patchAndHide(activity: Activity, label: String): Boolean { private suspend fun patchAndHide(activity: Activity, label: String): Boolean {
@ -141,9 +118,14 @@ object HideAPK {
return false return false
// Install and auto launch app // Install and auto launch app
APKInstall.registerInstallReceiver(activity, WaitPackageReceiver(pkg, activity)) val receiver = APKInstall.register(activity, pkg) {
if (!Shell.su("adb_pm_install $repack").exec().isSuccess) launchApp(activity, pkg)
APKInstall.installHideResult(activity, repack) }
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 return true
} }
@ -157,8 +139,8 @@ object HideAPK {
val result = withContext(Dispatchers.IO) { val result = withContext(Dispatchers.IO) {
patchAndHide(activity, label) patchAndHide(activity, label)
} }
dialog.dismiss()
if (!result) { if (!result) {
dialog.dismiss()
Utils.toast(R.string.failure, Toast.LENGTH_LONG) Utils.toast(R.string.failure, Toast.LENGTH_LONG)
} }
} }
@ -171,11 +153,15 @@ object HideAPK {
show() show()
} }
val apk = DynAPK.current(activity) val apk = DynAPK.current(activity)
APKInstall.registerInstallReceiver(activity, WaitPackageReceiver(APPLICATION_ID, activity)) val receiver = APKInstall.register(activity, APPLICATION_ID) {
Shell.su("adb_pm_install $apk").submit { launchApp(activity, APPLICATION_ID)
dialog.dismiss() 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) }
} }
} }
} }

View File

@ -129,9 +129,11 @@ adb_pm_install() {
local tmp=/data/local/tmp/temp.apk local tmp=/data/local/tmp/temp.apk
cp -f "$1" $tmp cp -f "$1" $tmp
chmod 644 $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=$? local res=$?
rm -f $tmp rm -f $tmp
# Note: change this will kill self
[ $res != 0 ] && appops set "$2" REQUEST_INSTALL_PACKAGES allow
return $res return $res
} }

View File

@ -105,7 +105,7 @@ fun genStubManifest(srcDir: File, outDir: File): String {
cmpList.add(Component( cmpList.add(Component(
"com.topjohnwu.magisk.core.Provider", "com.topjohnwu.magisk.core.Provider",
"FileProvider", "dummy.DummyProvider",
""" """
|<provider |<provider
| android:name="%s" | android:name="%s"

View File

@ -29,5 +29,4 @@
-allowaccessmodification -allowaccessmodification
-keepclassmembers class com.topjohnwu.magisk.dummy.* { <init>(); } -keepclassmembers class com.topjohnwu.magisk.dummy.* { <init>(); }
-keepclassmembers class com.topjohnwu.magisk.DownloadActivity { <init>(); } -keepclassmembers class com.topjohnwu.magisk.DownloadActivity { <init>(); }
-keepclassmembers class com.topjohnwu.magisk.FileProvider { <init>(); }
-keepclassmembers class com.topjohnwu.magisk.DelegateRootService { <init>(); } -keepclassmembers class com.topjohnwu.magisk.DelegateRootService { <init>(); }

View File

@ -12,6 +12,7 @@ import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
@ -79,6 +80,7 @@ public class DownloadActivity extends Activity {
private void error(Throwable e) { private void error(Throwable e) {
Log.e(getClass().getSimpleName(), "", e); Log.e(getClass().getSimpleName(), "", e);
Toast.makeText(themed, e.getMessage(), Toast.LENGTH_LONG).show();
finish(); finish();
} }
@ -111,22 +113,22 @@ public class DownloadActivity extends Activity {
private void dlAPK() { private void dlAPK() {
dialog = ProgressDialog.show(themed, getString(dling), getString(dling) + " " + APP_NAME, true); 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 // Download and upgrade the app
File apk = dynLoad ? DynAPK.current(this) : new File(getCacheDir(), "manager.apk"); File apk = dynLoad ? DynAPK.current(this) : new File(getCacheDir(), "manager.apk");
request(apkLink).setExecutor(AsyncTask.THREAD_POOL_EXECUTOR).getAsFile(apk, file -> { request(apkLink).setExecutor(AsyncTask.THREAD_POOL_EXECUTOR).getAsFile(apk, file -> {
if (dynLoad) { if (dynLoad) {
DynLoad.setup(this); DynLoad.setup(this);
runOnUiThread(() -> { onSuccess.run();
dialog.dismiss();
Toast.makeText(themed, relaunch_app, Toast.LENGTH_LONG).show();
finish();
});
} else { } else {
runOnUiThread(() -> { var receiver = APKInstall.register(this, BuildConfig.APPLICATION_ID, onSuccess);
dialog.dismiss(); APKInstall.installapk(this, file);
APKInstall.install(this, file); Intent intent = receiver.waitIntent();
finish(); if (intent != null) startActivity(intent);
});
} }
}); });
} }
@ -141,15 +143,10 @@ public class DownloadActivity extends Activity {
InputStream is = new CipherInputStream(new ByteArrayInputStream(Bytes.res()), cipher); InputStream is = new CipherInputStream(new ByteArrayInputStream(Bytes.res()), cipher);
try (InputStream gzip = new GZIPInputStream(is); try (InputStream gzip = new GZIPInputStream(is);
OutputStream out = new FileOutputStream(apk)) { OutputStream out = new FileOutputStream(apk)) {
byte[] buf = new byte[4096]; APKInstall.transfer(gzip, out);
for (int read; (read = gzip.read(buf)) >= 0;) {
out.write(buf, 0, read);
}
} }
DynAPK.addAssetPath(getResources().getAssets(), apk.getPath()); DynAPK.addAssetPath(getResources().getAssets(), apk.getPath());
} catch (Exception e) { } catch (Exception ignored) {
// Should not happen
e.printStackTrace();
} }
} }