• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.util;
17 
18 import static com.android.launcher3.anim.Interpolators.LINEAR;
19 import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_TRANSLATION_Y_FACTOR;
20 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
21 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
22 
23 import android.annotation.TargetApi;
24 import android.graphics.Canvas;
25 import android.graphics.Matrix;
26 import android.graphics.Matrix.ScaleToFit;
27 import android.graphics.PointF;
28 import android.graphics.Rect;
29 import android.graphics.RectF;
30 import android.os.Build;
31 import android.os.RemoteException;
32 import android.support.annotation.Nullable;
33 import android.view.animation.Interpolator;
34 
35 import com.android.launcher3.BaseDraggingActivity;
36 import com.android.launcher3.DeviceProfile;
37 import com.android.launcher3.R;
38 import com.android.launcher3.Utilities;
39 import com.android.launcher3.anim.Interpolators;
40 import com.android.launcher3.views.BaseDragLayer;
41 import com.android.quickstep.RecentsModel;
42 import com.android.quickstep.views.RecentsView;
43 import com.android.quickstep.views.TaskThumbnailView;
44 import com.android.systemui.shared.recents.ISystemUiProxy;
45 import com.android.systemui.shared.recents.utilities.RectFEvaluator;
46 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
47 import com.android.systemui.shared.system.TransactionCompat;
48 import com.android.systemui.shared.system.WindowManagerWrapper;
49 
50 import java.util.function.BiConsumer;
51 
52 /**
53  * Utility class to handle window clip animation
54  */
55 @TargetApi(Build.VERSION_CODES.P)
56 public class ClipAnimationHelper {
57 
58     // The bounds of the source app in device coordinates
59     private final Rect mSourceStackBounds = new Rect();
60     // The insets of the source app
61     private final Rect mSourceInsets = new Rect();
62     // The source app bounds with the source insets applied, in the source app window coordinates
63     private final RectF mSourceRect = new RectF();
64     // The bounds of the task view in launcher window coordinates
65     private final RectF mTargetRect = new RectF();
66     // Set when the final window destination is changed, such as offsetting for quick scrub
67     private final PointF mTargetOffset = new PointF();
68     // The insets to be used for clipping the app window, which can be larger than mSourceInsets
69     // if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In
70     // app window coordinates.
71     private final RectF mSourceWindowClipInsets = new RectF();
72 
73     // The bounds of launcher (not including insets) in device coordinates
74     public final Rect mHomeStackBounds = new Rect();
75 
76     // The clip rect in source app window coordinates
77     private final Rect mClipRect = new Rect();
78     private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
79     private final Matrix mTmpMatrix = new Matrix();
80     private final RectF mTmpRectF = new RectF();
81 
82     private float mTargetScale = 1f;
83     private float mOffsetScale = 1f;
84     private Interpolator mInterpolator = LINEAR;
85     // We translate y slightly faster than the rest of the animation for quick scrub.
86     private Interpolator mOffsetYInterpolator = LINEAR;
87 
88     // Whether to boost the opening animation target layers, or the closing
89     private int mBoostModeTargetLayers = -1;
90     // Wether or not applyTransform has been called yet since prepareAnimation()
91     private boolean mIsFirstFrame = true;
92 
93     private BiConsumer<TransactionCompat, RemoteAnimationTargetCompat> mTaskTransformCallback =
94             (t, a) -> { };
95 
updateSourceStack(RemoteAnimationTargetCompat target)96     private void updateSourceStack(RemoteAnimationTargetCompat target) {
97         mSourceInsets.set(target.contentInsets);
98         mSourceStackBounds.set(target.sourceContainerBounds);
99 
100         // TODO: Should sourceContainerBounds already have this offset?
101         mSourceStackBounds.offsetTo(target.position.x, target.position.y);
102 
103     }
104 
updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target)105     public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) {
106         mHomeStackBounds.set(homeStackBounds);
107         updateSourceStack(target);
108     }
109 
updateTargetRect(TransformedRect targetRect)110     public void updateTargetRect(TransformedRect targetRect) {
111         mOffsetScale = targetRect.scale;
112         mSourceRect.set(mSourceInsets.left, mSourceInsets.top,
113                 mSourceStackBounds.width() - mSourceInsets.right,
114                 mSourceStackBounds.height() - mSourceInsets.bottom);
115         mTargetRect.set(targetRect.rect);
116         Utilities.scaleRectFAboutCenter(mTargetRect, targetRect.scale);
117         mTargetRect.offset(mHomeStackBounds.left - mSourceStackBounds.left,
118                 mHomeStackBounds.top - mSourceStackBounds.top);
119 
120         // Calculate the clip based on the target rect (since the content insets and the
121         // launcher insets may differ, so the aspect ratio of the target rect can differ
122         // from the source rect. The difference between the target rect (scaled to the
123         // source rect) is the amount to clip on each edge.
124         RectF scaledTargetRect = new RectF(mTargetRect);
125         Utilities.scaleRectFAboutCenter(scaledTargetRect,
126                 mSourceRect.width() / mTargetRect.width());
127         scaledTargetRect.offsetTo(mSourceRect.left, mSourceRect.top);
128         mSourceWindowClipInsets.set(
129                 Math.max(scaledTargetRect.left, 0),
130                 Math.max(scaledTargetRect.top, 0),
131                 Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
132                 Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
133         mSourceRect.set(scaledTargetRect);
134     }
135 
prepareAnimation(boolean isOpening)136     public void prepareAnimation(boolean isOpening) {
137         mIsFirstFrame = true;
138         mBoostModeTargetLayers = isOpening ? MODE_OPENING : MODE_CLOSING;
139     }
140 
applyTransform(RemoteAnimationTargetSet targetSet, float progress)141     public RectF applyTransform(RemoteAnimationTargetSet targetSet, float progress) {
142         RectF currentRect;
143         mTmpRectF.set(mTargetRect);
144         Utilities.scaleRectFAboutCenter(mTmpRectF, mTargetScale);
145         float offsetYProgress = mOffsetYInterpolator.getInterpolation(progress);
146         progress = mInterpolator.getInterpolation(progress);
147         currentRect =  mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF);
148 
149         synchronized (mTargetOffset) {
150             // Stay lined up with the center of the target, since it moves for quick scrub.
151             currentRect.offset(mTargetOffset.x * mOffsetScale * progress,
152                     mTargetOffset.y  * offsetYProgress);
153         }
154 
155         mClipRect.left = (int) (mSourceWindowClipInsets.left * progress);
156         mClipRect.top = (int) (mSourceWindowClipInsets.top * progress);
157         mClipRect.right = (int)
158                 (mSourceStackBounds.width() - (mSourceWindowClipInsets.right * progress));
159         mClipRect.bottom = (int)
160                 (mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress));
161 
162         TransactionCompat transaction = new TransactionCompat();
163         if (mIsFirstFrame) {
164             RemoteAnimationProvider.prepareTargetsForFirstFrame(targetSet.unfilteredApps,
165                     transaction, mBoostModeTargetLayers);
166             mIsFirstFrame = false;
167         }
168         for (RemoteAnimationTargetCompat app : targetSet.apps) {
169             if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
170                 mTmpMatrix.setRectToRect(mSourceRect, currentRect, ScaleToFit.FILL);
171                 mTmpMatrix.postTranslate(app.position.x, app.position.y);
172                 transaction.setMatrix(app.leash, mTmpMatrix)
173                         .setWindowCrop(app.leash, mClipRect);
174             }
175 
176             if (app.isNotInRecents
177                     || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
178                 transaction.setAlpha(app.leash, 1 - progress);
179             }
180 
181             mTaskTransformCallback.accept(transaction, app);
182         }
183         transaction.setEarlyWakeup();
184         transaction.apply();
185         return currentRect;
186     }
187 
setTaskTransformCallback(BiConsumer<TransactionCompat, RemoteAnimationTargetCompat> callback)188     public void setTaskTransformCallback
189             (BiConsumer<TransactionCompat, RemoteAnimationTargetCompat> callback) {
190         mTaskTransformCallback = callback;
191     }
192 
offsetTarget(float scale, float offsetX, float offsetY, Interpolator interpolator)193     public void offsetTarget(float scale, float offsetX, float offsetY, Interpolator interpolator) {
194         synchronized (mTargetOffset) {
195             mTargetOffset.set(offsetX, offsetY);
196         }
197         mTargetScale = scale;
198         mInterpolator = interpolator;
199         mOffsetYInterpolator = Interpolators.clampToProgress(mInterpolator, 0,
200                 QUICK_SCRUB_TRANSLATION_Y_FACTOR);
201     }
202 
fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv)203     public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv) {
204         fromTaskThumbnailView(ttv, rv, null);
205     }
206 
fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv, @Nullable RemoteAnimationTargetCompat target)207     public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv,
208             @Nullable RemoteAnimationTargetCompat target) {
209         BaseDraggingActivity activity = BaseDraggingActivity.fromContext(ttv.getContext());
210         BaseDragLayer dl = activity.getDragLayer();
211 
212         int[] pos = new int[2];
213         dl.getLocationOnScreen(pos);
214         mHomeStackBounds.set(0, 0, dl.getWidth(), dl.getHeight());
215         mHomeStackBounds.offset(pos[0], pos[1]);
216 
217         if (target != null) {
218             updateSourceStack(target);
219         } else  if (rv.shouldUseMultiWindowTaskSizeStrategy()) {
220             updateStackBoundsToMultiWindowTaskSize(activity);
221         } else {
222             mSourceStackBounds.set(mHomeStackBounds);
223             mSourceInsets.set(activity.getDeviceProfile().getInsets());
224         }
225 
226         TransformedRect targetRect = new TransformedRect();
227         dl.getDescendantRectRelativeToSelf(ttv, targetRect.rect);
228         updateTargetRect(targetRect);
229 
230         if (target == null) {
231             // Transform the clip relative to the target rect. Only do this in the case where we
232             // aren't applying the insets to the app windows (where the clip should be in target app
233             // space)
234             float scale = mTargetRect.width() / mSourceRect.width();
235             mSourceWindowClipInsets.left = mSourceWindowClipInsets.left * scale;
236             mSourceWindowClipInsets.top = mSourceWindowClipInsets.top * scale;
237             mSourceWindowClipInsets.right = mSourceWindowClipInsets.right * scale;
238             mSourceWindowClipInsets.bottom = mSourceWindowClipInsets.bottom * scale;
239         }
240     }
241 
updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity)242     private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) {
243         ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy();
244         if (sysUiProxy != null) {
245             try {
246                 mSourceStackBounds.set(sysUiProxy.getNonMinimizedSplitScreenSecondaryBounds());
247                 return;
248             } catch (RemoteException e) {
249                 // Use half screen size
250             }
251         }
252 
253         // Assume that the task size is half screen size (minus the insets and the divider size)
254         DeviceProfile fullDp = activity.getDeviceProfile().getFullScreenProfile();
255         // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
256         // account for system insets
257         int taskWidth = fullDp.availableWidthPx;
258         int taskHeight = fullDp.availableHeightPx;
259         int halfDividerSize = activity.getResources()
260                 .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
261 
262         Rect insets = new Rect();
263         WindowManagerWrapper.getInstance().getStableInsets(insets);
264         if (fullDp.isLandscape) {
265             taskWidth = taskWidth / 2 - halfDividerSize;
266         } else {
267             taskHeight = taskHeight / 2 - halfDividerSize;
268         }
269 
270         // Align the task to bottom left/right edge (closer to nav bar).
271         int left = activity.getDeviceProfile().isSeascape() ? insets.left
272                 : (insets.left + fullDp.availableWidthPx - taskWidth);
273         mSourceStackBounds.set(0, 0, taskWidth, taskHeight);
274         mSourceStackBounds.offset(left, insets.top + fullDp.availableHeightPx - taskHeight);
275     }
276 
drawForProgress(TaskThumbnailView ttv, Canvas canvas, float progress)277     public void drawForProgress(TaskThumbnailView ttv, Canvas canvas, float progress) {
278         RectF currentRect =  mRectFEvaluator.evaluate(progress, mSourceRect, mTargetRect);
279         canvas.translate(mSourceStackBounds.left - mHomeStackBounds.left,
280                 mSourceStackBounds.top - mHomeStackBounds.top);
281         mTmpMatrix.setRectToRect(mTargetRect, currentRect, ScaleToFit.FILL);
282 
283         canvas.concat(mTmpMatrix);
284         canvas.translate(mTargetRect.left, mTargetRect.top);
285 
286         float insetProgress = (1 - progress);
287         ttv.drawOnCanvas(canvas,
288                 -mSourceWindowClipInsets.left * insetProgress,
289                 -mSourceWindowClipInsets.top * insetProgress,
290                 ttv.getMeasuredWidth() + mSourceWindowClipInsets.right * insetProgress,
291                 ttv.getMeasuredHeight() + mSourceWindowClipInsets.bottom * insetProgress,
292                 ttv.getCornerRadius() * progress);
293     }
294 
getTargetRect()295     public RectF getTargetRect() {
296         return mTargetRect;
297     }
298 
getSourceRect()299     public RectF getSourceRect() {
300         return mSourceRect;
301     }
302 }
303