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