From db4876e390eaca4487a2a738bbf23c69e0397cb9 Mon Sep 17 00:00:00 2001 From: Shan Huang Date: Fri, 3 Feb 2023 02:12:04 +0000 Subject: [PATCH] Add tests for the new back affordance. Bug: b/241117239 Test: atest BackPanelControllerTest Change-Id: Ic708da7102d11f8f133bcba49c6624c9a132eab3 --- .../gestural/BackPanelController.kt | 17 +- .../gestural/BackPanelControllerTest.kt | 167 ++++++++++++++++++ 2 files changed, 177 insertions(+), 7 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index ca1a49a6751ec..367d12547b9f9 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -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, diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt new file mode 100644 index 0000000000000..f42bfb8e0dfe5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt @@ -0,0 +1,167 @@ +/* + * 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) + } +}