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

Commit 96cdb8a2 authored by Matt Pietal's avatar Matt Pietal
Browse files

[DO NOT MERGE] Bouncer - Intercept touch events when not visible

There is a moment, when swiping on the lockscreen, that the bouncer
showing state is set to true but it isn't quite visible. In this
state, it is possible to interact with it.

Only allow interaction if the bouncer is at least 90% visible.

Also, refactor bouncer repository to an interface and add a
FakeKeyguardBouncerRepository so it can be tested with coroutines.

Test: atest PrimaryBouncerInteractorTest
PrimaryBouncerInteractorWithCoroutinesTest
Fixes: 260820843

Change-Id: I78cdf14521cc8cd02b6f2ab06e7ca518c92718c0
parent d2fe4b9e
Loading
Loading
Loading
Loading
+22 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.keyguard;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.FrameLayout;

/**
@@ -33,7 +34,7 @@ import android.widget.FrameLayout;
public class KeyguardHostView extends FrameLayout {

    protected ViewMediatorCallback mViewMediatorCallback;

    private boolean mIsInteractable;

    public KeyguardHostView(Context context) {
        this(context, null);
@@ -54,4 +55,24 @@ public class KeyguardHostView extends FrameLayout {
    public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
        mViewMediatorCallback = viewMediatorCallback;
    }

    /** Set true if the view can be interacted with */
    public void setInteractable(boolean isInteractable) {
        mIsInteractable = isInteractable;
    }

    /**
     * Make sure to disallow touches while transitioning the bouncer, otherwise
     * it can remain interactable even when barely visible.
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return !mIsInteractable;
    }

    /** True to consume any events that are sent to it */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return true;
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -527,4 +527,9 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
            mKeyguardSecurityContainerController.updateKeyguardPosition(x);
        }
    }

    /** Set true if the view can be interacted with */
    public void setInteractable(boolean isInteractable) {
        mView.setInteractable(isInteractable);
    }
}
+102 −37
Original line number Diff line number Diff line
@@ -41,31 +41,96 @@ import kotlinx.coroutines.flow.map
 *
 * Make sure to add newly added flows to the logger.
 */
interface KeyguardBouncerRepository {
    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
    val primaryBouncerVisible: StateFlow<Boolean>
    val primaryBouncerShow: StateFlow<KeyguardBouncerModel?>
    val primaryBouncerShowingSoon: StateFlow<Boolean>
    val primaryBouncerHide: StateFlow<Boolean>
    val primaryBouncerStartingToHide: StateFlow<Boolean>
    val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
    val primaryBouncerScrimmed: StateFlow<Boolean>
    /**
     * Set how much of the notification panel is showing on the screen.
     * ```
     *      0f = panel fully hidden = bouncer fully showing
     *      1f = panel fully showing = bouncer fully hidden
     * ```
     */
    val panelExpansionAmount: StateFlow<Float>
    val keyguardPosition: StateFlow<Float>
    val onScreenTurnedOff: StateFlow<Boolean>
    val isBackButtonEnabled: StateFlow<Boolean?>
    /** Determines if user is already unlocked */
    val keyguardAuthenticated: StateFlow<Boolean?>
    val showMessage: StateFlow<BouncerShowMessageModel?>
    val resourceUpdateRequests: StateFlow<Boolean>
    val bouncerPromptReason: Int
    val bouncerErrorMessage: CharSequence?
    val isAlternateBouncerVisible: StateFlow<Boolean>
    val isAlternateBouncerUIAvailable: StateFlow<Boolean>
    var lastAlternateBouncerVisibleTime: Long

    fun setPrimaryScrimmed(isScrimmed: Boolean)

    fun setPrimaryVisible(isVisible: Boolean)

    fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?)

    fun setPrimaryShowingSoon(showingSoon: Boolean)

    fun setPrimaryHide(hide: Boolean)

    fun setPrimaryStartingToHide(startingToHide: Boolean)

    fun setPrimaryStartDisappearAnimation(runnable: Runnable?)

    fun setPanelExpansion(panelExpansion: Float)

    fun setKeyguardPosition(keyguardPosition: Float)

    fun setResourceUpdateRequests(willUpdateResources: Boolean)

    fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?)

    fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?)

    fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean)

    fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean)

    fun setAlternateVisible(isVisible: Boolean)

    fun setAlternateBouncerUIAvailable(isAvailable: Boolean)
}

@SysUISingleton
class KeyguardBouncerRepository
class KeyguardBouncerRepositoryImpl
@Inject
constructor(
    private val viewMediatorCallback: ViewMediatorCallback,
    private val clock: SystemClock,
    @Application private val applicationScope: CoroutineScope,
    @BouncerLog private val buffer: TableLogBuffer,
) {
) : KeyguardBouncerRepository {
    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
    private val _primaryBouncerVisible = MutableStateFlow(false)
    val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
    override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
    private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
    val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
    override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
    val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
    override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
    private val _primaryBouncerHide = MutableStateFlow(false)
    val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
    override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
    val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
    override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
    val primaryBouncerStartingDisappearAnimation = _primaryBouncerDisappearAnimation.asStateFlow()
    override val primaryBouncerStartingDisappearAnimation =
        _primaryBouncerDisappearAnimation.asStateFlow()
    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
    private val _primaryBouncerScrimmed = MutableStateFlow(false)
    val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
    override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
    /**
     * Set how much of the notification panel is showing on the screen.
     * ```
@@ -74,23 +139,23 @@ constructor(
     * ```
     */
    private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
    val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
    override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
    private val _keyguardPosition = MutableStateFlow(0f)
    val keyguardPosition = _keyguardPosition.asStateFlow()
    override val keyguardPosition = _keyguardPosition.asStateFlow()
    private val _onScreenTurnedOff = MutableStateFlow(false)
    val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
    override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
    val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
    override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
    /** Determines if user is already unlocked */
    val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
    val showMessage = _showMessage.asStateFlow()
    override val showMessage = _showMessage.asStateFlow()
    private val _resourceUpdateRequests = MutableStateFlow(false)
    val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
    val bouncerPromptReason: Int
    override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
    override val bouncerPromptReason: Int
        get() = viewMediatorCallback.bouncerPromptReason
    val bouncerErrorMessage: CharSequence?
    override val bouncerErrorMessage: CharSequence?
        get() = viewMediatorCallback.consumeCustomMessage()

    init {
@@ -99,21 +164,21 @@ constructor(

    /** Values associated with the AlternateBouncer */
    private val _isAlternateBouncerVisible = MutableStateFlow(false)
    val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
    var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
    override val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
    override var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
    private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
    val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
    override val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
        _isAlternateBouncerUIAvailable.asStateFlow()

    fun setPrimaryScrimmed(isScrimmed: Boolean) {
    override fun setPrimaryScrimmed(isScrimmed: Boolean) {
        _primaryBouncerScrimmed.value = isScrimmed
    }

    fun setPrimaryVisible(isVisible: Boolean) {
    override fun setPrimaryVisible(isVisible: Boolean) {
        _primaryBouncerVisible.value = isVisible
    }

    fun setAlternateVisible(isVisible: Boolean) {
    override fun setAlternateVisible(isVisible: Boolean) {
        if (isVisible && !_isAlternateBouncerVisible.value) {
            lastAlternateBouncerVisibleTime = clock.uptimeMillis()
        } else if (!isVisible) {
@@ -122,55 +187,55 @@ constructor(
        _isAlternateBouncerVisible.value = isVisible
    }

    fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
    override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
        _isAlternateBouncerUIAvailable.value = isAvailable
    }

    fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
    override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
        _primaryBouncerShow.value = keyguardBouncerModel
    }

    fun setPrimaryShowingSoon(showingSoon: Boolean) {
    override fun setPrimaryShowingSoon(showingSoon: Boolean) {
        _primaryBouncerShowingSoon.value = showingSoon
    }

    fun setPrimaryHide(hide: Boolean) {
    override fun setPrimaryHide(hide: Boolean) {
        _primaryBouncerHide.value = hide
    }

    fun setPrimaryStartingToHide(startingToHide: Boolean) {
    override fun setPrimaryStartingToHide(startingToHide: Boolean) {
        _primaryBouncerStartingToHide.value = startingToHide
    }

    fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
    override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
        _primaryBouncerDisappearAnimation.value = runnable
    }

    fun setPanelExpansion(panelExpansion: Float) {
    override fun setPanelExpansion(panelExpansion: Float) {
        _panelExpansionAmount.value = panelExpansion
    }

    fun setKeyguardPosition(keyguardPosition: Float) {
    override fun setKeyguardPosition(keyguardPosition: Float) {
        _keyguardPosition.value = keyguardPosition
    }

    fun setResourceUpdateRequests(willUpdateResources: Boolean) {
    override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
        _resourceUpdateRequests.value = willUpdateResources
    }

    fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
    override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
        _showMessage.value = bouncerShowMessageModel
    }

    fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
        _keyguardAuthenticated.value = keyguardAuthenticated
    }

    fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
    override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
        _isBackButtonEnabled.value = isBackButtonEnabled
    }

    fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
    override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
        _onScreenTurnedOff.value = onScreenTurnedOff
    }

+3 −0
Original line number Diff line number Diff line
@@ -37,4 +37,7 @@ interface KeyguardRepositoryModule {
    fun deviceEntryFingerprintAuthRepository(
        impl: DeviceEntryFingerprintAuthRepositoryImpl
    ): DeviceEntryFingerprintAuthRepository

    @Binds
    fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
}
+2 −0
Original line number Diff line number Diff line
@@ -113,6 +113,8 @@ constructor(
                0f
            }
        }
    /** Allow for interaction when just about fully visible */
    val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }

    // TODO(b/243685699): Move isScrimmed logic to data layer.
    // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
Loading