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 Original line Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.keyguard;
import android.content.Context;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import android.widget.FrameLayout;


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


    protected ViewMediatorCallback mViewMediatorCallback;
    protected ViewMediatorCallback mViewMediatorCallback;

    private boolean mIsInteractable;


    public KeyguardHostView(Context context) {
    public KeyguardHostView(Context context) {
        this(context, null);
        this(context, null);
@@ -54,4 +55,24 @@ public class KeyguardHostView extends FrameLayout {
    public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
    public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
        mViewMediatorCallback = 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 Original line Diff line number Diff line
@@ -527,4 +527,9 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
            mKeyguardSecurityContainerController.updateKeyguardPosition(x);
            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 Original line Diff line number Diff line
@@ -41,31 +41,96 @@ import kotlinx.coroutines.flow.map
 *
 *
 * Make sure to add newly added flows to the logger.
 * 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
@SysUISingleton
class KeyguardBouncerRepository
class KeyguardBouncerRepositoryImpl
@Inject
@Inject
constructor(
constructor(
    private val viewMediatorCallback: ViewMediatorCallback,
    private val viewMediatorCallback: ViewMediatorCallback,
    private val clock: SystemClock,
    private val clock: SystemClock,
    @Application private val applicationScope: CoroutineScope,
    @Application private val applicationScope: CoroutineScope,
    @BouncerLog private val buffer: TableLogBuffer,
    @BouncerLog private val buffer: TableLogBuffer,
) {
) : KeyguardBouncerRepository {
    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
    private val _primaryBouncerVisible = MutableStateFlow(false)
    private val _primaryBouncerVisible = MutableStateFlow(false)
    val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
    override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
    private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
    private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
    val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
    override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
    val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
    override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
    private val _primaryBouncerHide = MutableStateFlow(false)
    private val _primaryBouncerHide = MutableStateFlow(false)
    val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
    override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
    val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
    override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
    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. */
    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
    private val _primaryBouncerScrimmed = MutableStateFlow(false)
    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.
     * Set how much of the notification panel is showing on the screen.
     * ```
     * ```
@@ -74,23 +139,23 @@ constructor(
     * ```
     * ```
     */
     */
    private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
    private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
    val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
    override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
    private val _keyguardPosition = MutableStateFlow(0f)
    private val _keyguardPosition = MutableStateFlow(0f)
    val keyguardPosition = _keyguardPosition.asStateFlow()
    override val keyguardPosition = _keyguardPosition.asStateFlow()
    private val _onScreenTurnedOff = MutableStateFlow(false)
    private val _onScreenTurnedOff = MutableStateFlow(false)
    val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
    override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
    val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
    override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
    /** Determines if user is already unlocked */
    /** Determines if user is already unlocked */
    val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
    val showMessage = _showMessage.asStateFlow()
    override val showMessage = _showMessage.asStateFlow()
    private val _resourceUpdateRequests = MutableStateFlow(false)
    private val _resourceUpdateRequests = MutableStateFlow(false)
    val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
    override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
    val bouncerPromptReason: Int
    override val bouncerPromptReason: Int
        get() = viewMediatorCallback.bouncerPromptReason
        get() = viewMediatorCallback.bouncerPromptReason
    val bouncerErrorMessage: CharSequence?
    override val bouncerErrorMessage: CharSequence?
        get() = viewMediatorCallback.consumeCustomMessage()
        get() = viewMediatorCallback.consumeCustomMessage()


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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

    @Binds
    fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
}
}
+2 −0
Original line number Original line Diff line number Diff line
@@ -113,6 +113,8 @@ constructor(
                0f
                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/243685699): Move isScrimmed logic to data layer.
    // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
    // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
Loading