1 /* 2 * Copyright (C) 2023 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 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 18 package com.android.quickstep.util 19 20 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN 21 import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW 22 import android.graphics.Bitmap 23 import android.graphics.drawable.Drawable 24 import android.view.ContextThemeWrapper 25 import android.view.SurfaceControl.Transaction 26 import android.view.View 27 import android.window.TransitionInfo 28 import androidx.test.ext.junit.runners.AndroidJUnit4 29 import com.android.launcher3.apppairs.AppPairIcon 30 import com.android.launcher3.model.data.ItemInfo 31 import com.android.launcher3.statehandlers.DepthController 32 import com.android.launcher3.statemanager.StateManager 33 import com.android.launcher3.taskbar.TaskbarActivityContext 34 import com.android.launcher3.util.SplitConfigurationOptions 35 import com.android.quickstep.views.GroupedTaskView 36 import com.android.quickstep.views.IconView 37 import com.android.quickstep.views.TaskContainer 38 import com.android.quickstep.views.TaskView 39 import com.android.systemui.shared.recents.model.Task 40 import org.junit.Assert.assertEquals 41 import org.junit.Before 42 import org.junit.Test 43 import org.junit.runner.RunWith 44 import org.mockito.ArgumentMatchers.eq 45 import org.mockito.kotlin.any 46 import org.mockito.kotlin.doNothing 47 import org.mockito.kotlin.doReturn 48 import org.mockito.kotlin.mock 49 import org.mockito.kotlin.spy 50 import org.mockito.kotlin.verify 51 import org.mockito.kotlin.whenever 52 53 @RunWith(AndroidJUnit4::class) 54 class SplitAnimationControllerTest { 55 56 private val taskId = 9 57 private val taskId2 = 10 58 59 private val mockSplitSelectStateController: SplitSelectStateController = mock() 60 // TaskView 61 private val mockTaskView: TaskView = mock() 62 private val mockSnapshotView: View = mock() 63 private val mockBitmap: Bitmap = mock() 64 private val mockIconView: IconView = mock() 65 private val mockTaskViewDrawable: Drawable = mock() 66 // GroupedTaskView 67 private val mockGroupedTaskView: GroupedTaskView = mock() 68 private val mockTask: Task = mock() 69 private val mockTaskKey: Task.TaskKey = mock() 70 private val mockTaskContainer: TaskContainer = mock() 71 // AppPairIcon 72 private val mockAppPairIcon: AppPairIcon = mock() 73 private val mockContextThemeWrapper: ContextThemeWrapper = mock() 74 private val mockTaskbarActivityContext: TaskbarActivityContext = mock() 75 76 // SplitSelectSource 77 private val splitSelectSource: SplitConfigurationOptions.SplitSelectSource = mock() 78 private val mockSplitSourceDrawable: Drawable = mock() 79 private val mockSplitSourceView: View = mock() 80 private val mockItemInfo: ItemInfo = mock() 81 82 private val stateManager: StateManager<*, *> = mock() 83 private val depthController: DepthController = mock() 84 private val transitionInfo: TransitionInfo = mock() 85 private val transaction: Transaction = mock() 86 87 private lateinit var splitAnimationController: SplitAnimationController 88 89 @Before setupnull90 fun setup() { 91 whenever(mockTaskContainer.snapshotView).thenReturn(mockSnapshotView) 92 whenever(mockTaskContainer.splitAnimationThumbnail).thenReturn(mockBitmap) 93 whenever(mockTaskContainer.iconView).thenReturn(mockIconView) 94 whenever(mockTaskContainer.task).thenReturn(mockTask) 95 whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable) 96 whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer }) 97 98 whenever(splitSelectSource.drawable).thenReturn(mockSplitSourceDrawable) 99 whenever(splitSelectSource.view).thenReturn(mockSplitSourceView) 100 whenever(splitSelectSource.itemInfo).thenReturn(mockItemInfo) 101 102 splitAnimationController = SplitAnimationController(mockSplitSelectStateController) 103 } 104 105 @Test getFirstAnimInitViews_nullTaskViewIcon_useSplitSourceIconnull106 fun getFirstAnimInitViews_nullTaskViewIcon_useSplitSourceIcon() { 107 // Hit fullscreen task dismissal state 108 whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(true) 109 whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(false) 110 111 // Missing taskView icon 112 whenever(mockIconView.drawable).thenReturn(null) 113 114 val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps = 115 splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource }) 116 117 assertEquals( 118 "Did not fallback to use splitSource icon drawable", 119 mockSplitSourceDrawable, 120 splitAnimInitProps.iconDrawable 121 ) 122 } 123 124 @Test getFirstAnimInitViews_validTaskViewIcon_useTaskViewIconnull125 fun getFirstAnimInitViews_validTaskViewIcon_useTaskViewIcon() { 126 // Hit fullscreen task dismissal state 127 whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(true) 128 whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(false) 129 130 val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps = 131 splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource }) 132 133 assertEquals( 134 "Did not use taskView icon drawable", 135 mockTaskViewDrawable, 136 splitAnimInitProps.iconDrawable 137 ) 138 } 139 140 @Test getFirstAnimInitViews_validTaskViewNullSplitSource_useTaskViewIconnull141 fun getFirstAnimInitViews_validTaskViewNullSplitSource_useTaskViewIcon() { 142 // Hit fullscreen task dismissal state 143 whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(true) 144 whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(false) 145 146 // Set split source to null 147 whenever(splitSelectSource.drawable).thenReturn(null) 148 149 val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps = 150 splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource }) 151 152 assertEquals( 153 "Did not use taskView icon drawable", 154 mockTaskViewDrawable, 155 splitAnimInitProps.iconDrawable 156 ) 157 } 158 159 @Test getFirstAnimInitViews_nullTaskViewValidSplitSource_noTaskDismissalnull160 fun getFirstAnimInitViews_nullTaskViewValidSplitSource_noTaskDismissal() { 161 // Hit initiating split from home 162 whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(false) 163 whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(false) 164 165 val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps = 166 splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource }) 167 168 assertEquals( 169 "Did not use splitSource icon drawable", 170 mockSplitSourceDrawable, 171 splitAnimInitProps.iconDrawable 172 ) 173 } 174 175 @Test getFirstAnimInitViews_nullTaskViewValidSplitSource_groupedTaskViewnull176 fun getFirstAnimInitViews_nullTaskViewValidSplitSource_groupedTaskView() { 177 // Hit groupedTaskView dismissal 178 whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(true) 179 whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(true) 180 181 // Remove icon view from GroupedTaskView 182 whenever(mockIconView.drawable).thenReturn(null) 183 184 whenever(mockTaskContainer.task).thenReturn(mockTask) 185 whenever(mockTaskContainer.iconView).thenReturn(mockIconView) 186 whenever(mockTask.getKey()).thenReturn(mockTaskKey) 187 whenever(mockTaskKey.getId()).thenReturn(taskId) 188 whenever(mockSplitSelectStateController.initialTaskId).thenReturn(taskId) 189 whenever(mockGroupedTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer }) 190 val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps = 191 splitAnimationController.getFirstAnimInitViews( 192 { mockGroupedTaskView }, 193 { splitSelectSource } 194 ) 195 196 assertEquals( 197 "Did not use splitSource icon drawable", 198 mockSplitSourceDrawable, 199 splitAnimInitProps.iconDrawable 200 ) 201 } 202 203 @Test playsAppropriateSplitLaunchAnimation_playsLegacyLaunchCorrectlynull204 fun playsAppropriateSplitLaunchAnimation_playsLegacyLaunchCorrectly() { 205 val spySplitAnimationController = spy(splitAnimationController) 206 doNothing() 207 .whenever(spySplitAnimationController) 208 .composeRecentsSplitLaunchAnimatorLegacy( 209 any(), 210 any(), 211 any(), 212 any(), 213 any(), 214 any(), 215 any(), 216 any(), 217 any() 218 ) 219 220 spySplitAnimationController.playSplitLaunchAnimation( 221 mockGroupedTaskView, 222 null /* launchingIconView */, 223 taskId, 224 taskId2, 225 arrayOf() /* apps */, 226 arrayOf() /* wallpapers */, 227 arrayOf() /* nonApps */, 228 stateManager, 229 depthController, 230 null /* info */, 231 null /* t */, 232 {} /* finishCallback */, 233 1f /* cornerRadius */ 234 ) 235 236 verify(spySplitAnimationController) 237 .composeRecentsSplitLaunchAnimatorLegacy( 238 any(), 239 any(), 240 any(), 241 any(), 242 any(), 243 any(), 244 any(), 245 any(), 246 any() 247 ) 248 } 249 250 @Test playsAppropriateSplitLaunchAnimation_playsRecentsLaunchCorrectlynull251 fun playsAppropriateSplitLaunchAnimation_playsRecentsLaunchCorrectly() { 252 val spySplitAnimationController = spy(splitAnimationController) 253 doNothing() 254 .whenever(spySplitAnimationController) 255 .composeRecentsSplitLaunchAnimator(any(), any(), any(), any(), any(), any()) 256 257 spySplitAnimationController.playSplitLaunchAnimation( 258 mockGroupedTaskView, 259 null /* launchingIconView */, 260 taskId, 261 taskId2, 262 null /* apps */, 263 null /* wallpapers */, 264 null /* nonApps */, 265 stateManager, 266 depthController, 267 transitionInfo, 268 transaction, 269 {} /* finishCallback */, 270 1f /* cornerRadius */ 271 ) 272 273 verify(spySplitAnimationController) 274 .composeRecentsSplitLaunchAnimator(any(), any(), any(), any(), any(), any()) 275 } 276 277 @Test playsAppropriateSplitLaunchAnimation_playsIconLaunchCorrectlynull278 fun playsAppropriateSplitLaunchAnimation_playsIconLaunchCorrectly() { 279 val spySplitAnimationController = spy(splitAnimationController) 280 whenever(mockAppPairIcon.context).thenReturn(mockContextThemeWrapper) 281 doNothing() 282 .whenever(spySplitAnimationController) 283 .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any()) 284 doReturn(-1).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any()) 285 286 spySplitAnimationController.playSplitLaunchAnimation( 287 null /* launchingTaskView */, 288 mockAppPairIcon, 289 taskId, 290 taskId2, 291 null /* apps */, 292 null /* wallpapers */, 293 null /* nonApps */, 294 stateManager, 295 depthController, 296 transitionInfo, 297 transaction, 298 {} /* finishCallback */, 299 1f /* cornerRadius */ 300 ) 301 302 verify(spySplitAnimationController) 303 .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any()) 304 } 305 306 @Test playsAppropriateSplitLaunchAnimation_playsIconFullscreenLaunchCorrectlynull307 fun playsAppropriateSplitLaunchAnimation_playsIconFullscreenLaunchCorrectly() { 308 val spySplitAnimationController = spy(splitAnimationController) 309 whenever(mockAppPairIcon.context).thenReturn(mockContextThemeWrapper) 310 doNothing() 311 .whenever(spySplitAnimationController) 312 .composeFullscreenIconSplitLaunchAnimator(any(), any(), any(), any(), any()) 313 doReturn(0).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any()) 314 315 spySplitAnimationController.playSplitLaunchAnimation( 316 null /* launchingTaskView */, 317 mockAppPairIcon, 318 taskId, 319 taskId2, 320 null /* apps */, 321 null /* wallpapers */, 322 null /* nonApps */, 323 stateManager, 324 depthController, 325 transitionInfo, 326 transaction, 327 {} /* finishCallback */, 328 1f /* cornerRadius */ 329 ) 330 331 verify(spySplitAnimationController) 332 .composeFullscreenIconSplitLaunchAnimator(any(), any(), any(), any(), eq(0)) 333 } 334 335 @Test playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarCMultiWindownull336 fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarCMultiWindow() { 337 val spySplitAnimationController = spy(splitAnimationController) 338 whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext) 339 doNothing() 340 .whenever(spySplitAnimationController) 341 .composeScaleUpLaunchAnimation(any(), any(), any(), any()) 342 doReturn(-1).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any()) 343 spySplitAnimationController.playSplitLaunchAnimation( 344 null /* launchingTaskView */, 345 mockAppPairIcon, 346 taskId, 347 taskId2, 348 null /* apps */, 349 null /* wallpapers */, 350 null /* nonApps */, 351 stateManager, 352 depthController, 353 transitionInfo, 354 transaction, 355 {} /* finishCallback */, 356 1f /* cornerRadius */ 357 ) 358 359 verify(spySplitAnimationController) 360 .composeScaleUpLaunchAnimation(any(), any(), any(), eq(WINDOWING_MODE_MULTI_WINDOW)) 361 } 362 363 @Test playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarFullscreennull364 fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarFullscreen() { 365 val spySplitAnimationController = spy(splitAnimationController) 366 whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext) 367 doNothing() 368 .whenever(spySplitAnimationController) 369 .composeScaleUpLaunchAnimation(any(), any(), any(), any()) 370 doReturn(0).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any()) 371 spySplitAnimationController.playSplitLaunchAnimation( 372 null /* launchingTaskView */, 373 mockAppPairIcon, 374 taskId, 375 taskId2, 376 null /* apps */, 377 null /* wallpapers */, 378 null /* nonApps */, 379 stateManager, 380 depthController, 381 transitionInfo, 382 transaction, 383 {} /* finishCallback */, 384 1f /* cornerRadius */ 385 ) 386 387 verify(spySplitAnimationController) 388 .composeScaleUpLaunchAnimation(any(), any(), any(), eq(WINDOWING_MODE_FULLSCREEN)) 389 } 390 391 @Test playsAppropriateSplitLaunchAnimation_playsFadeInLaunchCorrectlynull392 fun playsAppropriateSplitLaunchAnimation_playsFadeInLaunchCorrectly() { 393 val spySplitAnimationController = spy(splitAnimationController) 394 doNothing() 395 .whenever(spySplitAnimationController) 396 .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any(), any()) 397 398 spySplitAnimationController.playSplitLaunchAnimation( 399 null /* launchingTaskView */, 400 null /* launchingIconView */, 401 taskId, 402 taskId2, 403 null /* apps */, 404 null /* wallpapers */, 405 null /* nonApps */, 406 stateManager, 407 depthController, 408 transitionInfo, 409 transaction, 410 {} /* finishCallback */, 411 1f /* cornerRadius */ 412 ) 413 414 verify(spySplitAnimationController) 415 .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any(), any()) 416 } 417 } 418