From d7ec7f826baaec39e9c012622bec6f7179729a8c Mon Sep 17 00:00:00 2001 From: LoveSy Date: Tue, 3 Mar 2026 13:41:36 +0800 Subject: [PATCH] Migrate SuRequestActivity to Compose and clean up dead View code Rewrite SuRequestActivity and SuRequestViewModel to use Compose state and miuix components, preserving tapjack protection and security logic. Extract rememberDrawablePainter to shared utility. Remove dead code: BaseFragment, NavigationActivity, ShowUIEvent, FragmentExecutor, and UIActivity.setContentView(). Fix bottom nav icons to use outlined vector drawables instead of animated selectors. Made-with: Cursor --- .../com/topjohnwu/magisk/arch/BaseFragment.kt | 96 ---------- .../magisk/arch/NavigationActivity.kt | 50 ----- .../com/topjohnwu/magisk/arch/UIActivity.kt | 13 -- .../com/topjohnwu/magisk/arch/ViewEvent.kt | 3 - .../com/topjohnwu/magisk/events/ViewEvents.kt | 9 - .../com/topjohnwu/magisk/ui/MainScreen.kt | 10 +- .../magisk/ui/deny/DenyListScreen.kt | 18 +- .../magisk/ui/superuser/SuperuserScreen.kt | 18 +- .../magisk/ui/surequest/SuRequestActivity.kt | 24 ++- .../magisk/ui/surequest/SuRequestScreen.kt | 175 ++++++++++++++++++ .../magisk/ui/surequest/SuRequestViewModel.kt | 83 ++------- .../magisk/ui/util/DrawablePainter.kt | 22 +++ .../src/main/res/layout/activity_request.xml | 157 ---------------- 13 files changed, 244 insertions(+), 434 deletions(-) delete mode 100644 app/apk/src/main/java/com/topjohnwu/magisk/arch/BaseFragment.kt delete mode 100644 app/apk/src/main/java/com/topjohnwu/magisk/arch/NavigationActivity.kt create mode 100644 app/apk/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestScreen.kt create mode 100644 app/apk/src/main/java/com/topjohnwu/magisk/ui/util/DrawablePainter.kt delete mode 100644 app/apk/src/main/res/layout/activity_request.xml diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/arch/BaseFragment.kt b/app/apk/src/main/java/com/topjohnwu/magisk/arch/BaseFragment.kt deleted file mode 100644 index 526c7c00b..000000000 --- a/app/apk/src/main/java/com/topjohnwu/magisk/arch/BaseFragment.kt +++ /dev/null @@ -1,96 +0,0 @@ -package com.topjohnwu.magisk.arch - -import android.os.Bundle -import android.view.KeyEvent -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.MenuProvider -import androidx.databinding.DataBindingUtil -import androidx.databinding.OnRebindCallback -import androidx.databinding.ViewDataBinding -import androidx.fragment.app.Fragment -import androidx.lifecycle.Lifecycle -import androidx.navigation.NavDirections -import com.topjohnwu.magisk.BR - -abstract class BaseFragment : Fragment(), ViewModelHolder { - - val activity get() = getActivity() as? NavigationActivity<*> - protected lateinit var binding: Binding - protected abstract val layoutRes: Int - - private val navigation get() = activity?.navigation - open val snackbarView: View? get() = null - open val snackbarAnchorView: View? get() = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - startObserveLiveData() - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - binding = DataBindingUtil.inflate(inflater, layoutRes, container, false).also { - it.setVariable(BR.viewModel, viewModel) - it.lifecycleOwner = viewLifecycleOwner - } - if (this is MenuProvider) { - activity?.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.STARTED) - } - savedInstanceState?.let { viewModel.onRestoreState(it) } - return binding.root - } - - override fun onSaveInstanceState(outState: Bundle) { - viewModel.onSaveState(outState) - } - - override fun onStart() { - super.onStart() - activity?.supportActionBar?.subtitle = null - } - - override fun onEventDispatched(event: ViewEvent) = when(event) { - is ContextExecutor -> event(requireContext()) - is ActivityExecutor -> activity?.let { event(it) } ?: Unit - is FragmentExecutor -> event(this) - else -> Unit - } - - open fun onKeyEvent(event: KeyEvent): Boolean { - return false - } - - open fun onBackPressed(): Boolean = false - - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.addOnRebindCallback(object : OnRebindCallback() { - override fun onPreBind(binding: Binding): Boolean { - this@BaseFragment.onPreBind(binding) - return true - } - }) - } - - override fun onResume() { - super.onResume() - viewModel.let { - if (it is AsyncLoadViewModel) - it.startLoading() - } - } - - protected open fun onPreBind(binding: Binding) { - (binding.root as? ViewGroup)?.startAnimations() - } - - fun NavDirections.navigate() { - navigation?.currentDestination?.getAction(actionId)?.let { navigation!!.navigate(this) } - } -} diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/arch/NavigationActivity.kt b/app/apk/src/main/java/com/topjohnwu/magisk/arch/NavigationActivity.kt deleted file mode 100644 index 090b3c293..000000000 --- a/app/apk/src/main/java/com/topjohnwu/magisk/arch/NavigationActivity.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.topjohnwu.magisk.arch - -import android.content.ContentResolver -import android.view.KeyEvent -import androidx.databinding.ViewDataBinding -import androidx.navigation.NavController -import androidx.navigation.NavDirections -import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.navOptions -import com.topjohnwu.magisk.utils.AccessibilityUtils - -abstract class NavigationActivity : UIActivity() { - - abstract val navHostId: Int - - private val navHostFragment by lazy { - supportFragmentManager.findFragmentById(navHostId) as NavHostFragment - } - - protected val currentFragment get() = - navHostFragment.childFragmentManager.fragments.getOrNull(0) as? BaseFragment<*> - - val navigation: NavController get() = navHostFragment.navController - - override fun dispatchKeyEvent(event: KeyEvent): Boolean { - return if (binded && currentFragment?.onKeyEvent(event) == true) true else super.dispatchKeyEvent(event) - } - - override fun onBackPressed() { - if (binded) { - if (currentFragment?.onBackPressed() == false) { - super.onBackPressed() - } - } - } - - companion object { - fun navigate(directions: NavDirections, navigation: NavController, cr: ContentResolver) { - if (AccessibilityUtils.isAnimationEnabled(cr)) { - navigation.navigate(directions) - } else { - navigation.navigate(directions, navOptions {}) - } - } - } - - fun NavDirections.navigate() { - navigate(this, navigation, contentResolver) - } -} 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 07bae8ed7..db6056aef 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 @@ -11,13 +11,11 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.core.content.res.use import androidx.core.view.WindowCompat -import androidx.databinding.DataBindingUtil import androidx.databinding.ViewDataBinding import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.transition.AutoTransition import androidx.transition.TransitionManager 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.ActivityExtension @@ -96,17 +94,6 @@ abstract class UIActivity extension.onSaveInstanceState(outState) } - fun setContentView() { - binding = DataBindingUtil.setContentView(this, layoutRes).also { - it.setVariable(BR.viewModel, viewModel) - it.lifecycleOwner = this - } - } - - fun setAccessibilityDelegate(delegate: View.AccessibilityDelegate?) { - binding.root.rootView.accessibilityDelegate = delegate - } - fun showSnackbar( message: CharSequence, length: Int = Snackbar.LENGTH_SHORT, diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/arch/ViewEvent.kt b/app/apk/src/main/java/com/topjohnwu/magisk/arch/ViewEvent.kt index 1db4c5d98..e6e162e4b 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/arch/ViewEvent.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/arch/ViewEvent.kt @@ -16,6 +16,3 @@ interface ActivityExecutor { operator fun invoke(activity: UIActivity<*>) } -interface FragmentExecutor { - operator fun invoke(fragment: BaseFragment<*>) -} 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 32219febd..92cf03dd1 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 @@ -1,7 +1,6 @@ package com.topjohnwu.magisk.events import android.content.Context -import android.view.View import androidx.annotation.StringRes import com.google.android.material.snackbar.Snackbar import com.topjohnwu.magisk.arch.ActivityExecutor @@ -36,14 +35,6 @@ class DieEvent : ViewEvent(), ActivityExecutor { } } -class ShowUIEvent(private val delegate: View.AccessibilityDelegate?) - : ViewEvent(), ActivityExecutor { - override fun invoke(activity: UIActivity<*>) { - activity.setContentView() - activity.setAccessibilityDelegate(delegate) - } -} - class RecreateEvent : ViewEvent(), ActivityExecutor { override fun invoke(activity: UIActivity<*>) { activity.relaunch() diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/MainScreen.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/MainScreen.kt index 98c4e33d4..fcbecea47 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/MainScreen.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/MainScreen.kt @@ -50,11 +50,11 @@ import top.yukonga.miuix.kmp.basic.NavigationItem import com.topjohnwu.magisk.core.R as CoreR enum class Tab(val titleRes: Int, val iconRes: Int) { - HOME(CoreR.string.section_home, R.drawable.ic_home_md2), - SUPERUSER(CoreR.string.superuser, R.drawable.ic_superuser_md2), - LOG(CoreR.string.logs, R.drawable.ic_bug_md2), - MODULES(CoreR.string.modules, R.drawable.ic_module_md2), - SETTINGS(CoreR.string.settings, R.drawable.ic_settings_md2); + HOME(CoreR.string.section_home, R.drawable.ic_home_outlined_md2), + SUPERUSER(CoreR.string.superuser, R.drawable.ic_superuser_outlined_md2), + LOG(CoreR.string.logs, R.drawable.ic_bug_outlined_md2), + MODULES(CoreR.string.modules, R.drawable.ic_module_outlined_md2), + SETTINGS(CoreR.string.settings, R.drawable.ic_settings_outlined_md2); } @Composable diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListScreen.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListScreen.kt index ccd845a7a..1517923cd 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListScreen.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListScreen.kt @@ -1,8 +1,6 @@ package com.topjohnwu.magisk.ui.deny import androidx.compose.animation.AnimatedVisibility -import android.graphics.Bitmap -import android.graphics.drawable.Drawable import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -25,10 +23,8 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.graphics.painter.BitmapPainter -import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.dp +import com.topjohnwu.magisk.ui.util.rememberDrawablePainter import top.yukonga.miuix.kmp.basic.Card import top.yukonga.miuix.kmp.basic.Checkbox import top.yukonga.miuix.kmp.basic.CircularProgressIndicator @@ -255,15 +251,3 @@ private fun ProcessRow(proc: DenyProcessState) { } } -@Composable -private fun rememberDrawablePainter(drawable: Drawable): Painter { - return remember(drawable) { - val w = drawable.intrinsicWidth.coerceAtLeast(1) - val h = drawable.intrinsicHeight.coerceAtLeast(1) - val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) - val canvas = android.graphics.Canvas(bitmap) - drawable.setBounds(0, 0, w, h) - drawable.draw(canvas) - BitmapPainter(bitmap.asImageBitmap()) - } -} diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserScreen.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserScreen.kt index d051edc26..b90210255 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserScreen.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserScreen.kt @@ -1,7 +1,5 @@ package com.topjohnwu.magisk.ui.superuser -import android.graphics.Bitmap -import android.graphics.drawable.Drawable import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.Image import androidx.compose.foundation.clickable @@ -25,9 +23,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.graphics.painter.BitmapPainter -import androidx.compose.ui.graphics.painter.Painter +import com.topjohnwu.magisk.ui.util.rememberDrawablePainter import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.topjohnwu.magisk.core.model.su.SuPolicy @@ -213,15 +209,3 @@ private fun PolicySlider(value: Int, onValueChange: (Int) -> Unit) { } } -@Composable -private fun rememberDrawablePainter(drawable: Drawable): Painter { - return remember(drawable) { - val w = drawable.intrinsicWidth.coerceAtLeast(1) - val h = drawable.intrinsicHeight.coerceAtLeast(1) - val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) - val canvas = android.graphics.Canvas(bitmap) - drawable.setBounds(0, 0, w, h) - drawable.draw(canvas) - BitmapPainter(bitmap.asImageBitmap()) - } -} diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt index 9e04c0f53..322b3b832 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt @@ -5,8 +5,11 @@ import android.content.pm.ActivityInfo import android.content.res.Resources import android.os.Build import android.os.Bundle +import android.view.View import android.view.Window import android.view.WindowManager +import androidx.activity.compose.setContent +import androidx.databinding.ViewDataBinding import androidx.lifecycle.lifecycleScope import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.UIActivity @@ -14,17 +17,20 @@ import com.topjohnwu.magisk.arch.viewModel import com.topjohnwu.magisk.core.base.UntrackedActivity import com.topjohnwu.magisk.core.su.SuCallbackHandler import com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST -import com.topjohnwu.magisk.databinding.ActivityRequestBinding +import com.topjohnwu.magisk.ui.theme.MagiskTheme import com.topjohnwu.magisk.ui.theme.Theme import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -open class SuRequestActivity : UIActivity(), UntrackedActivity { +open class SuRequestActivity : UIActivity(), UntrackedActivity { - override val layoutRes: Int = R.layout.activity_request + override val layoutRes: Int = 0 override val viewModel: SuRequestViewModel by viewModel() + override val snackbarView: View + get() = window.decorView.findViewById(android.R.id.content) + override fun onCreate(savedInstanceState: Bundle?) { supportRequestWindowFeature(Window.FEATURE_NO_TITLE) requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED @@ -51,6 +57,17 @@ open class SuRequestActivity : UIActivity(), UntrackedAc } else { finish() } + + if (viewModel.useTapjackProtection) { + window.decorView.rootView.accessibilityDelegate = + SuRequestViewModel.EmptyAccessibilityDelegate + } + + setContent { + MagiskTheme { + SuRequestScreen(viewModel = viewModel) + } + } } override fun getTheme(): Resources.Theme { @@ -59,6 +76,7 @@ open class SuRequestActivity : UIActivity(), UntrackedAc return theme } + @Deprecated("Use OnBackPressedDispatcher") override fun onBackPressed() { viewModel.denyPressed() } diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestScreen.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestScreen.kt new file mode 100644 index 000000000..5f3e886dd --- /dev/null +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestScreen.kt @@ -0,0 +1,175 @@ +package com.topjohnwu.magisk.ui.surequest + +import android.view.MotionEvent +import android.widget.Toast +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInteropFilter +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringArrayResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.topjohnwu.magisk.core.ktx.toast +import com.topjohnwu.magisk.core.R as CoreR +import com.topjohnwu.magisk.ui.util.rememberDrawablePainter +import top.yukonga.miuix.kmp.basic.Card +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.basic.TextButton +import top.yukonga.miuix.kmp.extra.SuperDropdown +import top.yukonga.miuix.kmp.theme.MiuixTheme + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun SuRequestScreen(viewModel: SuRequestViewModel) { + if (!viewModel.showUi) return + + val context = LocalContext.current + val icon = viewModel.icon + val title = viewModel.title + val packageName = viewModel.packageName + val grantEnabled = viewModel.grantEnabled + val denyCountdown = viewModel.denyCountdown + val selectedPosition = viewModel.selectedItemPosition + val timeoutEntries = stringArrayResource(CoreR.array.allow_timeout).toList() + + val denyText = if (denyCountdown > 0) { + "${stringResource(CoreR.string.deny)} ($denyCountdown)" + } else { + stringResource(CoreR.string.deny) + } + + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Card( + modifier = Modifier + .widthIn(min = 300.dp, max = 380.dp) + .padding(24.dp) + ) { + Column( + modifier = Modifier.padding(20.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(CoreR.string.su_request_title), + style = MiuixTheme.textStyles.headline2, + textAlign = TextAlign.Center, + ) + + Spacer(Modifier.height(16.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(horizontal = 8.dp) + ) { + if (icon != null) { + Image( + painter = rememberDrawablePainter(icon), + contentDescription = null, + modifier = Modifier.size(48.dp) + ) + Spacer(Modifier.width(12.dp)) + } + Column { + Text( + text = title, + style = MiuixTheme.textStyles.body1, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = packageName, + style = MiuixTheme.textStyles.body2, + color = MiuixTheme.colorScheme.onSurfaceVariantSummary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + + Spacer(Modifier.height(16.dp)) + + SuperDropdown( + title = stringResource(CoreR.string.request_timeout), + items = timeoutEntries, + selectedIndex = selectedPosition, + onSelectedIndexChange = { index -> + viewModel.spinnerTouched() + viewModel.selectedItemPosition = index + }, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.height(12.dp)) + + Text( + text = stringResource(CoreR.string.su_warning), + style = MiuixTheme.textStyles.body2, + color = MiuixTheme.colorScheme.primary, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + ) + + Spacer(Modifier.height(16.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + TextButton( + text = denyText, + onClick = { viewModel.denyPressed() }, + modifier = Modifier.weight(1f) + ) + TextButton( + text = stringResource(CoreR.string.grant), + enabled = grantEnabled, + onClick = { viewModel.grantPressed() }, + modifier = Modifier + .weight(1f) + .then( + if (viewModel.useTapjackProtection) { + Modifier.pointerInteropFilter { event -> + if (event.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0 || + event.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0 + ) { + if (event.action == MotionEvent.ACTION_UP) { + context.toast( + CoreR.string.touch_filtered_warning, + Toast.LENGTH_SHORT + ) + } + true + } else { + false + } + } + } else Modifier + ) + ) + } + } + } + } +} diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt index 4d1a830f7..3cc3d53fd 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt @@ -1,9 +1,7 @@ package com.topjohnwu.magisk.ui.surequest -import android.annotation.SuppressLint import android.content.Intent import android.content.SharedPreferences -import android.content.res.Resources import android.graphics.drawable.Drawable import android.os.Bundle import android.os.CountDownTimer @@ -14,9 +12,11 @@ import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeProvider import android.widget.Toast -import androidx.databinding.Bindable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.lifecycle.viewModelScope -import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.arch.BaseViewModel import com.topjohnwu.magisk.core.AppContext import com.topjohnwu.magisk.core.Config @@ -27,11 +27,8 @@ import com.topjohnwu.magisk.core.ktx.toast import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.ALLOW import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.DENY import com.topjohnwu.magisk.core.su.SuRequestHandler -import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.events.AuthEvent import com.topjohnwu.magisk.events.DieEvent -import com.topjohnwu.magisk.events.ShowUIEvent -import com.topjohnwu.magisk.utils.TextHolder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.util.concurrent.TimeUnit.SECONDS @@ -41,33 +38,16 @@ class SuRequestViewModel( private val timeoutPrefs: SharedPreferences ) : BaseViewModel() { - lateinit var icon: Drawable - lateinit var title: String - lateinit var packageName: String + var icon by mutableStateOf(null) + var title by mutableStateOf("") + var packageName by mutableStateOf("") - @get:Bindable - val denyText = DenyText() + var selectedItemPosition by mutableIntStateOf(0) + var grantEnabled by mutableStateOf(false) + var denyCountdown by mutableIntStateOf(0) - @get:Bindable - var selectedItemPosition = 0 - set(value) = set(value, field, { field = it }, BR.selectedItemPosition) - - @get:Bindable - var grantEnabled = false - set(value) = set(value, field, { field = it }, BR.grantEnabled) - - @SuppressLint("ClickableViewAccessibility") - val grantTouchListener = View.OnTouchListener { _: View, event: MotionEvent -> - // Filter obscured touches by consuming them. - if (event.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0 - || event.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0) { - if (event.action == MotionEvent.ACTION_UP) { - AppContext.toast(R.string.touch_filtered_warning, Toast.LENGTH_SHORT) - } - return@OnTouchListener Config.suTapjack - } - false - } + var showUi by mutableStateOf(false) + var useTapjackProtection by mutableStateOf(false) private val handler = SuRequestHandler(AppContext.packageManager, policyDB) private val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong()) @@ -87,9 +67,8 @@ class SuRequestViewModel( respond(DENY) } - fun spinnerTouched(): Boolean { + fun spinnerTouched() { cancelTimer() - return false } fun handleRequest(intent: Intent) { @@ -107,8 +86,6 @@ class SuRequestViewModel( val app = info.applicationInfo if (app == null) { - // The request is not coming from an app process, and the UID is a - // shared UID. We have no way to know where this request comes from. icon = pm.defaultActivityIcon title = "[SharedUID] ${info.sharedUserId}" packageName = info.sharedUserId.toString() @@ -120,21 +97,14 @@ class SuRequestViewModel( } selectedItemPosition = timeoutPrefs.getInt(packageName, 0) - - // Set timer timer.start() - - // Actually show the UI - ShowUIEvent(if (Config.suTapjack) EmptyAccessibilityDelegate else null).publish() + useTapjackProtection = Config.suTapjack + showUi = true initialized = true } private fun respond(action: Int) { - if (!initialized) { - // ignore the response until showDialog done - return - } - + if (!initialized) return timer.cancel() val pos = selectedItemPosition @@ -142,14 +112,13 @@ class SuRequestViewModel( viewModelScope.launch { handler.respond(action, Config.Value.TIMEOUT_LIST[pos]) - // Kill activity after response DieEvent().publish() } } private fun cancelTimer() { timer.cancel() - denyText.seconds = 0 + denyCountdown = 0 } private inner class SuTimer( @@ -161,29 +130,15 @@ class SuRequestViewModel( if (!grantEnabled && remains <= millis - 1000) { grantEnabled = true } - denyText.seconds = (remains / 1000).toInt() + 1 + denyCountdown = (remains / 1000).toInt() + 1 } override fun onFinish() { - denyText.seconds = 0 + denyCountdown = 0 respond(DENY) } - } - inner class DenyText : TextHolder() { - var seconds = 0 - set(value) = set(value, field, { field = it }, BR.denyText) - - override fun getText(resources: Resources): CharSequence { - return if (seconds != 0) - "${resources.getString(R.string.deny)} ($seconds)" - else - resources.getString(R.string.deny) - } - } - - // Invisible for accessibility services object EmptyAccessibilityDelegate : View.AccessibilityDelegate() { override fun sendAccessibilityEvent(host: View, eventType: Int) {} override fun performAccessibilityAction(host: View, action: Int, args: Bundle?) = true diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/util/DrawablePainter.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/util/DrawablePainter.kt new file mode 100644 index 000000000..51b4f8618 --- /dev/null +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/util/DrawablePainter.kt @@ -0,0 +1,22 @@ +package com.topjohnwu.magisk.ui.util + +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.graphics.painter.Painter + +@Composable +fun rememberDrawablePainter(drawable: Drawable): Painter { + return remember(drawable) { + val w = drawable.intrinsicWidth.coerceAtLeast(1) + val h = drawable.intrinsicHeight.coerceAtLeast(1) + val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) + val canvas = android.graphics.Canvas(bitmap) + drawable.setBounds(0, 0, w, h) + drawable.draw(canvas) + BitmapPainter(bitmap.asImageBitmap()) + } +} diff --git a/app/apk/src/main/res/layout/activity_request.xml b/app/apk/src/main/res/layout/activity_request.xml deleted file mode 100644 index 8dbe49d5e..000000000 --- a/app/apk/src/main/res/layout/activity_request.xml +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -