1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.shared.animation
16 
17 import android.graphics.Point
18 import android.test.suitebuilder.annotation.SmallTest
19 import android.testing.AndroidTestingRunner
20 import android.view.Display
21 import android.view.Surface.ROTATION_0
22 import android.view.Surface.ROTATION_90
23 import android.view.View
24 import android.view.WindowManager
25 import com.android.systemui.SysuiTestCase
26 import com.android.systemui.util.mockito.any
27 import com.google.common.truth.Truth.assertThat
28 import org.junit.Before
29 import org.junit.Rule
30 import org.junit.Test
31 import org.junit.runner.RunWith
32 import org.mockito.Mock
33 import org.mockito.Mockito.doAnswer
34 import org.mockito.Mockito.mock
35 import org.mockito.Mockito.spy
36 import org.mockito.junit.MockitoJUnit
37 import org.mockito.Mockito.`when` as whenever
38 
39 @RunWith(AndroidTestingRunner::class)
40 @SmallTest
41 class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() {
42 
43     @Mock
44     private lateinit var windowManager: WindowManager
45 
46     @get:Rule
47     val mockito = MockitoJUnit.rule()
48 
49     private lateinit var animator: UnfoldMoveFromCenterAnimator
50 
51     @Before
52     fun before() {
53         animator = UnfoldMoveFromCenterAnimator(windowManager)
54     }
55 
56     @Test
57     fun testRegisterViewOnTheLeftOfVerticalFold_halfProgress_viewTranslatedToTheRight() {
58         givenScreen(width = 100, height = 100, rotation = ROTATION_0)
59         val view = createView(x = 20, width = 10, height = 10)
60         animator.registerViewForAnimation(view)
61         animator.onTransitionStarted()
62 
63         animator.onTransitionProgress(0.5f)
64 
65         // Positive translationX -> translated to the right
66         // 10x10 view center is 25px from the center,
67         // When progress is 0.5 it should be translated at:
68         // 25 * 0.08 * (1 - 0.5) = 1px
69         assertThat(view.translationX).isWithin(0.01f).of(1.0f)
70     }
71 
72     @Test
73     fun testRegisterViewOnTheLeftOfVerticalFold_zeroProgress_viewTranslatedToTheRight() {
74         givenScreen(width = 100, height = 100, rotation = ROTATION_0)
75         val view = createView(x = 20, width = 10, height = 10)
76         animator.registerViewForAnimation(view)
77         animator.onTransitionStarted()
78 
79         animator.onTransitionProgress(0f)
80 
81         // Positive translationX -> translated to the right
82         // 10x10 view center is 25px from the center,
83         // When progress is 0 it should be translated at:
84         // 25 * 0.08 * (1 - 0) = 7.5px
85         assertThat(view.translationX).isWithin(0.01f).of(2f)
86     }
87 
88     @Test
89     fun testRegisterViewOnTheLeftOfVerticalFold_fullProgress_viewTranslatedToTheOriginalPosition() {
90         givenScreen(width = 100, height = 100, rotation = ROTATION_0)
91         val view = createView(x = 20, width = 10, height = 10)
92         animator.registerViewForAnimation(view)
93         animator.onTransitionStarted()
94 
95         animator.onTransitionProgress(1f)
96 
97         // Positive translationX -> translated to the right
98         // 10x10 view center is 25px from the center,
99         // When progress is 1 it should be translated at:
100         // 25 * 0.08 * 0 = 0px
101         assertThat(view.translationX).isEqualTo(0f)
102     }
103 
104     @Test
105     fun testViewOnTheLeftOfVerticalFoldWithTranslation_halfProgress_viewTranslatedToTheRight() {
106         givenScreen(width = 100, height = 100, rotation = ROTATION_0)
107         val view = createView(x = 20, width = 10, height = 10, translationX = 100f)
108         animator.registerViewForAnimation(view)
109         animator.onTransitionStarted()
110 
111         animator.onTransitionProgress(0.5f)
112 
113         // Positive translationX -> translated to the right, original translation is ignored
114         // 10x10 view center is 25px from the center,
115         // When progress is 0.5 it should be translated at:
116         // 25 * 0.08 * (1 - 0.5) = 1px
117         assertThat(view.translationX).isWithin(0.01f).of(1.0f)
118     }
119 
120     @Test
121     fun testRegisterViewAndUnregister_halfProgress_viewIsNotUpdated() {
122         givenScreen(width = 100, height = 100, rotation = ROTATION_0)
123         val view = createView(x = 20, width = 10, height = 10)
124         animator.registerViewForAnimation(view)
125         animator.onTransitionStarted()
126         animator.clearRegisteredViews()
127 
128         animator.onTransitionProgress(0.5f)
129 
130         assertThat(view.translationX).isEqualTo(0f)
131     }
132 
133     @Test
134     fun testRegisterViewUpdateProgressAndUnregister_halfProgress_viewIsNotUpdated() {
135         givenScreen(width = 100, height = 100, rotation = ROTATION_0)
136         val view = createView(x = 20, width = 10, height = 10)
137         animator.registerViewForAnimation(view)
138         animator.onTransitionStarted()
139         animator.onTransitionProgress(0.2f)
140         animator.clearRegisteredViews()
141 
142         animator.onTransitionProgress(0.5f)
143 
144         assertThat(view.translationX).isEqualTo(0f)
145     }
146 
147     @Test
148     fun testRegisterViewOnTheTopOfHorizontalFold_halfProgress_viewTranslatedToTheBottom() {
149         givenScreen(width = 100, height = 100, rotation = ROTATION_90)
150         val view = createView(y = 20, width = 10, height = 10)
151         animator.registerViewForAnimation(view)
152         animator.onTransitionStarted()
153 
154         animator.onTransitionProgress(0.5f)
155 
156         // Positive translationY -> translated to the bottom
157         assertThat(view.translationY).isWithin(0.01f).of(1f)
158     }
159 
160     @Test
161     fun testUpdateViewPositions_viewOnTheLeftAndMovedToTheRight_viewTranslatedToTheLeft() {
162         givenScreen(width = 100, height = 100, rotation = ROTATION_0)
163         val view = createView(x = 20)
164         animator.registerViewForAnimation(view)
165         animator.onTransitionStarted()
166         animator.onTransitionProgress(0.5f)
167         view.updateMock(x = 80) // view moved from the left side to the right
168 
169         animator.updateViewPositions()
170 
171         // Negative translationX -> translated to the left
172         assertThat(view.translationX).isWithin(0.1f).of(-1.4f)
173     }
174 
175     private fun createView(
176         x: Int = 0,
177         y: Int = 0,
178         width: Int = 10,
179         height: Int = 10,
180         translationX: Float = 0f,
181         translationY: Float = 0f
182     ): View {
183         val view = spy(View(context))
184         doAnswer {
185             val location = (it.arguments[0] as IntArray)
186             location[0] = x
187             location[1] = y
188             Unit
189         }.`when`(view).getLocationOnScreen(any())
190 
191         whenever(view.width).thenReturn(width)
192         whenever(view.height).thenReturn(height)
193 
194         view.updateMock(x, y, width, height, translationX, translationY)
195 
196         return view
197     }
198 
199     private fun View.updateMock(
200         x: Int = 0,
201         y: Int = 0,
202         width: Int = 10,
203         height: Int = 10,
204         translationX: Float = 0f,
205         translationY: Float = 0f
206     ) {
207         doAnswer {
208             val location = (it.arguments[0] as IntArray)
209             location[0] = x
210             location[1] = y
211             Unit
212         }.`when`(this).getLocationOnScreen(any())
213 
214         whenever(this.width).thenReturn(width)
215         whenever(this.height).thenReturn(height)
216 
217         this.apply {
218             setTranslationX(translationX)
219             setTranslationY(translationY)
220         }
221     }
222 
223     private fun givenScreen(
224         width: Int = 100,
225         height: Int = 100,
226         rotation: Int = ROTATION_0
227     ) {
228         val display = mock(Display::class.java)
229         whenever(display.getSize(any())).thenAnswer {
230             val size = (it.arguments[0] as Point)
231             size.set(width, height)
232             Unit
233         }
234         whenever(display.rotation).thenReturn(rotation)
235         whenever(windowManager.defaultDisplay).thenReturn(display)
236 
237         animator.updateDisplayProperties()
238     }
239 }
240