1 /*
2  * Copyright (C) 2019 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.quickstep;
17 
18 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
19 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
20 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
21 import static com.android.launcher3.anim.Interpolators.LINEAR;
22 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
23 import static com.android.launcher3.anim.Interpolators.clampToProgress;
24 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
25 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
26 
27 import android.animation.Animator;
28 import android.animation.AnimatorListenerAdapter;
29 import android.annotation.TargetApi;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.graphics.Matrix;
33 import android.graphics.Matrix.ScaleToFit;
34 import android.graphics.RectF;
35 import android.os.Build;
36 import android.view.View;
37 
38 import com.android.launcher3.BaseActivity;
39 import com.android.launcher3.BaseDraggingActivity;
40 import com.android.launcher3.DeviceProfile;
41 import com.android.launcher3.anim.PendingAnimation;
42 import com.android.launcher3.model.data.ItemInfo;
43 import com.android.launcher3.statehandlers.DepthController;
44 import com.android.launcher3.util.DefaultDisplay;
45 import com.android.quickstep.util.SurfaceTransactionApplier;
46 import com.android.quickstep.util.TaskViewSimulator;
47 import com.android.quickstep.util.TransformParams;
48 import com.android.quickstep.views.RecentsView;
49 import com.android.quickstep.views.TaskThumbnailView;
50 import com.android.quickstep.views.TaskView;
51 import com.android.systemui.shared.recents.model.Task;
52 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
53 
54 /**
55  * Utility class for helpful methods related to {@link TaskView} objects and their tasks.
56  */
57 @TargetApi(Build.VERSION_CODES.R)
58 public final class TaskViewUtils {
59 
TaskViewUtils()60     private TaskViewUtils() {}
61 
62     /**
63      * Try to find a TaskView that corresponds with the component of the launched view.
64      *
65      * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation.
66      * Otherwise, we will assume we are using a normal app transition, but it's possible that the
67      * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
68      */
findTaskViewToLaunch( BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets)69     public static TaskView findTaskViewToLaunch(
70             BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets) {
71         RecentsView recentsView = activity.getOverviewPanel();
72         if (v instanceof TaskView) {
73             TaskView taskView = (TaskView) v;
74             return recentsView.isTaskViewVisible(taskView) ? taskView : null;
75         }
76 
77         // It's possible that the launched view can still be resolved to a visible task view, check
78         // the task id of the opening task and see if we can find a match.
79         if (v.getTag() instanceof ItemInfo) {
80             ItemInfo itemInfo = (ItemInfo) v.getTag();
81             ComponentName componentName = itemInfo.getTargetComponent();
82             int userId = itemInfo.user.getIdentifier();
83             if (componentName != null) {
84                 for (int i = 0; i < recentsView.getTaskViewCount(); i++) {
85                     TaskView taskView = recentsView.getTaskViewAt(i);
86                     if (recentsView.isTaskViewVisible(taskView)) {
87                         Task.TaskKey key = taskView.getTask().key;
88                         if (componentName.equals(key.getComponent()) && userId == key.userId) {
89                             return taskView;
90                         }
91                     }
92                 }
93             }
94         }
95 
96         if (targets == null) {
97             return null;
98         }
99         // Resolve the opening task id
100         int openingTaskId = -1;
101         for (RemoteAnimationTargetCompat target : targets) {
102             if (target.mode == MODE_OPENING) {
103                 openingTaskId = target.taskId;
104                 break;
105             }
106         }
107 
108         // If there is no opening task id, fall back to the normal app icon launch animation
109         if (openingTaskId == -1) {
110             return null;
111         }
112 
113         // If the opening task id is not currently visible in overview, then fall back to normal app
114         // icon launch animation
115         TaskView taskView = recentsView.getTaskView(openingTaskId);
116         if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
117             return null;
118         }
119         return taskView;
120     }
121 
122     /**
123      * Creates an animation that controls the window of the opening targets for the recents launch
124      * animation.
125      */
createRecentsWindowAnimator(TaskView v, boolean skipViewChanges, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController, PendingAnimation out)126     public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
127             RemoteAnimationTargetCompat[] appTargets,
128             RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController,
129             PendingAnimation out) {
130 
131         SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
132         final RemoteAnimationTargets targets =
133                 new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_OPENING);
134         targets.addReleaseCheck(applier);
135 
136         TransformParams params = new TransformParams()
137                     .setSyncTransactionApplier(applier)
138                     .setTargetSet(targets);
139 
140         final RecentsView recentsView = v.getRecentsView();
141         int taskIndex = recentsView.indexOfChild(v);
142         boolean parallaxCenterAndAdjacentTask = taskIndex != recentsView.getCurrentPage();
143         int startScroll = recentsView.getScrollOffset(taskIndex);
144 
145         Context context = v.getContext();
146         DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
147         // RecentsView never updates the display rotation until swipe-up so the value may be stale.
148         // Use the display value instead.
149         int displayRotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
150 
151         TaskViewSimulator topMostSimulator = null;
152         if (targets.apps.length > 0) {
153             TaskViewSimulator tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
154             tsv.setDp(dp);
155             tsv.setLayoutRotation(displayRotation, displayRotation);
156             tsv.setPreview(targets.apps[targets.apps.length - 1]);
157             tsv.fullScreenProgress.value = 0;
158             tsv.recentsViewScale.value = 1;
159             tsv.setScroll(startScroll);
160 
161             out.setFloat(tsv.fullScreenProgress,
162                     AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
163             out.setFloat(tsv.recentsViewScale,
164                     AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
165             out.setInt(tsv, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
166 
167             out.addOnFrameCallback(() -> tsv.apply(params));
168             topMostSimulator = tsv;
169         }
170 
171         // Fade in the task during the initial 20% of the animation
172         out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1, clampToProgress(LINEAR, 0, 0.2f));
173 
174         if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulator != null) {
175             out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
176 
177             TaskViewSimulator simulatorToCopy = topMostSimulator;
178             simulatorToCopy.apply(params);
179 
180             // Mt represents the overall transformation on the thumbnailView relative to the
181             // Launcher's rootView
182             // K(t) represents transformation on the running window by the taskViewSimulator at
183             // any time t.
184             // at t = 0, we know that the simulator matches the thumbnailView. So if we apply K(0)`
185             // on the Launcher's rootView, the thumbnailView would match the full running task
186             // window. If we apply "K(0)` K(t)" thumbnailView will match the final transformed
187             // window at any time t. This gives the overall matrix on thumbnailView to be:
188             //    Mt K(0)` K(t)
189             // During animation we apply transformation on the thumbnailView (and not the rootView)
190             // to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
191             //    Mt K(0)` K(t) Mt`
192             TaskThumbnailView ttv = v.getThumbnail();
193             RectF tvBounds = new RectF(0, 0,  ttv.getWidth(), ttv.getHeight());
194             float[] tvBoundsMapped = new float[]{0, 0,  ttv.getWidth(), ttv.getHeight()};
195             getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false);
196             RectF tvBoundsInRoot = new RectF(
197                     tvBoundsMapped[0], tvBoundsMapped[1],
198                     tvBoundsMapped[2], tvBoundsMapped[3]);
199 
200             Matrix mt = new Matrix();
201             mt.setRectToRect(tvBounds, tvBoundsInRoot, ScaleToFit.FILL);
202 
203             Matrix mti = new Matrix();
204             mt.invert(mti);
205 
206             Matrix k0i = new Matrix();
207             simulatorToCopy.getCurrentMatrix().invert(k0i);
208 
209             Matrix animationMatrix = new Matrix();
210             out.addOnFrameCallback(() -> {
211                 animationMatrix.set(mt);
212                 animationMatrix.postConcat(k0i);
213                 animationMatrix.postConcat(simulatorToCopy.getCurrentMatrix());
214                 animationMatrix.postConcat(mti);
215                 ttv.setAnimationMatrix(animationMatrix);
216             });
217 
218             out.addListener(new AnimatorListenerAdapter() {
219                 @Override
220                 public void onAnimationEnd(Animator animation) {
221                     ttv.setAnimationMatrix(null);
222                 }
223             });
224         }
225 
226         out.addListener(new AnimatorListenerAdapter() {
227             @Override
228             public void onAnimationEnd(Animator animation) {
229                 targets.release();
230             }
231         });
232 
233         if (depthController != null) {
234             out.setFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(context),
235                     TOUCH_RESPONSE_INTERPOLATOR);
236         }
237     }
238 }
239