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