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