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.anim.Interpolators.LINEAR;
19 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
20 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
21 
22 import android.animation.Animator;
23 import android.animation.AnimatorListenerAdapter;
24 import android.animation.ValueAnimator;
25 import android.content.ComponentName;
26 import android.graphics.RectF;
27 import android.view.View;
28 
29 import com.android.launcher3.BaseActivity;
30 import com.android.launcher3.BaseDraggingActivity;
31 import com.android.launcher3.ItemInfo;
32 import com.android.launcher3.Utilities;
33 import com.android.quickstep.util.ClipAnimationHelper;
34 import com.android.quickstep.util.MultiValueUpdateListener;
35 import com.android.quickstep.util.RemoteAnimationTargetSet;
36 import com.android.quickstep.views.RecentsView;
37 import com.android.quickstep.views.TaskView;
38 import com.android.systemui.shared.recents.model.Task;
39 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
40 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
41 
42 /**
43  * Utility class for helpful methods related to {@link TaskView} objects and their tasks.
44  */
45 public final class TaskViewUtils {
46 
TaskViewUtils()47     private TaskViewUtils() {}
48 
49     /**
50      * Try to find a TaskView that corresponds with the component of the launched view.
51      *
52      * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation.
53      * Otherwise, we will assume we are using a normal app transition, but it's possible that the
54      * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
55      */
findTaskViewToLaunch( BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets)56     public static TaskView findTaskViewToLaunch(
57             BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets) {
58         RecentsView recentsView = activity.getOverviewPanel();
59         if (v instanceof TaskView) {
60             TaskView taskView = (TaskView) v;
61             return recentsView.isTaskViewVisible(taskView) ? taskView : null;
62         }
63 
64         // It's possible that the launched view can still be resolved to a visible task view, check
65         // the task id of the opening task and see if we can find a match.
66         if (v.getTag() instanceof ItemInfo) {
67             ItemInfo itemInfo = (ItemInfo) v.getTag();
68             ComponentName componentName = itemInfo.getTargetComponent();
69             int userId = itemInfo.user.getIdentifier();
70             if (componentName != null) {
71                 for (int i = 0; i < recentsView.getTaskViewCount(); i++) {
72                     TaskView taskView = recentsView.getTaskViewAt(i);
73                     if (recentsView.isTaskViewVisible(taskView)) {
74                         Task.TaskKey key = taskView.getTask().key;
75                         if (componentName.equals(key.getComponent()) && userId == key.userId) {
76                             return taskView;
77                         }
78                     }
79                 }
80             }
81         }
82 
83         if (targets == null) {
84             return null;
85         }
86         // Resolve the opening task id
87         int openingTaskId = -1;
88         for (RemoteAnimationTargetCompat target : targets) {
89             if (target.mode == MODE_OPENING) {
90                 openingTaskId = target.taskId;
91                 break;
92             }
93         }
94 
95         // If there is no opening task id, fall back to the normal app icon launch animation
96         if (openingTaskId == -1) {
97             return null;
98         }
99 
100         // If the opening task id is not currently visible in overview, then fall back to normal app
101         // icon launch animation
102         TaskView taskView = recentsView.getTaskView(openingTaskId);
103         if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
104             return null;
105         }
106         return taskView;
107     }
108 
109     /**
110      * @return Animator that controls the window of the opening targets for the recents launch
111      * animation.
112      */
getRecentsWindowAnimator(TaskView v, boolean skipViewChanges, RemoteAnimationTargetCompat[] targets, final ClipAnimationHelper inOutHelper)113     public static ValueAnimator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
114             RemoteAnimationTargetCompat[] targets, final ClipAnimationHelper inOutHelper) {
115         SyncRtSurfaceTransactionApplierCompat applier =
116                 new SyncRtSurfaceTransactionApplierCompat(v);
117         ClipAnimationHelper.TransformParams params = new ClipAnimationHelper.TransformParams()
118                 .setSyncTransactionApplier(applier);
119 
120         final RemoteAnimationTargetSet targetSet =
121                 new RemoteAnimationTargetSet(targets, MODE_OPENING);
122         targetSet.addDependentTransactionApplier(applier);
123 
124         final RecentsView recentsView = v.getRecentsView();
125         final ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
126         appAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
127         appAnimator.addUpdateListener(new MultiValueUpdateListener() {
128 
129             // Defer fading out the view until after the app window gets faded in
130             final FloatProp mViewAlpha = new FloatProp(1f, 0f, 75, 75, LINEAR);
131             final FloatProp mTaskAlpha = new FloatProp(0f, 1f, 0, 75, LINEAR);
132 
133 
134             final RectF mThumbnailRect;
135 
136             {
137                 inOutHelper.setTaskAlphaCallback((t, alpha) -> mTaskAlpha.value);
138 
139                 inOutHelper.prepareAnimation(
140                         BaseActivity.fromContext(v.getContext()).getDeviceProfile(),
141                         true /* isOpening */);
142                 inOutHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(),
143                         targetSet.apps.length == 0 ? null : targetSet.apps[0]);
144 
145                 mThumbnailRect = new RectF(inOutHelper.getTargetRect());
146                 mThumbnailRect.offset(-v.getTranslationX(), -v.getTranslationY());
147                 Utilities.scaleRectFAboutCenter(mThumbnailRect, 1 / v.getScaleX());
148             }
149 
150             @Override
151             public void onUpdate(float percent) {
152                 // TODO: Take into account the current fullscreen progress for animating the insets
153                 params.setProgress(1 - percent);
154                 RectF taskBounds = inOutHelper.applyTransform(targetSet, params);
155                 int taskIndex = recentsView.indexOfChild(v);
156                 int centerTaskIndex = recentsView.getCurrentPage();
157                 boolean parallaxCenterAndAdjacentTask = taskIndex != centerTaskIndex;
158                 if (!skipViewChanges && parallaxCenterAndAdjacentTask) {
159                     float scale = taskBounds.width() / mThumbnailRect.width();
160                     v.setScaleX(scale);
161                     v.setScaleY(scale);
162                     v.setTranslationX(taskBounds.centerX() - mThumbnailRect.centerX());
163                     v.setTranslationY(taskBounds.centerY() - mThumbnailRect.centerY());
164                     v.setAlpha(mViewAlpha.value);
165                 }
166             }
167         });
168         appAnimator.addListener(new AnimatorListenerAdapter() {
169             @Override
170             public void onAnimationEnd(Animator animation) {
171                 targetSet.release();
172             }
173         });
174         return appAnimator;
175     }
176 }
177