• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 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 package com.android.launcher3.uioverrides
17 
18 import com.android.app.animation.Interpolators.ACCELERATE_DECELERATE
19 import com.android.app.animation.Interpolators.AGGRESSIVE_EASE_IN_OUT
20 import com.android.app.animation.Interpolators.FINAL_FRAME
21 import com.android.app.animation.Interpolators.INSTANT
22 import com.android.app.animation.Interpolators.LINEAR
23 import com.android.launcher3.Flags.enableDesktopExplodedView
24 import com.android.launcher3.Flags.enableGridOnlyOverview
25 import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
26 import com.android.launcher3.LauncherState
27 import com.android.launcher3.anim.AnimatedFloat
28 import com.android.launcher3.anim.AnimatorListeners.forSuccessCallback
29 import com.android.launcher3.anim.PendingAnimation
30 import com.android.launcher3.anim.PropertySetter
31 import com.android.launcher3.statemanager.StateManager.StateHandler
32 import com.android.launcher3.states.StateAnimationConfig
33 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE
34 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE
35 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL
36 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE
37 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X
38 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y
39 import com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW
40 import com.android.quickstep.util.AnimUtils
41 import com.android.quickstep.views.AddDesktopButton
42 import com.android.quickstep.views.ClearAllButton
43 import com.android.quickstep.views.RecentsView
44 import com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET
45 import com.android.quickstep.views.RecentsView.CONTENT_ALPHA
46 import com.android.quickstep.views.RecentsView.DESKTOP_CAROUSEL_DETACH_PROGRESS
47 import com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS
48 import com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS
49 import com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY
50 import com.android.quickstep.views.RecentsView.TASK_MODALNESS
51 import com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLATION
52 import com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION
53 import com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION
54 import com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA
55 import com.android.quickstep.views.RecentsViewUtils.Companion.DESK_EXPLODE_PROGRESS
56 import com.android.quickstep.views.TaskView.Companion.FLAG_UPDATE_ALL
57 
58 /**
59  * State handler for handling UI changes for [com.android.quickstep.views.LauncherRecentsView]. In
60  * addition to managing the basic view properties, this class also manages changes in the task
61  * visuals.
62  */
63 class RecentsViewStateController(private val launcher: QuickstepLauncher) :
64     StateHandler<LauncherState> {
65     private val recentsView: RecentsView<*, *> = launcher.getOverviewPanel()
66 
67     override fun setState(state: LauncherState) {
68         val scaleAndOffset = state.getOverviewScaleAndOffset(launcher)
69         RECENTS_SCALE_PROPERTY.set(recentsView, scaleAndOffset[0])
70         ADJACENT_PAGE_HORIZONTAL_OFFSET.set(recentsView, scaleAndOffset[1])
71         TASK_SECONDARY_TRANSLATION.set(recentsView, 0f)
72 
73         CONTENT_ALPHA.set(recentsView, if (state.isRecentsViewVisible) 1f else 0f)
74         TASK_MODALNESS.set(recentsView, state.overviewModalness)
75         RECENTS_GRID_PROGRESS.set(
76             recentsView,
77             if (state.displayOverviewTasksAsGrid(launcher.deviceProfile)) 1f else 0f,
78         )
79         if (enableDesktopExplodedView()) {
80             DESK_EXPLODE_PROGRESS.set(recentsView, if (state.showExplodedDesktopView()) 1f else 0f)
81         }
82 
83         TASK_THUMBNAIL_SPLASH_ALPHA.set(
84             recentsView,
85             if (state.showTaskThumbnailSplash()) 1f else 0f,
86         )
87         if (enableLargeDesktopWindowingTile()) {
88             DESKTOP_CAROUSEL_DETACH_PROGRESS.set(
89                 recentsView,
90                 if (state.detachDesktopCarousel()) 1f else 0f,
91             )
92         }
93 
94         if (state.isRecentsViewVisible) {
95             recentsView.updateEmptyMessage()
96         } else {
97             recentsView.resetTaskVisuals()
98         }
99         setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, StateAnimationConfig(), state)
100         recentsView.setFullscreenProgress(state.overviewFullscreenProgress)
101         // In Overview, we may be layering app surfaces behind Launcher, so we need to notify
102         // DepthController to prevent optimizations which might occlude the layers behind
103         launcher.depthController.setHasContentBehindLauncher(state.isRecentsViewVisible)
104 
105         val builder = PendingAnimation(state.getTransitionDuration(launcher, true).toLong())
106         handleSplitSelectionState(state, builder, animate = false)
107     }
108 
109     override fun setStateWithAnimation(
110         toState: LauncherState,
111         config: StateAnimationConfig,
112         builder: PendingAnimation,
113     ) {
114         if (config.hasAnimationFlag(SKIP_OVERVIEW)) return
115 
116         val scaleAndOffset = toState.getOverviewScaleAndOffset(launcher)
117         builder.setFloat(
118             recentsView,
119             RECENTS_SCALE_PROPERTY,
120             scaleAndOffset[0],
121             config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR),
122         )
123         builder.setFloat(
124             recentsView,
125             ADJACENT_PAGE_HORIZONTAL_OFFSET,
126             scaleAndOffset[1],
127             config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR),
128         )
129         builder.setFloat(
130             recentsView,
131             TASK_SECONDARY_TRANSLATION,
132             0f,
133             config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR),
134         )
135 
136         builder.setFloat(
137             recentsView,
138             CONTENT_ALPHA,
139             if (toState.isRecentsViewVisible) 1f else 0f,
140             config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT),
141         )
142 
143         builder.setFloat(
144             recentsView,
145             TASK_MODALNESS,
146             toState.overviewModalness,
147             config.getInterpolator(
148                 ANIM_OVERVIEW_MODAL,
149                 if (enableGridOnlyOverview() && !toState.isRecentsViewVisible) FINAL_FRAME
150                 else LINEAR,
151             ),
152         )
153 
154         val fromState = launcher.stateManager.state
155         builder.setFloat(
156             recentsView,
157             TASK_THUMBNAIL_SPLASH_ALPHA,
158             if (toState.showTaskThumbnailSplash()) 1f else 0f,
159             getOverviewInterpolator(fromState, toState),
160         )
161 
162         builder.setFloat(
163             recentsView,
164             RECENTS_GRID_PROGRESS,
165             if (toState.displayOverviewTasksAsGrid(launcher.deviceProfile)) 1f else 0f,
166             getOverviewInterpolator(fromState, toState),
167         )
168 
169         if (enableDesktopExplodedView()) {
170             builder.setFloat(
171                 recentsView,
172                 DESK_EXPLODE_PROGRESS,
173                 if (toState.showExplodedDesktopView()) 1f else 0f,
174                 getOverviewInterpolator(fromState, toState),
175             )
176         }
177 
178         if (enableLargeDesktopWindowingTile()) {
179             builder.setFloat(
180                 recentsView,
181                 DESKTOP_CAROUSEL_DETACH_PROGRESS,
182                 if (toState.detachDesktopCarousel()) 1f else 0f,
183                 getOverviewInterpolator(fromState, toState),
184             )
185         }
186 
187         if (toState.isRecentsViewVisible) {
188             // While animating into recents, update the visible task data as needed
189             builder.addOnFrameCallback { recentsView.loadVisibleTaskData(FLAG_UPDATE_ALL) }
190             recentsView.updateEmptyMessage()
191         } else {
192             builder.addListener(forSuccessCallback { recentsView.resetTaskVisuals() })
193         }
194         // In Overview, we may be layering app surfaces behind Launcher, so we need to notify
195         // DepthController to prevent optimizations which might occlude the layers behind
196         builder.addListener(
197             forSuccessCallback {
198                 launcher.depthController.setHasContentBehindLauncher(toState.isRecentsViewVisible)
199             }
200         )
201 
202         handleSplitSelectionState(toState, builder, animate = true)
203 
204         setAlphas(builder, config, toState)
205         builder.setFloat(
206             recentsView,
207             FULLSCREEN_PROGRESS,
208             toState.overviewFullscreenProgress,
209             LINEAR,
210         )
211 
212         builder.addEndListener { success: Boolean ->
213             if (!success && !toState.isRecentsViewVisible) {
214                 recentsView.reset()
215             }
216         }
217     }
218 
219     /**
220      * Create or dismiss split screen select animations.
221      *
222      * @param builder if null then this will run the split select animations right away, otherwise
223      *   will add animations to builder.
224      */
225     private fun handleSplitSelectionState(
226         toState: LauncherState,
227         builder: PendingAnimation,
228         animate: Boolean,
229     ) {
230         val goingToOverviewFromWorkspaceContextual =
231             toState == LauncherState.OVERVIEW && launcher.isSplitSelectionActive
232         if (
233             toState != LauncherState.OVERVIEW_SPLIT_SELECT &&
234                 !goingToOverviewFromWorkspaceContextual
235         ) {
236             // Not going to split
237             return
238         }
239 
240         // Create transition animations to split select
241         val orientationHandler = recentsView.pagedOrientationHandler
242         val taskViewsFloat =
243             orientationHandler.getSplitSelectTaskOffset(
244                 TASK_PRIMARY_SPLIT_TRANSLATION,
245                 TASK_SECONDARY_SPLIT_TRANSLATION,
246                 launcher.deviceProfile,
247             )
248 
249         val timings = AnimUtils.getDeviceOverviewToSplitTimings(launcher.deviceProfile.isTablet)
250         if (!goingToOverviewFromWorkspaceContextual) {
251             // This animation is already done for the contextual case, don't redo it
252             recentsView.createSplitSelectInitAnimation(
253                 builder,
254                 toState.getTransitionDuration(launcher, true),
255             )
256         }
257         // Shift tasks vertically downward to get out of placeholder view
258         builder.setFloat(
259             recentsView,
260             taskViewsFloat.first,
261             toState.getSplitSelectTranslation(launcher),
262             timings.gridSlidePrimaryInterpolator,
263         )
264         // Zero out horizontal translation
265         builder.setFloat(
266             recentsView,
267             taskViewsFloat.second,
268             0f,
269             timings.gridSlideSecondaryInterpolator,
270         )
271 
272         recentsView.handleDesktopTaskInSplitSelectState(
273             builder,
274             timings.desktopTaskFadeInterpolator,
275         )
276 
277         if (!animate) {
278             builder.buildAnim().apply {
279                 start()
280                 end()
281             }
282         }
283     }
284 
285     private fun setAlphas(
286         propertySetter: PropertySetter,
287         config: StateAnimationConfig,
288         state: LauncherState,
289     ) {
290         val clearAllButtonAlpha =
291             if (state.areElementsVisible(launcher, LauncherState.CLEAR_ALL_BUTTON)) 1f else 0f
292         propertySetter.setFloat(
293             recentsView.clearAllButton,
294             ClearAllButton.VISIBILITY_ALPHA,
295             clearAllButtonAlpha,
296             LINEAR,
297         )
298         val overviewButtonAlpha =
299             if (state.areElementsVisible(launcher, LauncherState.OVERVIEW_ACTIONS)) 1f else 0f
300         propertySetter.setFloat(
301             launcher.actionsView.visibilityAlpha,
302             AnimatedFloat.VALUE,
303             overviewButtonAlpha,
304             config.getInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, LINEAR),
305         )
306         recentsView.addDeskButton?.let {
307             propertySetter.setFloat(
308                 it,
309                 AddDesktopButton.VISIBILITY_ALPHA,
310                 if (state.areElementsVisible(launcher, LauncherState.ADD_DESK_BUTTON)) 1f else 0f,
311                 LINEAR,
312             )
313         }
314     }
315 
316     private fun getOverviewInterpolator(fromState: LauncherState, toState: LauncherState) =
317         when {
318             fromState == LauncherState.QUICK_SWITCH_FROM_HOME -> ACCELERATE_DECELERATE
319             toState.isRecentsViewVisible -> INSTANT
320             else -> FINAL_FRAME
321         }
322 }
323