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