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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -