Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 927a8853 authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Android (Google) Code Review
Browse files

Merge "[Media] Falsing protection." into main

parents 3644dfcd a1992368
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -82,6 +82,7 @@ fun DismissibleHorizontalPager(
    modifier: Modifier = Modifier,
    key: ((Int) -> Any)? = null,
    pageSpacing: Dp = 0.dp,
    isFalseTouchDetected: Boolean,
    indicator: @Composable BoxScope.() -> Unit,
    pageContent: @Composable PagerScope.(page: Int) -> Unit,
) {
@@ -142,7 +143,7 @@ fun DismissibleHorizontalPager(
    Box(modifier = modifier) {
        HorizontalPager(
            state = state.pagerState,
            userScrollEnabled = state.isScrollingEnabled,
            userScrollEnabled = state.isScrollingEnabled && !isFalseTouchDetected,
            key = key,
            pageSpacing = pageSpacing,
            pageContent = pageContent,
+23 −2
Original line number Diff line number Diff line
@@ -36,6 +36,8 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.DragInteraction
@@ -72,7 +74,9 @@ import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -89,6 +93,7 @@ import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.clipRect
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.Layout
@@ -202,6 +207,8 @@ private fun CardCarouselContent(
        ) {
            viewModel.cards.size
        }
    var isFalseTouchDetected: Boolean by
        remember(behavior.isCarouselScrollFalseTouch) { mutableStateOf(false) }

    val roundedCornerShape = RoundedCornerShape(32.dp)

@@ -229,7 +236,16 @@ private fun CardCarouselContent(
                )
            }
        },
        modifier = modifier.padding(8.dp).clip(roundedCornerShape),
        isFalseTouchDetected = isFalseTouchDetected,
        modifier =
            modifier.padding(8.dp).clip(roundedCornerShape).pointerInput(behavior) {
                if (behavior.isCarouselScrollFalseTouch != null) {
                    awaitEachGesture {
                        awaitFirstDown(false, PointerEventPass.Initial)
                        isFalseTouchDetected = behavior.isCarouselScrollFalseTouch.invoke()
                    }
                }
            },
    ) { index ->
        Card(
            viewModel = viewModel.cards[index],
@@ -1084,7 +1100,12 @@ data class MediaUiBehavior(
    val isCarouselDismissible: Boolean = true,
    val isCarouselScrollingEnabled: Boolean = true,
    val carouselVisibility: MediaCarouselVisibility = MediaCarouselVisibility.WhenNotEmpty,
    val isFalsingProtectionNeeded: Boolean = false,
    /**
     * If provided, this callback will be consulted at the beginning of each carousel scroll gesture
     * to see if the falsing system thinks that it's a false touch. If it then returns `true`, the
     * scroll will be canceled.
     */
    val isCarouselScrollFalseTouch: (() -> Boolean)? = null,
)

@Stable
+72 −14
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.ImageBitmap
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.domain.interactor.runIfNotFalseTap
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -31,6 +33,7 @@ import com.android.systemui.media.remedia.domain.interactor.MediaInteractor
import com.android.systemui.media.remedia.domain.model.MediaActionModel
import com.android.systemui.media.remedia.shared.model.MediaColorScheme
import com.android.systemui.media.remedia.shared.model.MediaSessionState
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -43,6 +46,7 @@ class MediaViewModel
@AssistedInject
constructor(
    private val interactor: MediaInteractor,
    private val falsingSystem: FalsingSystem,
    @Assisted private val context: Context,
    @Assisted private val carouselVisibility: MediaCarouselVisibility,
) : ExclusiveActivatable() {
@@ -106,10 +110,12 @@ constructor(
                                    seekProgress = progress
                                },
                                onScrubFinished = {
                                    if (!falsingSystem.isFalseTouch(Classifier.MEDIA_SEEKBAR)) {
                                        interactor.seek(
                                            sessionKey = session.key,
                                            to = (seekProgress * session.durationMs).roundToLong(),
                                        )
                                    }
                                    isScrubbing = false
                                },
                            )
@@ -139,20 +145,36 @@ constructor(
                                                R.string.controls_media_dismiss_button
                                            ),
                                        onClick = {
                                            falsingSystem.runIfNotFalseTap(
                                                FalsingManager.LOW_PENALTY
                                            ) {
                                                interactor.hide(session.key)
                                                isGutsVisible = false
                                            }
                                        },
                                    )
                                } else {
                                    MediaGutsButtonViewModel(
                                        text = context.getString(R.string.cancel),
                                        onClick = { isGutsVisible = false },
                                        onClick = {
                                            falsingSystem.runIfNotFalseTap(
                                                FalsingManager.LOW_PENALTY
                                            ) {
                                                isGutsVisible = false
                                            }
                                        },
                                    )
                                },
                            secondaryAction =
                                MediaGutsButtonViewModel(
                                        text = context.getString(R.string.cancel),
                                        onClick = { isGutsVisible = false },
                                        onClick = {
                                            falsingSystem.runIfNotFalseTap(
                                                FalsingManager.LOW_PENALTY
                                            ) {
                                                isGutsVisible = false
                                            }
                                        },
                                    )
                                    .takeIf { session.canBeHidden },
                            settingsButton =
@@ -165,7 +187,11 @@ constructor(
                                                    res = R.string.controls_media_settings_button
                                                ),
                                        ),
                                    onClick = { interactor.openMediaSettings() },
                                    onClick = {
                                        falsingSystem.runIfNotFalseTap(FalsingManager.LOW_PENALTY) {
                                            interactor.openMediaSettings()
                                        }
                                    },
                                ),
                            onLongClick = { isGutsVisible = false },
                        )
