• 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.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
19 import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
20 import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
21 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
22 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
23 
24 import android.annotation.TargetApi;
25 import android.content.Context;
26 import android.graphics.Matrix;
27 import android.graphics.Matrix.ScaleToFit;
28 import android.graphics.Rect;
29 import android.graphics.RectF;
30 import android.os.Build;
31 import android.os.RemoteException;
32 
33 import androidx.annotation.Nullable;
34 
35 import com.android.launcher3.BaseDraggingActivity;
36 import com.android.launcher3.DeviceProfile;
37 import com.android.launcher3.LauncherState;
38 import com.android.launcher3.R;
39 import com.android.launcher3.Utilities;
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.quickstep.views.TaskView;
45 import com.android.systemui.shared.recents.ISystemUiProxy;
46 import com.android.systemui.shared.recents.utilities.RectFEvaluator;
47 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
48 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
49 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
50 import com.android.systemui.shared.system.TransactionCompat;
51 import com.android.systemui.shared.system.WindowManagerWrapper;
52 
53 import java.util.function.BiFunction;
54 
55 /**
56  * Utility class to handle window clip animation
57  */
58 @TargetApi(Build.VERSION_CODES.P)
59 public class ClipAnimationHelper {
60 
61     // The bounds of the source app in device coordinates
62     private final Rect mSourceStackBounds = new Rect();
63     // The insets of the source app
64     private final Rect mSourceInsets = new Rect();
65     // The source app bounds with the source insets applied, in the source app window coordinates
66     private final RectF mSourceRect = new RectF();
67     // The bounds of the task view in launcher window coordinates
68     private final RectF mTargetRect = new RectF();
69     // The insets to be used for clipping the app window, which can be larger than mSourceInsets
70     // if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In
71     // app window coordinates.
72     private final RectF mSourceWindowClipInsets = new RectF();
73     // The insets to be used for clipping the app window. For live tile, we don't transform the clip
74     // relative to the target rect.
75     private final RectF mSourceWindowClipInsetsForLiveTile = new RectF();
76 
77     // The bounds of launcher (not including insets) in device coordinates
78     public final Rect mHomeStackBounds = new Rect();
79 
80     // The clip rect in source app window coordinates
81     private final RectF mClipRectF = new RectF();
82     private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
83     private final Matrix mTmpMatrix = new Matrix();
84     private final Rect mTmpRect = new Rect();
85     private final RectF mTmpRectF = new RectF();
86     private final RectF mCurrentRectWithInsets = new RectF();
87     // Corner radius of windows, in pixels
88     private final float mWindowCornerRadius;
89     // Corner radius of windows when they're in overview mode.
90     private final float mTaskCornerRadius;
91     // If windows can have real time rounded corners.
92     private final boolean mSupportsRoundedCornersOnWindows;
93     // Whether or not to actually use the rounded cornders on windows
94     private boolean mUseRoundedCornersOnWindows;
95 
96     // Corner radius currently applied to transformed window.
97     private float mCurrentCornerRadius;
98 
99     // Whether to boost the opening animation target layers, or the closing
100     private int mBoostModeTargetLayers = -1;
101 
102     private BiFunction<RemoteAnimationTargetCompat, Float, Float> mTaskAlphaCallback =
103             (t, a1) -> a1;
104 
ClipAnimationHelper(Context context)105     public ClipAnimationHelper(Context context) {
106         mWindowCornerRadius = getWindowCornerRadius(context.getResources());
107         mSupportsRoundedCornersOnWindows = supportsRoundedCornersOnWindows(context.getResources());
108         mTaskCornerRadius = TaskCornerRadius.get(context);
109         mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows;
110     }
111 
updateSourceStack(RemoteAnimationTargetCompat target)112     private void updateSourceStack(RemoteAnimationTargetCompat target) {
113         mSourceInsets.set(target.contentInsets);
114         mSourceStackBounds.set(target.sourceContainerBounds);
115 
116         // TODO: Should sourceContainerBounds already have this offset?
117         mSourceStackBounds.offsetTo(target.position.x, target.position.y);
118 
119     }
120 
updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target)121     public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) {
122         mHomeStackBounds.set(homeStackBounds);
123         updateSourceStack(target);
124     }
125 
updateTargetRect(Rect targetRect)126     public void updateTargetRect(Rect targetRect) {
127         mSourceRect.set(mSourceInsets.left, mSourceInsets.top,
128                 mSourceStackBounds.width() - mSourceInsets.right,
129                 mSourceStackBounds.height() - mSourceInsets.bottom);
130         mTargetRect.set(targetRect);
131         mTargetRect.offset(mHomeStackBounds.left - mSourceStackBounds.left,
132                 mHomeStackBounds.top - mSourceStackBounds.top);
133 
134         // Calculate the clip based on the target rect (since the content insets and the
135         // launcher insets may differ, so the aspect ratio of the target rect can differ
136         // from the source rect. The difference between the target rect (scaled to the
137         // source rect) is the amount to clip on each edge.
138         RectF scaledTargetRect = new RectF(mTargetRect);
139         Utilities.scaleRectFAboutCenter(scaledTargetRect,
140                 mSourceRect.width() / mTargetRect.width());
141         scaledTargetRect.offsetTo(mSourceRect.left, mSourceRect.top);
142         mSourceWindowClipInsets.set(
143                 Math.max(scaledTargetRect.left, 0),
144                 Math.max(scaledTargetRect.top, 0),
145                 Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
146                 Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
147         mSourceWindowClipInsetsForLiveTile.set(mSourceWindowClipInsets);
148         mSourceRect.set(scaledTargetRect);
149     }
150 
prepareAnimation(DeviceProfile dp, boolean isOpening)151     public void prepareAnimation(DeviceProfile dp, boolean isOpening) {
152         mBoostModeTargetLayers = isOpening ? MODE_OPENING : MODE_CLOSING;
153         mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows && !dp.isMultiWindowMode;
154     }
155 
applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params)156     public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params) {
157         return applyTransform(targetSet, params, true /* launcherOnTop */);
158     }
159 
applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params, boolean launcherOnTop)160     public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params,
161             boolean launcherOnTop) {
162         float progress = params.progress;
163         if (params.currentRect == null) {
164             RectF currentRect;
165             mTmpRectF.set(mTargetRect);
166             Utilities.scaleRectFAboutCenter(mTmpRectF, params.offsetScale);
167             currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF);
168             currentRect.offset(params.offsetX, 0);
169 
170             // Don't clip past progress > 1.
171             progress = Math.min(1, progress);
172             final RectF sourceWindowClipInsets = params.forLiveTile
173                     ? mSourceWindowClipInsetsForLiveTile : mSourceWindowClipInsets;
174             mClipRectF.left = sourceWindowClipInsets.left * progress;
175             mClipRectF.top = sourceWindowClipInsets.top * progress;
176             mClipRectF.right =
177                     mSourceStackBounds.width() - (sourceWindowClipInsets.right * progress);
178             mClipRectF.bottom =
179                     mSourceStackBounds.height() - (sourceWindowClipInsets.bottom * progress);
180             params.setCurrentRectAndTargetAlpha(currentRect, 1);
181         }
182 
183         SurfaceParams[] surfaceParams = new SurfaceParams[targetSet.unfilteredApps.length];
184         for (int i = 0; i < targetSet.unfilteredApps.length; i++) {
185             RemoteAnimationTargetCompat app = targetSet.unfilteredApps[i];
186             mTmpMatrix.setTranslate(app.position.x, app.position.y);
187             Rect crop = mTmpRect;
188             crop.set(app.sourceContainerBounds);
189             crop.offsetTo(0, 0);
190             float alpha = 1f;
191             int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
192             float cornerRadius = 0f;
193             float scale = Math.max(params.currentRect.width(), mTargetRect.width()) / crop.width();
194             if (app.mode == targetSet.targetMode) {
195                 alpha = mTaskAlphaCallback.apply(app, params.targetAlpha);
196                 if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
197                     mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL);
198                     mTmpMatrix.postTranslate(app.position.x, app.position.y);
199                     mClipRectF.roundOut(crop);
200                     if (mSupportsRoundedCornersOnWindows) {
201                         if (params.cornerRadius > -1) {
202                             cornerRadius = params.cornerRadius;
203                             scale = params.currentRect.width() / crop.width();
204                         } else {
205                             float windowCornerRadius = mUseRoundedCornersOnWindows
206                                     ? mWindowCornerRadius : 0;
207                             cornerRadius = Utilities.mapRange(progress, windowCornerRadius,
208                                     mTaskCornerRadius);
209                         }
210                         mCurrentCornerRadius = cornerRadius;
211                     }
212                 } else if (targetSet.hasRecents) {
213                     // If home has a different target then recents, reverse anim the
214                     // home target.
215                     alpha = 1 - (progress * params.targetAlpha);
216                 }
217             } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && launcherOnTop) {
218                 crop = null;
219                 layer = Integer.MAX_VALUE;
220             }
221 
222             // Since radius is in Surface space, but we draw the rounded corners in screen space, we
223             // have to undo the scale.
224             surfaceParams[i] = new SurfaceParams(app.leash, alpha, mTmpMatrix, crop, layer,
225                     cornerRadius / scale);
226         }
227         applySurfaceParams(params.syncTransactionApplier, surfaceParams);
228         return params.currentRect;
229     }
230 
getCurrentRectWithInsets()231     public RectF getCurrentRectWithInsets() {
232         mTmpMatrix.mapRect(mCurrentRectWithInsets, mClipRectF);
233         return mCurrentRectWithInsets;
234     }
235 
applySurfaceParams(@ullable SyncRtSurfaceTransactionApplierCompat syncTransactionApplier, SurfaceParams[] params)236     private void applySurfaceParams(@Nullable SyncRtSurfaceTransactionApplierCompat
237             syncTransactionApplier, SurfaceParams[] params) {
238         if (syncTransactionApplier != null) {
239             syncTransactionApplier.scheduleApply(params);
240         } else {
241             TransactionCompat t = new TransactionCompat();
242             for (SurfaceParams param : params) {
243                 SyncRtSurfaceTransactionApplierCompat.applyParams(t, param);
244             }
245             t.setEarlyWakeup();
246             t.apply();
247         }
248     }
249 
setTaskAlphaCallback( BiFunction<RemoteAnimationTargetCompat, Float, Float> callback)250     public void setTaskAlphaCallback(
251             BiFunction<RemoteAnimationTargetCompat, Float, Float> callback) {
252         mTaskAlphaCallback = callback;
253     }
254 
fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv)255     public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv) {
256         fromTaskThumbnailView(ttv, rv, null);
257     }
258 
fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv, @Nullable RemoteAnimationTargetCompat target)259     public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv,
260             @Nullable RemoteAnimationTargetCompat target) {
261         BaseDraggingActivity activity = BaseDraggingActivity.fromContext(ttv.getContext());
262         BaseDragLayer dl = activity.getDragLayer();
263 
264         int[] pos = new int[2];
265         dl.getLocationOnScreen(pos);
266         mHomeStackBounds.set(0, 0, dl.getWidth(), dl.getHeight());
267         mHomeStackBounds.offset(pos[0], pos[1]);
268 
269         if (target != null) {
270             updateSourceStack(target);
271         } else  if (rv.shouldUseMultiWindowTaskSizeStrategy()) {
272             updateStackBoundsToMultiWindowTaskSize(activity);
273         } else {
274             mSourceStackBounds.set(mHomeStackBounds);
275             Rect fallback = dl.getInsets();
276             mSourceInsets.set(ttv.getInsets(fallback));
277         }
278 
279         Rect targetRect = new Rect();
280         dl.getDescendantRectRelativeToSelf(ttv, targetRect);
281         updateTargetRect(targetRect);
282 
283         if (target == null) {
284             // Transform the clip relative to the target rect. Only do this in the case where we
285             // aren't applying the insets to the app windows (where the clip should be in target app
286             // space)
287             float scale = mTargetRect.width() / mSourceRect.width();
288             mSourceWindowClipInsets.left = mSourceWindowClipInsets.left * scale;
289             mSourceWindowClipInsets.top = mSourceWindowClipInsets.top * scale;
290             mSourceWindowClipInsets.right = mSourceWindowClipInsets.right * scale;
291             mSourceWindowClipInsets.bottom = mSourceWindowClipInsets.bottom * scale;
292         }
293     }
294 
295     /**
296      * Compute scale and translation y such that the specified task view fills the screen.
297      */
updateForFullscreenOverview(TaskView v)298     public ClipAnimationHelper updateForFullscreenOverview(TaskView v) {
299         TaskThumbnailView thumbnailView = v.getThumbnail();
300         RecentsView recentsView = v.getRecentsView();
301         fromTaskThumbnailView(thumbnailView, recentsView);
302         Rect taskSize = new Rect();
303         recentsView.getTaskSize(taskSize);
304         updateTargetRect(taskSize);
305         return this;
306     }
307 
308     /**
309      * @return The source rect's scale and translation relative to the target rect.
310      */
getScaleAndTranslation()311     public LauncherState.ScaleAndTranslation getScaleAndTranslation() {
312         float scale = mSourceRect.width() / mTargetRect.width();
313         float translationY = mSourceRect.centerY() - mSourceRect.top - mTargetRect.centerY();
314         return new LauncherState.ScaleAndTranslation(scale, 0, translationY);
315     }
316 
updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity)317     private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) {
318         ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
319         if (sysUiProxy != null) {
320             try {
321                 mSourceStackBounds.set(sysUiProxy.getNonMinimizedSplitScreenSecondaryBounds());
322                 return;
323             } catch (RemoteException e) {
324                 // Use half screen size
325             }
326         }
327 
328         // Assume that the task size is half screen size (minus the insets and the divider size)
329         DeviceProfile fullDp = activity.getDeviceProfile().getFullScreenProfile();
330         // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
331         // account for system insets
332         int taskWidth = fullDp.availableWidthPx;
333         int taskHeight = fullDp.availableHeightPx;
334         int halfDividerSize = activity.getResources()
335                 .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
336 
337         Rect insets = new Rect();
338         WindowManagerWrapper.getInstance().getStableInsets(insets);
339         if (fullDp.isLandscape) {
340             taskWidth = taskWidth / 2 - halfDividerSize;
341         } else {
342             taskHeight = taskHeight / 2 - halfDividerSize;
343         }
344 
345         // Align the task to bottom left/right edge (closer to nav bar).
346         int left = activity.getDeviceProfile().isSeascape() ? insets.left
347                 : (insets.left + fullDp.availableWidthPx - taskWidth);
348         mSourceStackBounds.set(0, 0, taskWidth, taskHeight);
349         mSourceStackBounds.offset(left, insets.top + fullDp.availableHeightPx - taskHeight);
350     }
351 
getTargetRect()352     public RectF getTargetRect() {
353         return mTargetRect;
354     }
355 
getCurrentCornerRadius()356     public float getCurrentCornerRadius() {
357         return mCurrentCornerRadius;
358     }
359 
360     public static class TransformParams {
361         float progress;
362         public float offsetX;
363         public float offsetScale;
364         @Nullable RectF currentRect;
365         float targetAlpha;
366         boolean forLiveTile;
367         float cornerRadius;
368 
369         SyncRtSurfaceTransactionApplierCompat syncTransactionApplier;
370 
TransformParams()371         public TransformParams() {
372             progress = 0;
373             offsetX = 0;
374             offsetScale = 1;
375             currentRect = null;
376             targetAlpha = 0;
377             forLiveTile = false;
378             cornerRadius = -1;
379         }
380 
setProgress(float progress)381         public TransformParams setProgress(float progress) {
382             this.progress = progress;
383             this.currentRect = null;
384             return this;
385         }
386 
setCornerRadius(float cornerRadius)387         public TransformParams setCornerRadius(float cornerRadius) {
388             this.cornerRadius = cornerRadius;
389             return this;
390         }
391 
setCurrentRectAndTargetAlpha(RectF currentRect, float targetAlpha)392         public TransformParams setCurrentRectAndTargetAlpha(RectF currentRect, float targetAlpha) {
393             this.currentRect = currentRect;
394             this.targetAlpha = targetAlpha;
395             return this;
396         }
397 
setOffsetX(float offsetX)398         public TransformParams setOffsetX(float offsetX) {
399             this.offsetX = offsetX;
400             return this;
401         }
402 
setOffsetScale(float offsetScale)403         public TransformParams setOffsetScale(float offsetScale) {
404             this.offsetScale = offsetScale;
405             return this;
406         }
407 
setForLiveTile(boolean forLiveTile)408         public TransformParams setForLiveTile(boolean forLiveTile) {
409             this.forLiveTile = forLiveTile;
410             return this;
411         }
412 
setSyncTransactionApplier( SyncRtSurfaceTransactionApplierCompat applier)413         public TransformParams setSyncTransactionApplier(
414                 SyncRtSurfaceTransactionApplierCompat applier) {
415             this.syncTransactionApplier = applier;
416             return this;
417         }
418     }
419 }
420 
421