diff --git a/app/shared/src/main/AndroidManifest.xml b/app/shared/src/main/AndroidManifest.xml index 096449b52..9afc17dfb 100644 --- a/app/shared/src/main/AndroidManifest.xml +++ b/app/shared/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ + 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 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 mostSpecific = null; - for (Map.Entry 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; - } -} 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 d76b3a7e8..418bd5756 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 @@ -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; + } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/Provider.kt b/app/src/main/java/com/topjohnwu/magisk/core/Provider.kt index b5ac6ef95..6acc662af 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/Provider.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/Provider.kt @@ -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?) = 0 + override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?) = 0 + override fun query(uri: Uri, projection: Array?, selection: String?, selectionArgs: Array?, sortOrder: String?): Cursor? = null } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt index f635e805b..d9ada0970 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt @@ -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") 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 a5bdff0bc..43ede587e 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,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) } } } } diff --git a/app/src/main/res/raw/manager.sh b/app/src/main/res/raw/manager.sh index 966859752..87c56d2e7 100644 --- a/app/src/main/res/raw/manager.sh +++ b/app/src/main/res/raw/manager.sh @@ -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 } diff --git a/buildSrc/src/main/java/Codegen.kt b/buildSrc/src/main/java/Codegen.kt index 0125aad32..6e5a4bd12 100644 --- a/buildSrc/src/main/java/Codegen.kt +++ b/buildSrc/src/main/java/Codegen.kt @@ -105,7 +105,7 @@ fun genStubManifest(srcDir: File, outDir: File): String { cmpList.add(Component( "com.topjohnwu.magisk.core.Provider", - "FileProvider", + "dummy.DummyProvider", """ |(); } -keepclassmembers class com.topjohnwu.magisk.DownloadActivity { (); } --keepclassmembers class com.topjohnwu.magisk.FileProvider { (); } -keepclassmembers class com.topjohnwu.magisk.DelegateRootService { (); } diff --git a/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java b/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java index fa2810f80..e2ff4e5e1 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java @@ -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) { } }