Commit 331b1f54 authored by topjohnwu's avatar topjohnwu

Use standard Android APIs for install and launch

parent ccb55205
package com.topjohnwu.magisk.utils;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Build;
......@@ -10,20 +13,33 @@ import com.topjohnwu.magisk.FileProvider;
import java.io.File;
public class APKInstall {
public static void install(Context c, File apk) {
c.startActivity(installIntent(c, apk));
}
public static Intent installIntent(Context c, File apk) {
Intent install = new Intent(Intent.ACTION_INSTALL_PACKAGE);
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
install.setData(FileProvider.getUriForFile(c, c.getPackageName() + ".provider", 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 {
apk.setReadable(true, false);
install.setData(Uri.fromFile(apk));
intent.setData(Uri.fromFile(apk));
}
return install;
return intent;
}
public static void install(Context c, File apk) {
c.startActivity(installIntent(c, apk));
}
public static void installAndWait(Activity c, File apk, BroadcastReceiver r) {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package");
c.getApplicationContext().registerReceiver(r, filter);
Intent intent = installIntent(c, apk);
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
c.startActivityForResult(intent, 0);
}
}
......@@ -84,11 +84,6 @@
android:name="androidx.room.MultiInstanceInvalidationService"
tools:node="remove" />
<!-- We don't use Device Credentials -->
<activity
android:name="androidx.biometric.DeviceCredentialHandlerActivity"
tools:node="remove" />
</application>
</manifest>
package com.topjohnwu.magisk.core.tasks
import android.app.ProgressDialog
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast
......@@ -16,18 +17,17 @@ import com.topjohnwu.magisk.core.utils.Keygen
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.ktx.inject
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.signing.JarMap
import com.topjohnwu.signing.SignApk
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
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 {
......@@ -92,8 +92,39 @@ object HideAPK {
return true
}
private suspend fun patchAndHide(context: Context, label: String): Boolean {
val stub = File(context.cacheDir, "stub.apk")
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 suspend fun patchAndHide(activity: Activity, label: String): Boolean {
val stub = File(activity.cacheDir, "stub.apk")
try {
svc.fetchFile(Info.remote.stub.link).byteStream().writeTo(stub)
} catch (e: IOException) {
......@@ -102,71 +133,29 @@ object HideAPK {
}
// Generate a new random package name and signature
val repack = File(context.cacheDir, "patched.apk")
val repack = File(activity.cacheDir, "patched.apk")
val pkg = genPackageName()
Config.keyStoreRaw = ""
if (!patch(context, stub, repack, pkg, label))
return false
// Install the application
if (!Shell.su("adb_pm_install $repack").exec().isSuccess)
if (!patch(activity, stub, repack, pkg, label))
return false
context.apply {
val intent = packageManager.getLaunchIntentForPackage(pkg) ?: return false
Config.suManager = 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)
}
// Install and auto launch app
APKInstall.installAndWait(activity, repack, WaitPackageReceiver(pkg, activity))
return true
}
@Suppress("DEPRECATION")
fun hide(context: Context, label: String) {
val dialog = ProgressDialog.show(context, context.getString(R.string.hide_app_title), "", true)
GlobalScope.launch {
val result = withContext(Dispatchers.IO) {
patchAndHide(context, label)
}
if (!result) {
Utils.toast(R.string.failure, Toast.LENGTH_LONG)
dialog.dismiss()
}
suspend fun hide(activity: Activity, label: String) {
val result = withContext(Dispatchers.IO) {
patchAndHide(activity, label)
}
}
private fun restoreImpl(context: Context): Boolean {
val apk = DynAPK.current(context)
if (!Shell.su("adb_pm_install $apk").exec().isSuccess)
return false
context.apply {
val intent = packageManager.getLaunchIntentForPackage(APPLICATION_ID) ?: return false
Config.suManager = ""
grantUriPermission(APPLICATION_ID, APK_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
grantUriPermission(APPLICATION_ID, PREFS_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.putExtra(Const.Key.PREV_PKG, packageName)
startActivity(intent)
if (!result) {
Utils.toast(R.string.failure, Toast.LENGTH_LONG)
}
return true
}
@Suppress("DEPRECATION")
fun restore(context: Context) {
val dialog = ProgressDialog.show(context, context.getString(R.string.restore_img_msg), "", true)
GlobalScope.launch {
val result = withContext(Dispatchers.IO) {
restoreImpl(context)
}
if (!result) {
Utils.toast(R.string.failure, Toast.LENGTH_LONG)
dialog.dismiss()
}
}
fun restore(activity: Activity) {
val apk = DynAPK.current(activity)
APKInstall.installAndWait(activity, apk, WaitPackageReceiver(APPLICATION_ID, activity))
}
}
......@@ -6,10 +6,8 @@ import androidx.annotation.CallSuper
import androidx.lifecycle.lifecycleScope
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.ktx.coroutineScope
import com.topjohnwu.magisk.view.MagiskDialog
import io.noties.markwon.Markwon
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
......@@ -31,7 +29,6 @@ abstract class MarkDownDialog : DialogEvent(), KoinComponent {
applyView(view)
(ownerActivity as BaseActivity).lifecycleScope.launch {
val tv = view.findViewById<TextView>(R.id.md_txt)
tv.coroutineScope = CoroutineScope(coroutineContext)
withContext(Dispatchers.IO) {
try {
markwon.setMarkdown(tv, getMarkdownText())
......
......@@ -21,7 +21,6 @@ import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.LayerDrawable
import android.net.Uri
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.system.Os
import android.text.PrecomputedText
......@@ -42,11 +41,13 @@ import androidx.core.widget.TextViewCompat
import androidx.databinding.BindingAdapter
import androidx.fragment.app.Fragment
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.lifecycle.lifecycleScope
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.AssetHack
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.utils.DynamicClassLoader
import com.topjohnwu.magisk.utils.Utils
......@@ -249,15 +250,13 @@ fun Context.colorStateListCompat(@ColorRes id: Int) = try {
null
}
fun Context.drawableCompat(@DrawableRes id: Int) = ContextCompat.getDrawable(this, id)
fun Context.drawableCompat(@DrawableRes id: Int) = AppCompatResources.getDrawable(this, id)
/**
* Pass [start] and [end] dimensions, function will return left and right
* with respect to RTL layout direction
*/
fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
if (SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
) {
if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
return end to start
}
return start to end
......@@ -317,8 +316,21 @@ fun ViewGroup.startAnimations() {
)
}
val View.activity: Activity get() {
var context = context
while(true) {
if (context !is ContextWrapper)
error("View is not attached to activity")
if (context is Activity)
return context
context = context.baseContext
}
}
var View.coroutineScope: CoroutineScope
get() = getTag(R.id.coroutineScope) as? CoroutineScope ?: GlobalScope
get() = getTag(R.id.coroutineScope) as? CoroutineScope
?: (activity as? BaseActivity)?.lifecycleScope
?: GlobalScope
set(value) = setTag(R.id.coroutineScope, value)
@set:BindingAdapter("precomputedText")
......
......@@ -21,6 +21,7 @@ import com.topjohnwu.magisk.data.database.RepoDao
import com.topjohnwu.magisk.events.AddHomeIconEvent
import com.topjohnwu.magisk.events.RecreateEvent
import com.topjohnwu.magisk.events.dialog.BiometricEvent
import com.topjohnwu.magisk.ktx.activity
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch
......@@ -105,16 +106,18 @@ class SettingsViewModel(
is Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().publish()
is ClearRepoCache -> clearRepoCache()
is SystemlessHosts -> createHosts()
is Restore -> HideAPK.restore(view.context)
is Restore -> HideAPK.restore(view.activity)
is AddShortcut -> AddHomeIconEvent().publish()
else -> callback()
}
override fun onItemChanged(view: View, item: BaseSettingsItem) = when (item) {
is Language -> RecreateEvent().publish()
is UpdateChannel -> openUrlIfNecessary(view)
is Hide -> HideAPK.hide(view.context, item.value)
else -> Unit
override fun onItemChanged(view: View, item: BaseSettingsItem) {
when (item) {
is Language -> RecreateEvent().publish()
is UpdateChannel -> openUrlIfNecessary(view)
is Hide -> viewModelScope.launch { HideAPK.hide(view.activity, item.value) }
else -> Unit
}
}
private fun openUrlIfNecessary(view: View) {
......
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