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 2270017ec..3efe27d9e 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,7 +1,10 @@
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 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 {
+ apk.setReadable(true, false);
+ intent.setData(Uri.fromFile(apk));
+ }
+ return intent;
+ }
+
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));
- } else {
- apk.setReadable(true, false);
- install.setData(Uri.fromFile(apk));
- }
- return install;
+ 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);
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 45e880079..4854eb8db 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -84,11 +84,6 @@
android:name="androidx.room.MultiInstanceInvalidationService"
tools:node="remove" />
-
-
-
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 dba447f39..bccf2ec47 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,6 +1,7 @@
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))
+ if (!patch(activity, stub, repack, pkg, label))
return false
- // Install the application
- if (!Shell.su("adb_pm_install $repack").exec().isSuccess)
- 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)
+ }
+ if (!result) {
+ Utils.toast(R.string.failure, Toast.LENGTH_LONG)
}
}
- 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)
- }
-
- 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))
}
}
diff --git a/app/src/main/java/com/topjohnwu/magisk/events/dialog/MarkDownDialog.kt b/app/src/main/java/com/topjohnwu/magisk/events/dialog/MarkDownDialog.kt
index 2891d3b39..56b95b40f 100644
--- a/app/src/main/java/com/topjohnwu/magisk/events/dialog/MarkDownDialog.kt
+++ b/app/src/main/java/com/topjohnwu/magisk/events/dialog/MarkDownDialog.kt
@@ -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(R.id.md_txt)
- tv.coroutineScope = CoroutineScope(coroutineContext)
withContext(Dispatchers.IO) {
try {
markwon.setMarkdown(tv, getMarkdownText())
diff --git a/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt b/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt
index f970649f9..040d07a53 100644
--- a/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt
+++ b/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt
@@ -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 {
- 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")
diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt
index 624eff3f0..805aa405f 100644
--- a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt
+++ b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt
@@ -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) {