• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.animation.ObjectAnimator
21 import android.graphics.Bitmap
22 import android.graphics.drawable.Drawable
23 import android.view.View
24 import com.android.launcher3.DeviceProfile
25 import com.android.launcher3.anim.PendingAnimation
26 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
27 import com.android.quickstep.views.IconView
28 import com.android.quickstep.views.TaskThumbnailView
29 import com.android.quickstep.views.TaskView
30 import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
31 import java.util.function.Supplier
32 
33 /**
34  * Utils class to help run animations for initiating split screen from launcher.
35  * Will be expanded with future refactors. Works in conjunction with the state stored in
36  * [SplitSelectStateController]
37  */
38 class SplitAnimationController(val splitSelectStateController: SplitSelectStateController) {
39     companion object {
40         // Break this out into maybe enums? Abstractions into its own classes? Tbd.
41         data class SplitAnimInitProps(
42                 val originalView: View,
43                 val originalBitmap: Bitmap?,
44                 val iconDrawable: Drawable,
45                 val fadeWithThumbnail: Boolean,
46                 val isStagedTask: Boolean,
47                 val iconView: View?
48         )
49     }
50 
51     /**
52      * Returns different elements to animate for the initial split selection animation
53      * depending on the state of the surface from which the split was initiated
54      */
getFirstAnimInitViewsnull55     fun getFirstAnimInitViews(taskViewSupplier: Supplier<TaskView>,
56                               splitSelectSourceSupplier: Supplier<SplitSelectSource?>)
57             : SplitAnimInitProps {
58         val splitSelectSource = splitSelectSourceSupplier.get()
59         if (!splitSelectStateController.isAnimateCurrentTaskDismissal) {
60             // Initiating from home
61             return SplitAnimInitProps(splitSelectSource!!.view, originalBitmap = null,
62                     splitSelectSource.drawable, fadeWithThumbnail = false, isStagedTask = true,
63                     iconView = null)
64         } else if (splitSelectStateController.isDismissingFromSplitPair) {
65             // Initiating split from overview, but on a split pair
66             val taskView = taskViewSupplier.get()
67             for (container : TaskIdAttributeContainer in taskView.taskIdAttributeContainers) {
68                 if (container.task.getKey().getId() == splitSelectStateController.initialTaskId) {
69                     val drawable = getDrawable(container.iconView, splitSelectSource)
70                     return SplitAnimInitProps(container.thumbnailView,
71                             container.thumbnailView.thumbnail, drawable!!,
72                             fadeWithThumbnail = true, isStagedTask = true,
73                             iconView = container.iconView
74                     )
75                 }
76             }
77             throw IllegalStateException("Attempting to init split from existing split pair " +
78                     "without a valid taskIdAttributeContainer")
79         } else {
80             // Initiating split from overview on fullscreen task TaskView
81             val taskView = taskViewSupplier.get()
82             val drawable = getDrawable(taskView.iconView, splitSelectSource)
83             return SplitAnimInitProps(taskView.thumbnail, taskView.thumbnail.thumbnail,
84                     drawable!!, fadeWithThumbnail = true, isStagedTask = true,
85                     taskView.iconView
86             )
87         }
88     }
89 
90     /**
91      * Returns the drawable that's provided in iconView, however if that
92      * is null it falls back to the drawable that's in splitSelectSource.
93      * TaskView's icon drawable can be null if the TaskView is scrolled far enough off screen
94      * @return [Drawable]
95      */
getDrawablenull96     fun getDrawable(iconView: IconView, splitSelectSource: SplitSelectSource?) : Drawable? {
97         if (iconView.drawable == null && splitSelectSource != null) {
98             return splitSelectSource.drawable
99         }
100         return iconView.drawable
101     }
102 
103     /**
104      * When selecting first app from split pair, second app's thumbnail remains. This animates
105      * the second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying
106      * it with [TaskThumbnailView]'s splashView. Adds animations to the provided builder.
107      * Note: The app that **was not** selected as the first split app should be the container that's
108      * passed through.
109      *
110      * @param builder Adds animation to this
111      * @param taskIdAttributeContainer container of the app that **was not** selected
112      * @param isPrimaryTaskSplitting if true, task that was split would be top/left in the pair
113      *                               (opposite of that representing [taskIdAttributeContainer])
114      */
addInitialSplitFromPairnull115     fun addInitialSplitFromPair(taskIdAttributeContainer: TaskIdAttributeContainer,
116                                 builder: PendingAnimation, deviceProfile: DeviceProfile,
117                                 taskViewWidth: Int, taskViewHeight: Int,
118                                 isPrimaryTaskSplitting: Boolean) {
119         val thumbnail = taskIdAttributeContainer.thumbnailView
120         val iconView: View = taskIdAttributeContainer.iconView
121         builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLASH_ALPHA, 1f))
122         thumbnail.setShowSplashForSplitSelection(true)
123         if (deviceProfile.isLandscape) {
124             // Center view first so scaling happens uniformly, alternatively we can move pivotX to 0
125             val centerThumbnailTranslationX: Float = (taskViewWidth - thumbnail.width) / 2f
126             val centerIconTranslationX: Float = (taskViewWidth - iconView.width) / 2f
127             val finalScaleX: Float = taskViewWidth.toFloat() / thumbnail.width
128             builder.add(ObjectAnimator.ofFloat(thumbnail,
129                     TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, centerThumbnailTranslationX))
130             // icons are anchored from Gravity.END, so need to use negative translation
131             builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
132                     -centerIconTranslationX))
133             builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_X, finalScaleX))
134 
135             // Reset other dimensions
136             // TODO(b/271468547), can't set Y translate to 0, need to account for top space
137             thumbnail.scaleY = 1f
138             val translateYResetVal: Float = if (!isPrimaryTaskSplitting) 0f else
139                 deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
140             builder.add(ObjectAnimator.ofFloat(thumbnail,
141                     TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y,
142                     translateYResetVal))
143         } else {
144             val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
145             // Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
146             // primary thumbnail has layout margin above it, so secondary thumbnail needs to take
147             // that into account. We should migrate to only using translations otherwise this
148             // asymmetry causes problems..
149 
150             // Icon defaults to center | horizontal, we add additional translation for split
151             val centerIconTranslationX = 0f
152             var centerThumbnailTranslationY: Float
153 
154             // TODO(b/271468547), primary thumbnail has layout margin above it, so secondary
155             //  thumbnail needs to take that into account. We should migrate to only using
156             //  translations otherwise this asymmetry causes problems..
157             if (isPrimaryTaskSplitting) {
158                 centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
159                 centerThumbnailTranslationY += deviceProfile.overviewTaskThumbnailTopMarginPx
160                         .toFloat()
161             } else {
162                 centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
163             }
164             val finalScaleY: Float = thumbnailSize.toFloat() / thumbnail.height
165             builder.add(ObjectAnimator.ofFloat(thumbnail,
166                     TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, centerThumbnailTranslationY))
167 
168             // icons are anchored from Gravity.END, so need to use negative translation
169             builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
170                     centerIconTranslationX))
171             builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_Y, finalScaleY))
172 
173             // Reset other dimensions
174             thumbnail.scaleX = 1f
175             builder.add(ObjectAnimator.ofFloat(thumbnail,
176                     TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f))
177         }
178     }
179 }
180