1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software distributed under the 11 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 12 * KIND, either express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 package com.android.systemui.shared.animation 16 17 import android.testing.AndroidTestingRunner 18 import android.view.View 19 import android.view.ViewGroup 20 import androidx.test.filters.SmallTest 21 import com.android.systemui.SysuiTestCase 22 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction 23 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate 24 import com.android.systemui.unfold.TestUnfoldTransitionProvider 25 import org.junit.Assert.assertEquals 26 import org.junit.Before 27 import org.junit.Test 28 import org.junit.runner.RunWith 29 import org.mockito.Mock 30 import org.mockito.Mockito.`when` as whenever 31 import org.mockito.MockitoAnnotations 32 33 @SmallTest 34 @RunWith(AndroidTestingRunner::class) 35 class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() { 36 37 private val progressProvider = TestUnfoldTransitionProvider() 38 39 @Mock 40 private lateinit var parent: ViewGroup 41 42 @Mock 43 private lateinit var shouldBeAnimated: () -> Boolean 44 45 private lateinit var animator: UnfoldConstantTranslateAnimator 46 47 private val viewsIdToRegister 48 get() = 49 setOf( 50 ViewIdToTranslate(START_VIEW_ID, Direction.START, shouldBeAnimated), 51 ViewIdToTranslate(END_VIEW_ID, Direction.END, shouldBeAnimated) 52 ) 53 54 @Before 55 fun setup() { 56 MockitoAnnotations.initMocks(this) 57 whenever(shouldBeAnimated.invoke()).thenReturn(true) 58 animator = UnfoldConstantTranslateAnimator(viewsIdToRegister, progressProvider) 59 60 animator.init(parent, MAX_TRANSLATION) 61 } 62 63 @Test 64 fun onTransition_noMatchingIds() { 65 // GIVEN no views matching any ids 66 // WHEN the transition starts 67 progressProvider.onTransitionStarted() 68 progressProvider.onTransitionProgress(.1f) 69 70 // THEN nothing... no exceptions 71 } 72 73 @Test 74 fun onTransition_oneMovesStartWithLTR() { 75 // GIVEN one view with a matching id 76 val view = View(context) 77 whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(view) 78 79 moveAndValidate(listOf(view to START), View.LAYOUT_DIRECTION_LTR) 80 } 81 82 @Test 83 fun onTransition_oneMovesStartWithRTL() { 84 // GIVEN one view with a matching id 85 val view = View(context) 86 whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(view) 87 88 whenever(parent.getLayoutDirection()).thenReturn(View.LAYOUT_DIRECTION_RTL) 89 moveAndValidate(listOf(view to START), View.LAYOUT_DIRECTION_RTL) 90 } 91 92 @Test 93 fun onTransition_oneMovesStartAndOneMovesEndMultipleTimes() { 94 // GIVEN two views with a matching id 95 val leftView = View(context) 96 val rightView = View(context) 97 whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(leftView) 98 whenever(parent.findViewById<View>(END_VIEW_ID)).thenReturn(rightView) 99 100 moveAndValidate(listOf(leftView to START, rightView to END), View.LAYOUT_DIRECTION_LTR) 101 moveAndValidate(listOf(leftView to START, rightView to END), View.LAYOUT_DIRECTION_LTR) 102 } 103 104 @Test 105 fun onTransition_completeStartedTranslation() { 106 // GIVEN 107 val leftView = View(context) 108 whenever(parent.findViewById<View>(START_VIEW_ID)).thenReturn(leftView) 109 // To start animation, shouldBeAnimated should return true. 110 // There is a possibility for shouldBeAnimated to return false during the animation. 111 whenever(shouldBeAnimated.invoke()).thenReturn(true).thenReturn(false) 112 113 // shouldBeAnimated state may change during the animation. 114 // However, started animation should be completed. 115 moveAndValidate(listOf(leftView to START), View.LAYOUT_DIRECTION_LTR) 116 } 117 118 private fun moveAndValidate(list: List<Pair<View, Int>>, layoutDirection: Int) { 119 // Compare values as ints because -0f != 0f 120 121 // WHEN the transition starts 122 progressProvider.onTransitionStarted() 123 progressProvider.onTransitionProgress(0f) 124 125 val rtlMultiplier = if (layoutDirection == View.LAYOUT_DIRECTION_LTR) { 126 1 127 } else { 128 -1 129 } 130 list.forEach { (view, direction) -> 131 assertEquals( 132 (-MAX_TRANSLATION * direction * rtlMultiplier).toInt(), 133 view.translationX.toInt() 134 ) 135 } 136 137 // WHEN the transition progresses, translation is updated 138 progressProvider.onTransitionProgress(.5f) 139 list.forEach { (view, direction) -> 140 assertEquals( 141 (-MAX_TRANSLATION / 2f * direction * rtlMultiplier).toInt(), 142 view.translationX.toInt() 143 ) 144 } 145 146 // WHEN the transition ends, translation is completed 147 progressProvider.onTransitionProgress(1f) 148 progressProvider.onTransitionFinished() 149 list.forEach { (view, _) -> assertEquals(0, view.translationX.toInt()) } 150 } 151 152 companion object { 153 private val START = Direction.START.multiplier.toInt() 154 private val END = Direction.END.multiplier.toInt() 155 156 private const val MAX_TRANSLATION = 42f 157 158 private const val START_VIEW_ID = 1 159 private const val END_VIEW_ID = 2 160 } 161 } 162