@@ -178,7 +204,12 @@ constructor(
                                icon = session.outputDevice.icon,
                                text = session.outputDevice.name,
                                onClick = {
                                    // TODO(b/397989775): tell the UI to show the output switcher.
                                    falsingSystem.runIfNotFalseTap(
                                        FalsingManager.MODERATE_PENALTY
                                    ) {
                                        // TODO(b/397989775): tell the UI to show the output
                                        // switcher.
                                    }
                                },
                            )
                        )
@@ -189,12 +220,16 @@ constructor(
                        return MediaSecondaryActionViewModel.Action(
                            icon = session.outputDevice.icon,
                            onClick = {
                                falsingSystem.runIfNotFalseTap(FalsingManager.MODERATE_PENALTY) {
                                    // TODO(b/397989775): tell the UI to show the output switcher.
                                }
                            },
                        )
                    }

                override val onClick = session.onClick
                override val onClick = {
                    falsingSystem.runIfNotFalseTap(FalsingManager.LOW_PENALTY) { session.onClick() }
                }
                override val onClickLabel =
                    context.getString(R.string.controls_media_playing_item_description)
                override val onLongClick = { isGutsVisible = true }
@@ -230,7 +265,14 @@ constructor(
                MediaPlayPauseActionViewModel(
                    state = mediaSessionState,
                    icon = icon,
                    onClick = onClick ?: {},
                    onClick =
                        onClick?.let {
                            {
                                falsingSystem.runIfNotFalseTap(FalsingManager.MODERATE_PENALTY) {
                                    it()
                                }
                            }
                        },
                )
            is MediaActionModel.None,
            is MediaActionModel.ReserveSpace -> null
@@ -240,12 +282,28 @@ constructor(
    private fun MediaActionModel.toSecondaryActionViewModel(): MediaSecondaryActionViewModel {
        return when (this) {
            is MediaActionModel.Action ->
                MediaSecondaryActionViewModel.Action(icon = icon, onClick = onClick)
                MediaSecondaryActionViewModel.Action(
                    icon = icon,
                    onClick =
                        onClick?.let {
                            {
                                falsingSystem.runIfNotFalseTap(FalsingManager.MODERATE_PENALTY) {
                                    it()
                                }
                            }
                        },
                )
            is MediaActionModel.ReserveSpace -> MediaSecondaryActionViewModel.ReserveSpace
            is MediaActionModel.None -> MediaSecondaryActionViewModel.None
        }
    }

    interface FalsingSystem {
        fun runIfNotFalseTap(@FalsingManager.Penalty penalty: Int, block: () -> Unit)

        fun isFalseTouch(@Classifier.InteractionType interactionType: Int): Boolean
    }

    @AssistedFactory
    interface Factory {
        fun create(context: Context, carouselVisibility: MediaCarouselVisibility): MediaViewModel