Unverified Commit baa19f0c authored by 南宫雪珊's avatar 南宫雪珊 Committed by GitHub

Rewrite app installation

Fix  #4960
parent 5a49bd3a
......@@ -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"
......
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 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));
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);
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;
}
}
}
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")
......
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"
......
......@@ -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) {
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment