diff --git a/app/apk/build.gradle.kts b/app/apk/build.gradle.kts index 2d6ff0998..35188a81d 100644 --- a/app/apk/build.gradle.kts +++ b/app/apk/build.gradle.kts @@ -58,6 +58,8 @@ dependencies { implementation("androidx.transition:transition:1.5.0") implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.fragment:fragment-ktx:1.8.1") + implementation("androidx.appcompat:appcompat:1.7.0") + implementation("com.google.android.material:material:1.12.0") // Make sure kapt runs with a proper kotlin-stdlib kapt(kotlin("stdlib")) diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/arch/UIActivity.kt b/app/apk/src/main/java/com/topjohnwu/magisk/arch/UIActivity.kt index 904802b09..f91d78296 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/arch/UIActivity.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/arch/UIActivity.kt @@ -1,11 +1,13 @@ package com.topjohnwu.magisk.arch +import android.content.Context import android.content.res.Resources import android.graphics.Color import android.os.Build import android.os.Bundle import android.view.View import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.core.content.res.use import androidx.core.view.WindowCompat @@ -18,14 +20,20 @@ import com.google.android.material.snackbar.Snackbar import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.R import com.topjohnwu.magisk.core.Config -import com.topjohnwu.magisk.core.base.BaseActivity +import com.topjohnwu.magisk.core.base.ActivityExtension +import com.topjohnwu.magisk.core.base.IActivityExtension +import com.topjohnwu.magisk.core.isRunningAsStub +import com.topjohnwu.magisk.core.ktx.reflectField +import com.topjohnwu.magisk.core.wrap import rikka.insets.WindowInsetsHelper import rikka.layoutinflater.view.LayoutInflaterFactory -abstract class UIActivity : BaseActivity(), ViewModelHolder { +abstract class UIActivity + : AppCompatActivity(), ViewModelHolder, IActivityExtension { protected lateinit var binding: Binding protected abstract val layoutRes: Int + override val extension = ActivityExtension(this) protected val binded get() = ::binding.isInitialized @@ -36,10 +44,23 @@ abstract class UIActivity : BaseActivity(), ViewModel AppCompatDelegate.setDefaultNightMode(Config.darkTheme) } + override fun attachBaseContext(base: Context) { + super.attachBaseContext(base.wrap()) + } + override fun onCreate(savedInstanceState: Bundle?) { layoutInflater.factory2 = LayoutInflaterFactory(delegate) .addOnViewCreatedListener(WindowInsetsHelper.LISTENER) + extension.onCreate(savedInstanceState) + if (isRunningAsStub) { + // Overwrite private members to avoid nasty "false" stack traces being logged + val delegate = delegate + val clz = delegate.javaClass + clz.reflectField("mActivityHandlesConfigFlagsChecked").set(delegate, true) + clz.reflectField("mActivityHandlesConfigFlags").set(delegate, 0) + } + super.onCreate(savedInstanceState) startObserveLiveData() @@ -70,6 +91,11 @@ abstract class UIActivity : BaseActivity(), ViewModel } } + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + extension.onSaveInstanceState(outState) + } + fun setContentView() { binding = DataBindingUtil.setContentView(this, layoutRes).also { it.setVariable(BR.viewModel, viewModel) diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/dialog/EnvFixDialog.kt b/app/apk/src/main/java/com/topjohnwu/magisk/dialog/EnvFixDialog.kt index e48c310dd..a7fc631e3 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/dialog/EnvFixDialog.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/dialog/EnvFixDialog.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.lifecycleScope import com.topjohnwu.magisk.R import com.topjohnwu.magisk.core.BuildConfig import com.topjohnwu.magisk.core.Info -import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.tasks.MagiskInstaller import com.topjohnwu.magisk.events.DialogBuilder import com.topjohnwu.magisk.ui.home.HomeViewModel @@ -27,7 +26,7 @@ class EnvFixDialog(private val vm: HomeViewModel, private val code: Int) : Dialo resetButtons() setCancelable(false) } - (dialog.ownerActivity as BaseActivity).lifecycleScope.launch { + dialog.activity.lifecycleScope.launch { MagiskInstaller.FixEnv { dialog.dismiss() }.exec() diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt b/app/apk/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt index 490daa825..c73fd31f6 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt @@ -11,6 +11,7 @@ import com.topjohnwu.magisk.arch.NavigationActivity import com.topjohnwu.magisk.arch.UIActivity import com.topjohnwu.magisk.arch.ViewEvent import com.topjohnwu.magisk.core.base.ContentResultCallback +import com.topjohnwu.magisk.core.base.relaunch import com.topjohnwu.magisk.utils.TextHolder import com.topjohnwu.magisk.utils.asText import com.topjohnwu.magisk.view.MagiskDialog @@ -47,7 +48,7 @@ class ShowUIEvent(private val delegate: View.AccessibilityDelegate?) class RecreateEvent : ViewEvent(), ActivityExecutor { override fun invoke(activity: UIActivity<*>) { - activity.recreate() + activity.relaunch() } } @@ -56,8 +57,7 @@ class AuthEvent( ) : ViewEvent(), ActivityExecutor { override fun invoke(activity: UIActivity<*>) { - activity.authenticateCallback = { if (it) callback() } - activity.requestAuthenticate.launch(Unit) + activity.withAuthentication { if (it) callback() } } } diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt index 902a8cfe7..7dae89592 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt @@ -17,6 +17,8 @@ import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.JobService +import com.topjohnwu.magisk.core.base.realCallingPackage +import com.topjohnwu.magisk.core.base.relaunch import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.isRunningAsStub import com.topjohnwu.magisk.core.ktx.toast diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/home/RebootMenu.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/home/RebootMenu.kt index 5602a8261..44f1f0a3d 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/home/RebootMenu.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/home/RebootMenu.kt @@ -1,5 +1,6 @@ package com.topjohnwu.magisk.ui.home +import android.app.Activity import android.os.Build import android.os.PowerManager import android.view.ContextThemeWrapper @@ -9,7 +10,6 @@ import androidx.core.content.getSystemService import com.topjohnwu.magisk.R import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Const -import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.ktx.reboot as systemReboot object RebootMenu { @@ -32,7 +32,7 @@ object RebootMenu { return true } - fun inflate(activity: BaseActivity): PopupMenu { + fun inflate(activity: Activity): PopupMenu { val themeWrapper = ContextThemeWrapper(activity, R.style.Foundation_PopupMenu) val menu = PopupMenu(themeWrapper, activity.findViewById(R.id.action_reboot)) activity.menuInflater.inflate(R.menu.menu_reboot, menu.menu) diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt b/app/apk/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt index 8317d04b9..df1e23635 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt @@ -21,7 +21,7 @@ import com.google.android.material.color.MaterialColors import com.google.android.material.shape.MaterialShapeDrawable import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.R -import com.topjohnwu.magisk.core.base.BaseActivity +import com.topjohnwu.magisk.arch.UIActivity import com.topjohnwu.magisk.databinding.DialogMagiskBaseBinding import com.topjohnwu.magisk.databinding.DiffItem import com.topjohnwu.magisk.databinding.ItemWrapper @@ -42,7 +42,7 @@ class MagiskDialog( DialogMagiskBaseBinding.inflate(LayoutInflater.from(context)) private val data = Data() - val activity: BaseActivity get() = ownerActivity as BaseActivity + val activity: UIActivity<*> get() = ownerActivity as UIActivity<*> init { binding.setVariable(BR.data, data) diff --git a/app/core/src/main/res/drawable/avd_bug_from_filled.xml b/app/apk/src/main/res/drawable/avd_bug_from_filled.xml similarity index 97% rename from app/core/src/main/res/drawable/avd_bug_from_filled.xml rename to app/apk/src/main/res/drawable/avd_bug_from_filled.xml index ec6640d2f..b56ed84de 100644 --- a/app/core/src/main/res/drawable/avd_bug_from_filled.xml +++ b/app/apk/src/main/res/drawable/avd_bug_from_filled.xml @@ -18,7 +18,7 @@ , Parcelable { fun onActivityLaunch() {} @@ -32,60 +29,55 @@ interface ContentResultCallback: ActivityResultCallback, Parcelable { interface UntrackedActivity -abstract class BaseActivity : AppCompatActivity() { +interface IActivityExtension { + val extension: ActivityExtension + fun withPermission(permission: String, callback: (Boolean) -> Unit) { + extension.withPermission(permission, callback) + } + fun withAuthentication(callback: (Boolean) -> Unit) { + extension.withAuthentication(callback) + } + fun getContent(type: String, callback: ContentResultCallback) { + extension.getContent(type, callback) + } +} + +class ActivityExtension(private val activity: ComponentActivity) { private var permissionCallback: ((Boolean) -> Unit)? = null - private val requestPermission = registerForActivityResult(RequestPermission()) { + private val requestPermission = activity.registerForActivityResult(RequestPermission()) { permissionCallback?.invoke(it) permissionCallback = null } private var installCallback: ((Boolean) -> Unit)? = null - private val requestInstall = registerForActivityResult(RequestInstall()) { + private val requestInstall = activity.registerForActivityResult(RequestInstall()) { installCallback?.invoke(it) installCallback = null } - var authenticateCallback: ((Boolean) -> Unit)? = null - val requestAuthenticate = registerForActivityResult(RequestAuthentication()) { + private var authenticateCallback: ((Boolean) -> Unit)? = null + private val requestAuthenticate = activity.registerForActivityResult(RequestAuthentication()) { authenticateCallback?.invoke(it) authenticateCallback = null } private var contentCallback: ContentResultCallback? = null - private val getContent = registerForActivityResult(GetContent()) { + private val getContent = activity.registerForActivityResult(GetContent()) { if (it != null) contentCallback?.onActivityResult(it) contentCallback = null } - private val mReferrerField by lazy(LazyThreadSafetyMode.NONE) { - Activity::class.java.reflectField("mReferrer") - } - - val realCallingPackage: String? get() { - callingPackage?.let { return it } - mReferrerField.get(this)?.let { return it as String } - return null - } - - override fun attachBaseContext(base: Context) { - super.attachBaseContext(base.wrap()) - } - - override fun onCreate(savedInstanceState: Bundle?) { - if (isRunningAsStub) { - // Overwrite private members to avoid nasty "false" stack traces being logged - val delegate = delegate - val clz = delegate.javaClass - clz.reflectField("mActivityHandlesConfigFlagsChecked").set(delegate, true) - clz.reflectField("mActivityHandlesConfigFlags").set(delegate, 0) + fun onCreate(savedInstanceState: Bundle?) { + contentCallback = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + savedInstanceState?.getParcelable(CONTENT_CALLBACK_KEY) + } else { + savedInstanceState + ?.getParcelable(CONTENT_CALLBACK_KEY, ContentResultCallback::class.java) } - contentCallback = savedInstanceState?.getParcelable(CONTENT_CALLBACK_KEY) - super.onCreate(savedInstanceState) } - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) + fun onSaveInstanceState(outState: Bundle) { contentCallback?.let { outState.putParcelable(CONTENT_CALLBACK_KEY, it) } @@ -113,27 +105,37 @@ abstract class BaseActivity : AppCompatActivity() { } } + fun withAuthentication(callback: (Boolean) -> Unit) { + authenticateCallback = callback + requestAuthenticate.launch(Unit) + } + fun getContent(type: String, callback: ContentResultCallback) { contentCallback = callback try { getContent.launch(type) callback.onActivityLaunch() } catch (e: ActivityNotFoundException) { - toast(R.string.app_not_found, Toast.LENGTH_SHORT) + activity.toast(R.string.app_not_found, Toast.LENGTH_SHORT) } } - override fun recreate() { - startActivity(Intent().setComponent(intent.component)) - finish() - } - - fun relaunch() { - startActivity(Intent(intent).setFlags(0)) - finish() - } - companion object { private const val CONTENT_CALLBACK_KEY = "content_callback" } } + +private val mReferrerField by lazy(LazyThreadSafetyMode.NONE) { + Activity::class.java.reflectField("mReferrer") +} + +val Activity.realCallingPackage: String? get() { + callingPackage?.let { return it } + mReferrerField.get(this)?.let { return it as String } + return null +} + +fun Activity.relaunch() { + startActivity(Intent(intent).setFlags(0)) + finish() +} diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/download/DownloadEngine.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/download/DownloadEngine.kt index e67da1f10..7d6874928 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/download/DownloadEngine.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/download/DownloadEngine.kt @@ -10,6 +10,7 @@ import android.content.Context import android.net.Uri import android.os.Build import android.os.Bundle +import androidx.activity.ComponentActivity import androidx.collection.SparseArrayCompat import androidx.collection.isNotEmpty import androidx.core.content.getSystemService @@ -20,7 +21,7 @@ import com.topjohnwu.magisk.core.ActivityTracker import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.JobService import com.topjohnwu.magisk.core.R -import com.topjohnwu.magisk.core.base.BaseActivity +import com.topjohnwu.magisk.core.base.IActivityExtension import com.topjohnwu.magisk.core.cmp import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.intent @@ -129,7 +130,10 @@ class DownloadEngine( } @SuppressLint("InlinedApi") - fun startWithActivity(activity: BaseActivity, subject: Subject) { + fun startWithActivity( + activity: T, + subject: Subject + ) where T : ComponentActivity, T : IActivityExtension { activity.withPermission(Manifest.permission.POST_NOTIFICATIONS) { // Always download regardless of notification permission status start(activity.applicationContext, subject) diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XAndroid.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XAndroid.kt index b077a1645..dd711d192 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XAndroid.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XAndroid.kt @@ -18,7 +18,6 @@ import android.os.Process import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.Toast -import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.getSystemService import com.topjohnwu.magisk.core.utils.RootUtils import com.topjohnwu.magisk.core.utils.currentLocale @@ -28,7 +27,7 @@ import java.io.File import kotlin.String fun Context.getBitmap(id: Int): Bitmap { - var drawable = AppCompatResources.getDrawable(this, id)!! + var drawable = getDrawable(id)!! if (drawable is BitmapDrawable) return drawable.bitmap if (SDK_INT >= Build.VERSION_CODES.O && drawable is AdaptiveIconDrawable) { diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt index 2af595dca..9a168d476 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt @@ -8,6 +8,7 @@ import android.content.res.Resources import com.topjohnwu.magisk.core.ActivityTracker import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.R +import com.topjohnwu.magisk.core.base.relaunch import com.topjohnwu.magisk.core.createNewResources import com.topjohnwu.magisk.core.di.AppContext import kotlinx.coroutines.Dispatchers @@ -84,5 +85,5 @@ fun refreshLocale() { } Locale.setDefault(currentLocale) AppContext.resources.syncLocale() - ActivityTracker.foreground?.recreate() + ActivityTracker.foreground?.relaunch() }