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

Commit 1553a754 authored by Chandru S's avatar Chandru S Committed by Android (Google) Code Review
Browse files

Merge "Fix pattern snapping to incorrect dots." into main

parents a9e0b680 858078c3
Loading
Loading
Loading
Loading
+34 −1
Original line number Diff line number Diff line
@@ -129,7 +129,12 @@ class PatternBouncerViewModel(
                    buildList {
                        var dot = previousDot
                        while (dot != hitDot) {
                            // Move along the direction of the line connecting the previously
                            // selected dot and current hit dot, and see if they were skipped over
                            // but fall on that line.
                            if (dot.isOnLineSegment(previousDot, hitDot)) {
                                add(dot)
                            }
                            dot =
                                PatternDotViewModel(
                                    x =
@@ -208,6 +213,34 @@ class PatternBouncerViewModel(
    }
}

/**
 * Determines whether [this] dot is present on the line segment connecting [first] and [second]
 * dots.
 */
private fun PatternDotViewModel.isOnLineSegment(
    first: PatternDotViewModel,
    second: PatternDotViewModel
): Boolean {
    val anotherPoint = this
    // No need to consider any points outside the bounds of two end points
    val isWithinBounds =
        anotherPoint.x.isBetween(first.x, second.x) && anotherPoint.y.isBetween(first.y, second.y)
    if (!isWithinBounds) {
        return false
    }

    // Uses the 2 point line equation: (y-y1)/(x-x1) = (y2-y1)/(x2-x1)
    // which can be rewritten as:      (y-y1)*(x2-x1) = (x-x1)*(y2-y1)
    // This is true for any point on the line passing through these two points
    return (anotherPoint.y - first.y) * (second.x - first.x) ==
        (anotherPoint.x - first.x) * (second.y - first.y)
}

/** Is [this] Int between [a] and [b] */
private fun Int.isBetween(a: Int, b: Int): Boolean {
    return (this in a..b) || (this in b..a)
}

data class PatternDotViewModel(
    val x: Int,
    val y: Int,
+178 −65
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
@@ -30,6 +31,7 @@ import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -67,6 +69,9 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
            isInputEnabled = MutableStateFlow(true).asStateFlow(),
        )

    private val containerSize = 90 // px
    private val dotSize = 30 // px

    @Before
    fun setUp() {
        overrideResource(R.string.keyguard_enter_your_pattern, ENTER_YOUR_PATTERN)
@@ -80,13 +85,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
            val message by collectLastValue(bouncerViewModel.message)
            val selectedDots by collectLastValue(underTest.selectedDots)
            val currentDot by collectLastValue(underTest.currentDot)
            utils.authenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.Pattern
            )
            utils.authenticationRepository.setUnlocked(false)
            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
            transitionToPatternBouncer()

            underTest.onShown()

@@ -103,13 +102,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
            val message by collectLastValue(bouncerViewModel.message)
            val selectedDots by collectLastValue(underTest.selectedDots)
            val currentDot by collectLastValue(underTest.currentDot)
            utils.authenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.Pattern
            )
            utils.authenticationRepository.setUnlocked(false)
            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
            transitionToPatternBouncer()
            underTest.onShown()
            runCurrent()

@@ -127,23 +120,12 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
            val currentScene by collectLastValue(sceneInteractor.desiredScene)
            val selectedDots by collectLastValue(underTest.selectedDots)
            val currentDot by collectLastValue(underTest.currentDot)
            utils.authenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.Pattern
            )
            utils.authenticationRepository.setUnlocked(false)
            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
            transitionToPatternBouncer()
            underTest.onShown()
            underTest.onDragStart()
            assertThat(currentDot).isNull()
            CORRECT_PATTERN.forEachIndexed { index, coordinate ->
                underTest.onDrag(
                    xPx = 30f * coordinate.x + 15,
                    yPx = 30f * coordinate.y + 15,
                    containerSizePx = 90,
                    verticalOffsetPx = 0f,
                )
                dragToCoordinate(coordinate)
                assertWithMessage("Wrong selected dots for index $index")
                    .that(selectedDots)
                    .isEqualTo(
@@ -176,23 +158,10 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
            val message by collectLastValue(bouncerViewModel.message)
            val selectedDots by collectLastValue(underTest.selectedDots)
            val currentDot by collectLastValue(underTest.currentDot)
            utils.authenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.Pattern
            )
            utils.authenticationRepository.setUnlocked(false)
            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
            transitionToPatternBouncer()
            underTest.onShown()
            underTest.onDragStart()
            CORRECT_PATTERN.subList(0, 3).forEach { coordinate ->
                underTest.onDrag(
                    xPx = 30f * coordinate.x + 15,
                    yPx = 30f * coordinate.y + 15,
                    containerSizePx = 90,
                    verticalOffsetPx = 0f,
                )
            }
            CORRECT_PATTERN.subList(0, 3).forEach { coordinate -> dragToCoordinate(coordinate) }

            underTest.onDragEnd()

@@ -202,6 +171,147 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
        }

    @Test
    fun onDrag_shouldIncludeDotsThatWereSkippedOverAlongTheSameRow() =
        testScope.runTest {
            val selectedDots by collectLastValue(underTest.selectedDots)
            transitionToPatternBouncer()
            underTest.onShown()

            /*
             * Pattern setup, coordinates are (column, row)
             *   0  1  2
             * 0 x  x  x
             * 1 x  x  x
             * 2 x  x  x
             */
            // Select (0,0), Skip over (1,0) and select (2,0)
            dragOverCoordinates(Point(0, 0), Point(2, 0))

            assertThat(selectedDots)
                .isEqualTo(
                    listOf(
                        PatternDotViewModel(0, 0),
                        PatternDotViewModel(1, 0),
                        PatternDotViewModel(2, 0)
                    )
                )
        }

    @Test
    fun onDrag_shouldIncludeDotsThatWereSkippedOverAlongTheSameColumn() =
        testScope.runTest {
            val selectedDots by collectLastValue(underTest.selectedDots)
            transitionToPatternBouncer()
            underTest.onShown()

            /*
             * Pattern setup, coordinates are (column, row)
             *   0  1  2
             * 0 x  x  x
             * 1 x  x  x
             * 2 x  x  x
             */
            // Select (1,0), Skip over (1,1) and select (1, 2)
            dragOverCoordinates(Point(1, 0), Point(1, 2))

            assertThat(selectedDots)
                .isEqualTo(
                    listOf(
                        PatternDotViewModel(1, 0),
                        PatternDotViewModel(1, 1),
                        PatternDotViewModel(1, 2)
                    )
                )
        }

    @Test
    fun onDrag_shouldIncludeDotsThatWereSkippedOverAlongTheDiagonal() =
        testScope.runTest {
            val selectedDots by collectLastValue(underTest.selectedDots)
            transitionToPatternBouncer()
            underTest.onShown()

            /*
             * Pattern setup
             *   0  1  2
             * 0 x  x  x
             * 1 x  x  x
             * 2 x  x  x
             *
             * Coordinates are (column, row)
             * Select (2,0), Skip over (1,1) and select (0, 2)
             */
            dragOverCoordinates(Point(2, 0), Point(0, 2))

            assertThat(selectedDots)
                .isEqualTo(
                    listOf(
                        PatternDotViewModel(2, 0),
                        PatternDotViewModel(1, 1),
                        PatternDotViewModel(0, 2)
                    )
                )
        }

    @Test
    fun onDrag_shouldNotIncludeDotIfItIsNotOnTheLine() =
        testScope.runTest {
            val selectedDots by collectLastValue(underTest.selectedDots)
            transitionToPatternBouncer()
            underTest.onShown()

            /*
             * Pattern setup
             *   0  1  2
             * 0 x  x  x
             * 1 x  x  x
             * 2 x  x  x
             *
             * Coordinates are (column, row)
             */
            dragOverCoordinates(Point(0, 0), Point(1, 0), Point(2, 0), Point(0, 1))

            assertThat(selectedDots)
                .isEqualTo(
                    listOf(
                        PatternDotViewModel(0, 0),
                        PatternDotViewModel(1, 0),
                        PatternDotViewModel(2, 0),
                        PatternDotViewModel(0, 1),
                    )
                )
        }

    @Test
    fun onDrag_shouldNotIncludeSkippedOverDotsIfTheyAreAlreadySelected() =
        testScope.runTest {
            val selectedDots by collectLastValue(underTest.selectedDots)
            transitionToPatternBouncer()
            underTest.onShown()

            /*
             * Pattern setup
             *   0  1  2
             * 0 x  x  x
             * 1 x  x  x
             * 2 x  x  x
             *
             * Coordinates are (column, row)
             */
            dragOverCoordinates(Point(1, 0), Point(1, 1), Point(0, 0), Point(2, 0))

            assertThat(selectedDots)
                .isEqualTo(
                    listOf(
                        PatternDotViewModel(1, 0),
                        PatternDotViewModel(1, 1),
                        PatternDotViewModel(0, 0),
                        PatternDotViewModel(2, 0),
                    )
                )
        }

    @Test
    fun onDragEnd_whenPatternTooShort() =
        testScope.runTest {
@@ -252,23 +362,10 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
            val message by collectLastValue(bouncerViewModel.message)
            val selectedDots by collectLastValue(underTest.selectedDots)
            val currentDot by collectLastValue(underTest.currentDot)
            utils.authenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.Pattern
            )
            utils.authenticationRepository.setUnlocked(false)
            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
            transitionToPatternBouncer()
            underTest.onShown()
            underTest.onDragStart()
            CORRECT_PATTERN.subList(2, 7).forEach { coordinate ->
                underTest.onDrag(
                    xPx = 30f * coordinate.x + 15,
                    yPx = 30f * coordinate.y + 15,
                    containerSizePx = 90,
                    verticalOffsetPx = 0f,
                )
            }
            CORRECT_PATTERN.subList(2, 7).forEach { coordinate -> dragToCoordinate(coordinate) }
            underTest.onDragEnd()
            assertThat(selectedDots).isEmpty()
            assertThat(currentDot).isNull()
@@ -276,18 +373,34 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))

            // Enter the correct pattern:
            CORRECT_PATTERN.forEach { coordinate ->
            CORRECT_PATTERN.forEach { coordinate -> dragToCoordinate(coordinate) }

            underTest.onDragEnd()

            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
        }

    private fun dragOverCoordinates(vararg coordinatesDragged: Point) {
        underTest.onDragStart()
        coordinatesDragged.forEach { dragToCoordinate(it) }
    }

    private fun dragToCoordinate(coordinate: Point) {
        underTest.onDrag(
                    xPx = 30f * coordinate.x + 15,
                    yPx = 30f * coordinate.y + 15,
                    containerSizePx = 90,
            xPx = dotSize * coordinate.x + 15f,
            yPx = dotSize * coordinate.y + 15f,
            containerSizePx = containerSize,
            verticalOffsetPx = 0f,
        )
    }

            underTest.onDragEnd()

            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
    private fun TestScope.transitionToPatternBouncer() {
        utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern)
        utils.authenticationRepository.setUnlocked(false)
        sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
        sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
        assertThat(collectLastValue(sceneInteractor.desiredScene).invoke())
            .isEqualTo(SceneModel(SceneKey.Bouncer))
    }

    companion object {