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

Commit db4876e3 authored by Shan Huang's avatar Shan Huang
Browse files

Add tests for the new back affordance.

Bug: b/241117239
Test: atest BackPanelControllerTest
Change-Id: Ic708da7102d11f8f133bcba49c6624c9a132eab3
parent 1a83f7a6
Loading
Loading
Loading
Loading
+10 −7
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.ViewConfiguration
import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import androidx.core.os.postDelayed
import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation
@@ -52,24 +53,24 @@ private const val ENABLE_FAILSAFE = true
private const val PX_PER_SEC = 1000
private const val PX_PER_MS = 1

internal const val MIN_DURATION_ACTIVE_ANIMATION = 300L
private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
private const val MIN_DURATION_COMMITTED_ANIMATION = 200L
private const val MIN_DURATION_ACTIVE_ANIMATION = 300L
private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
private const val MIN_DURATION_CONSIDERED_AS_FLING = 100L

private const val FAILSAFE_DELAY_MS = 350L
private const val POP_ON_FLING_DELAY = 160L

private val VIBRATE_ACTIVATED_EFFECT =
internal val VIBRATE_ACTIVATED_EFFECT =
        VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)

private val VIBRATE_DEACTIVATED_EFFECT =
internal val VIBRATE_DEACTIVATED_EFFECT =
        VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)

private const val DEBUG = false

class BackPanelController private constructor(
class BackPanelController internal constructor(
        context: Context,
        private val windowManager: WindowManager,
        private val viewConfiguration: ViewConfiguration,
@@ -114,8 +115,10 @@ class BackPanelController private constructor(
        }
    }

    private var params: EdgePanelParams = EdgePanelParams(resources)
    private var currentState: GestureState = GestureState.GONE
    @VisibleForTesting
    internal var params: EdgePanelParams = EdgePanelParams(resources)
    @VisibleForTesting
    internal var currentState: GestureState = GestureState.GONE
    private var previousState: GestureState = GestureState.GONE

    // Screen attributes
@@ -159,7 +162,7 @@ class BackPanelController private constructor(

    private val failsafeRunnable = Runnable { onFailsafe() }

    private enum class GestureState {
    internal enum class GestureState {
        /* Arrow is off the screen and invisible */
        GONE,

+167 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.navigationbar.gestural

import android.os.Handler
import android.os.Looper
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.MotionEvent
import android.view.MotionEvent.ACTION_DOWN
import android.view.MotionEvent.ACTION_MOVE
import android.view.MotionEvent.ACTION_UP
import android.view.ViewConfiguration
import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.NavigationEdgeBackPlugin
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class BackPanelControllerTest : SysuiTestCase() {
    companion object {
        private const val START_X: Float = 0f
    }
    private lateinit var mBackPanelController: BackPanelController
    private lateinit var testableLooper: TestableLooper
    private var triggerThreshold: Float = 0.0f
    private val touchSlop = ViewConfiguration.get(context).scaledEdgeSlop
    @Mock private lateinit var vibratorHelper: VibratorHelper
    @Mock private lateinit var windowManager: WindowManager
    @Mock private lateinit var configurationController: ConfigurationController
    @Mock private lateinit var latencyTracker: LatencyTracker
    @Mock private lateinit var layoutParams: WindowManager.LayoutParams
    @Mock private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        mBackPanelController =
            BackPanelController(
                context,
                windowManager,
                ViewConfiguration.get(context),
                Handler.createAsync(Looper.myLooper()),
                vibratorHelper,
                configurationController,
                latencyTracker
            )
        mBackPanelController.setLayoutParams(layoutParams)
        mBackPanelController.setBackCallback(backCallback)
        mBackPanelController.setIsLeftPanel(true)
        testableLooper = TestableLooper.get(this)
        triggerThreshold = mBackPanelController.params.staticTriggerThreshold
    }

    @Test
    fun handlesActionDown() {
        startTouch()

        assertThat(mBackPanelController.currentState)
            .isEqualTo(BackPanelController.GestureState.GONE)
    }

    @Test
    fun staysHiddenBeforeSlopCrossed() {
        startTouch()
        // Move just enough to not cross the touch slop
        continueTouch(START_X + touchSlop - 1)

        assertThat(mBackPanelController.currentState)
            .isEqualTo(BackPanelController.GestureState.GONE)
    }

    @Test
    fun handlesDragSlopCrossed() {
        startTouch()
        continueTouch(START_X + touchSlop + 1)

        assertThat(mBackPanelController.currentState)
            .isEqualTo(BackPanelController.GestureState.ENTRY)
    }

    @Test
    fun handlesBackCommitted() {
        startTouch()
        // Move once to cross the touch slop
        continueTouch(START_X + touchSlop.toFloat() + 1)
        // Move again to cross the back trigger threshold
        continueTouch(START_X + touchSlop + triggerThreshold + 1)

        assertThat(mBackPanelController.currentState)
            .isEqualTo(BackPanelController.GestureState.ACTIVE)
        verify(backCallback).setTriggerBack(true)
        testableLooper.moveTimeForward(100)
        testableLooper.processAllMessages()
        verify(vibratorHelper).vibrate(VIBRATE_ACTIVATED_EFFECT)

        finishTouchActionUp(START_X + touchSlop + triggerThreshold + 1)
        assertThat(mBackPanelController.currentState)
            .isEqualTo(BackPanelController.GestureState.FLUNG)
        verify(backCallback).triggerBack()
    }

    @Test
    fun handlesBackCancelled() {
        startTouch()
        continueTouch(START_X + touchSlop.toFloat() + 1)
        continueTouch(
            START_X + touchSlop + triggerThreshold -
                mBackPanelController.params.deactivationSwipeTriggerThreshold
        )
        clearInvocations(backCallback)
        Thread.sleep(MIN_DURATION_ACTIVE_ANIMATION)
        // Move in the opposite direction to cross the deactivation threshold and cancel back
        continueTouch(START_X)

        assertThat(mBackPanelController.currentState)
            .isEqualTo(BackPanelController.GestureState.INACTIVE)
        verify(backCallback).setTriggerBack(false)
        verify(vibratorHelper).vibrate(VIBRATE_DEACTIVATED_EFFECT)

        finishTouchActionUp(START_X)
        verify(backCallback).cancelBack()
    }

    private fun startTouch() {
        mBackPanelController.onMotionEvent(createMotionEvent(ACTION_DOWN, START_X, 0f))
    }

    private fun continueTouch(x: Float) {
        mBackPanelController.onMotionEvent(createMotionEvent(ACTION_MOVE, x, 0f))
    }

    private fun finishTouchActionUp(x: Float) {
        mBackPanelController.onMotionEvent(createMotionEvent(ACTION_UP, x, 0f))
    }

    private fun createMotionEvent(action: Int, x: Float, y: Float): MotionEvent {
        return MotionEvent.obtain(0L, 0L, action, x, y, 0)
    }
}