• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
19 
20 import static com.android.launcher3.states.RotationHelper.deltaRotation;
21 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
22 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
23 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
24 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
25 import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
26 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
27 import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
28 import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
29 
30 import android.animation.TimeInterpolator;
31 import android.content.Context;
32 import android.content.res.Resources;
33 import android.graphics.Matrix;
34 import android.graphics.PointF;
35 import android.graphics.Rect;
36 import android.graphics.RectF;
37 import android.util.Log;
38 import android.view.RemoteAnimationTarget;
39 
40 import androidx.annotation.NonNull;
41 
42 import com.android.launcher3.DeviceProfile;
43 import com.android.launcher3.Utilities;
44 import com.android.launcher3.anim.AnimatedFloat;
45 import com.android.launcher3.anim.PendingAnimation;
46 import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
47 import com.android.launcher3.util.TraceHelper;
48 import com.android.quickstep.BaseActivityInterface;
49 import com.android.quickstep.TaskAnimationManager;
50 import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
51 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
52 import com.android.systemui.shared.recents.model.ThumbnailData;
53 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
54 
55 /**
56  * A utility class which emulates the layout behavior of TaskView and RecentsView
57  */
58 public class TaskViewSimulator implements TransformParams.BuilderProxy {
59 
60     private static final String TAG = "TaskViewSimulator";
61     private static final boolean DEBUG = false;
62 
63     private final Rect mTmpCropRect = new Rect();
64     private final RectF mTempRectF = new RectF();
65     private final float[] mTempPoint = new float[2];
66 
67     private final Context mContext;
68     private final BaseActivityInterface mSizeStrategy;
69 
70     @NonNull
71     private RecentsOrientedState mOrientationState;
72     private final boolean mIsRecentsRtl;
73 
74     private final Rect mTaskRect = new Rect();
75     private final PointF mPivot = new PointF();
76     private DeviceProfile mDp;
77     @StagePosition
78     private int mStagePosition = STAGE_POSITION_UNDEFINED;
79 
80     private final Matrix mMatrix = new Matrix();
81     private final Matrix mMatrixTmp = new Matrix();
82 
83     // Thumbnail view properties
84     private final Rect mThumbnailPosition = new Rect();
85     private final ThumbnailData mThumbnailData = new ThumbnailData();
86     private final PreviewPositionHelper mPositionHelper = new PreviewPositionHelper();
87     private final Matrix mInversePositionMatrix = new Matrix();
88 
89     // TaskView properties
90     private final FullscreenDrawParams mCurrentFullscreenParams;
91     public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat();
92     public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat();
93 
94     // RecentsView properties
95     public final AnimatedFloat recentsViewScale = new AnimatedFloat();
96     public final AnimatedFloat fullScreenProgress = new AnimatedFloat();
97     public final AnimatedFloat recentsViewSecondaryTranslation = new AnimatedFloat();
98     public final AnimatedFloat recentsViewPrimaryTranslation = new AnimatedFloat();
99     public final AnimatedFloat recentsViewScroll = new AnimatedFloat();
100 
101     // Cached calculations
102     private boolean mLayoutValid = false;
103     private int mOrientationStateId;
104     private SplitBounds mSplitBounds;
105     private Boolean mDrawsBelowRecents = null;
106     private boolean mIsGridTask;
107     private boolean mIsDesktopTask;
108     private int mTaskRectTranslationX;
109     private int mTaskRectTranslationY;
110 
TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy)111     public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
112         mContext = context;
113         mSizeStrategy = sizeStrategy;
114 
115         mOrientationState = TraceHelper.allowIpcs("TaskViewSimulator.init",
116                 () -> new RecentsOrientedState(context, sizeStrategy, i -> { }));
117         mOrientationState.setGestureActive(true);
118         mCurrentFullscreenParams = new FullscreenDrawParams(context);
119         mOrientationStateId = mOrientationState.getStateId();
120         Resources resources = context.getResources();
121         mIsRecentsRtl = mOrientationState.getOrientationHandler().getRecentsRtlSetting(resources);
122     }
123 
124     /**
125      * Sets the device profile for the current state
126      */
setDp(DeviceProfile dp)127     public void setDp(DeviceProfile dp) {
128         mDp = dp;
129         mLayoutValid = false;
130         mOrientationState.setDeviceProfile(dp);
131     }
132 
133     /**
134      * Sets the orientation state used for this animation
135      */
setOrientationState(@onNull RecentsOrientedState orientationState)136     public void setOrientationState(@NonNull RecentsOrientedState orientationState) {
137         mOrientationState = orientationState;
138         mLayoutValid = false;
139     }
140 
141     /**
142      * @see com.android.quickstep.views.RecentsView#FULLSCREEN_PROGRESS
143      */
getFullScreenScale()144     public float getFullScreenScale() {
145         if (mDp == null) {
146             return 1;
147         }
148 
149         if (mIsDesktopTask) {
150             mTaskRect.set(mThumbnailPosition);
151             mPivot.set(mTaskRect.centerX(), mTaskRect.centerY());
152             return 1;
153         }
154 
155         if (mIsGridTask) {
156             mSizeStrategy.calculateGridTaskSize(mContext, mDp, mTaskRect,
157                     mOrientationState.getOrientationHandler());
158         } else {
159             mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect,
160                     mOrientationState.getOrientationHandler());
161         }
162 
163         Rect fullTaskSize;
164         if (mSplitBounds != null) {
165             // The task rect changes according to the staged split task sizes, but recents
166             // fullscreen scale and pivot remains the same since the task fits into the existing
167             // sized task space bounds
168             fullTaskSize = new Rect(mTaskRect);
169             mOrientationState.getOrientationHandler()
170                     .setSplitTaskSwipeRect(mDp, mTaskRect, mSplitBounds, mStagePosition);
171             mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY);
172         } else {
173             fullTaskSize = mTaskRect;
174         }
175         fullTaskSize.offset(mTaskRectTranslationX, mTaskRectTranslationY);
176         return mOrientationState.getFullScreenScaleAndPivot(fullTaskSize, mDp, mPivot);
177     }
178 
179     /**
180      * Sets the targets which the simulator will control
181      */
setPreview(RemoteAnimationTarget runningTarget)182     public void setPreview(RemoteAnimationTarget runningTarget) {
183         setPreviewBounds(
184                 runningTarget.startBounds == null
185                         ? runningTarget.screenSpaceBounds : runningTarget.startBounds,
186                 runningTarget.contentInsets);
187     }
188 
189     /**
190      * Sets the targets which the simulator will control specifically for targets to animate when
191      * in split screen
192      *
193      * @param splitInfo set to {@code null} when not in staged split mode
194      */
setPreview(RemoteAnimationTarget runningTarget, SplitBounds splitInfo)195     public void setPreview(RemoteAnimationTarget runningTarget, SplitBounds splitInfo) {
196         setPreview(runningTarget);
197         mSplitBounds = splitInfo;
198         if (mSplitBounds == null) {
199             mStagePosition = STAGE_POSITION_UNDEFINED;
200             return;
201         }
202         mStagePosition = mThumbnailPosition.equals(splitInfo.leftTopBounds) ?
203                 STAGE_POSITION_TOP_OR_LEFT :
204                 STAGE_POSITION_BOTTOM_OR_RIGHT;
205         mPositionHelper.setSplitBounds(convertSplitBounds(mSplitBounds), mStagePosition);
206     }
207 
208     /**
209      * Sets the targets which the simulator will control
210      */
setPreviewBounds(Rect bounds, Rect insets)211     public void setPreviewBounds(Rect bounds, Rect insets) {
212         mThumbnailData.insets.set(insets);
213         // TODO: What is this?
214         mThumbnailData.windowingMode = WINDOWING_MODE_FULLSCREEN;
215 
216         mThumbnailPosition.set(bounds);
217         mLayoutValid = false;
218     }
219 
220     /**
221      * Updates the scroll for RecentsView
222      */
setScroll(float scroll)223     public void setScroll(float scroll) {
224         recentsViewScroll.value = scroll;
225     }
226 
setDrawsBelowRecents(boolean drawsBelowRecents)227     public void setDrawsBelowRecents(boolean drawsBelowRecents) {
228         mDrawsBelowRecents = drawsBelowRecents;
229     }
230 
231     /**
232      * Sets whether the task is part of overview grid and not being focused.
233      */
setIsGridTask(boolean isGridTask)234     public void setIsGridTask(boolean isGridTask) {
235         mIsGridTask = isGridTask;
236     }
237 
238     /**
239      * Sets whether this task is part of desktop tasks in overview.
240      */
setIsDesktopTask(boolean desktop)241     public void setIsDesktopTask(boolean desktop) {
242         mIsDesktopTask = desktop;
243     }
244 
245     /**
246      * Apply translations on TaskRect's starting location.
247      */
setTaskRectTranslation(int taskRectTranslationX, int taskRectTranslationY)248     public void setTaskRectTranslation(int taskRectTranslationX, int taskRectTranslationY) {
249         mTaskRectTranslationX = taskRectTranslationX;
250         mTaskRectTranslationY = taskRectTranslationY;
251     }
252 
253     /**
254      * Adds animation for all the components corresponding to transition from an app to overview.
255      */
addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator)256     public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) {
257         pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator);
258         pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1, interpolator);
259     }
260 
261     /**
262      * Adds animation for all the components corresponding to transition from overview to the app.
263      */
addOverviewToAppAnim(PendingAnimation pa, TimeInterpolator interpolator)264     public void addOverviewToAppAnim(PendingAnimation pa, TimeInterpolator interpolator) {
265         pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 0, 1, interpolator);
266         pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, 1, getFullScreenScale(), interpolator);
267     }
268 
269     /**
270      * Returns the current clipped/visible window bounds in the window coordinate space
271      */
getCurrentCropRect()272     public RectF getCurrentCropRect() {
273         // Crop rect is the inverse of thumbnail matrix
274         mTempRectF.set(0, 0, mTaskRect.width(), mTaskRect.height());
275         mInversePositionMatrix.mapRect(mTempRectF);
276         return mTempRectF;
277     }
278 
279     /**
280      * Returns the current task bounds in the Launcher coordinate space.
281      */
getCurrentRect()282     public RectF getCurrentRect() {
283         RectF result = getCurrentCropRect();
284         mMatrixTmp.set(mMatrix);
285         preDisplayRotation(mOrientationState.getDisplayRotation(), mDp.widthPx, mDp.heightPx,
286                 mMatrixTmp);
287         mMatrixTmp.mapRect(result);
288         return result;
289     }
290 
getOrientationState()291     public RecentsOrientedState getOrientationState() {
292         return mOrientationState;
293     }
294 
295     /**
296      * Returns the current transform applied to the window
297      */
getCurrentMatrix()298     public Matrix getCurrentMatrix() {
299         return mMatrix;
300     }
301 
302     /**
303      * Applies the rotation on the matrix to so that it maps from launcher coordinate space to
304      * window coordinate space.
305      */
applyWindowToHomeRotation(Matrix matrix)306     public void applyWindowToHomeRotation(Matrix matrix) {
307         matrix.postTranslate(mDp.windowX, mDp.windowY);
308         postDisplayRotation(deltaRotation(
309                 mOrientationState.getRecentsActivityRotation(),
310                 mOrientationState.getDisplayRotation()),
311                 mDp.widthPx, mDp.heightPx, matrix);
312     }
313 
314     /**
315      * Applies the target to the previously set parameters
316      */
apply(TransformParams params)317     public void apply(TransformParams params) {
318         if (mDp == null || mThumbnailPosition.isEmpty()) {
319             return;
320         }
321         if (!mLayoutValid || mOrientationStateId != mOrientationState.getStateId()) {
322             mLayoutValid = true;
323             mOrientationStateId = mOrientationState.getStateId();
324 
325             getFullScreenScale();
326             if (TaskAnimationManager.SHELL_TRANSITIONS_ROTATION) {
327                 // With shell transitions, the display is rotated early so we need to actually use
328                 // the rotation when the gesture starts
329                 mThumbnailData.rotation = mOrientationState.getTouchRotation();
330             } else {
331                 mThumbnailData.rotation = mOrientationState.getDisplayRotation();
332             }
333 
334             // mIsRecentsRtl is the inverse of TaskView RTL.
335             boolean isRtlEnabled = !mIsRecentsRtl;
336             mPositionHelper.updateThumbnailMatrix(
337                     mThumbnailPosition, mThumbnailData, mTaskRect.width(), mTaskRect.height(),
338                     mDp.widthPx, mDp.heightPx, mDp.taskbarHeight, mDp.isTablet,
339                     mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
340             mPositionHelper.getMatrix().invert(mInversePositionMatrix);
341             if (DEBUG) {
342                 Log.d(TAG, " taskRect: " + mTaskRect);
343             }
344         }
345 
346         float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
347         mCurrentFullscreenParams.setProgress(fullScreenProgress, recentsViewScale.value,
348                 /* taskViewScale= */1f, mTaskRect.width(), mDp, mPositionHelper);
349 
350         // Apply thumbnail matrix
351         float taskWidth = mTaskRect.width();
352         float taskHeight = mTaskRect.height();
353 
354         mMatrix.set(mPositionHelper.getMatrix());
355 
356         // Apply TaskView matrix: taskRect, translate
357         mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
358         mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE,
359                 taskPrimaryTranslation.value);
360         mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
361                 taskSecondaryTranslation.value);
362         mOrientationState.getOrientationHandler().setPrimary(
363                 mMatrix, MATRIX_POST_TRANSLATE, recentsViewScroll.value);
364 
365         // Apply RecentsView matrix
366         mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);
367         mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
368                 recentsViewSecondaryTranslation.value);
369         mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE,
370                 recentsViewPrimaryTranslation.value);
371         applyWindowToHomeRotation(mMatrix);
372 
373         // Crop rect is the inverse of thumbnail matrix
374         mTempRectF.set(0, 0, taskWidth, taskHeight);
375         mInversePositionMatrix.mapRect(mTempRectF);
376         mTempRectF.roundOut(mTmpCropRect);
377 
378         params.setProgress(1f - fullScreenProgress);
379         params.applySurfaceParams(params.createSurfaceParams(this));
380 
381         if (!DEBUG) {
382             return;
383         }
384         Log.d(TAG, "progress: " + fullScreenProgress
385                 + " recentsViewScale: " + recentsViewScale.value
386                 + " crop: " + mTmpCropRect
387                 + " radius: " + getCurrentCornerRadius()
388                 + " taskW: " + taskWidth + " H: " + taskHeight
389                 + " taskRect: " + mTaskRect
390                 + " taskPrimaryT: " + taskPrimaryTranslation.value
391                 + " recentsPrimaryT: " + recentsViewPrimaryTranslation.value
392                 + " recentsSecondaryT: " + recentsViewSecondaryTranslation.value
393                 + " taskSecondaryT: " + taskSecondaryTranslation.value
394                 + " recentsScroll: " + recentsViewScroll.value
395                 + " pivot: " + mPivot
396         );
397     }
398 
399     @Override
onBuildTargetParams( SurfaceProperties builder, RemoteAnimationTarget app, TransformParams params)400     public void onBuildTargetParams(
401             SurfaceProperties builder, RemoteAnimationTarget app, TransformParams params) {
402         builder.setMatrix(mMatrix)
403                 .setWindowCrop(mTmpCropRect)
404                 .setCornerRadius(getCurrentCornerRadius());
405 
406         // If mDrawsBelowRecents is unset, no reordering will be enforced.
407         if (mDrawsBelowRecents != null) {
408             // In legacy transitions, the animation leashes remain in same hierarchy in the
409             // TaskDisplayArea, so we don't want to bump the layer too high otherwise it will
410             // conflict with layers that WM core positions (ie. the input consumers).  For shell
411             // transitions, the animation leashes are reparented to an animation container so we
412             // can bump layers as needed.
413             builder.setLayer(mDrawsBelowRecents
414                     ? Integer.MIN_VALUE + app.prefixOrderIndex
415                     : ENABLE_SHELL_TRANSITIONS ? Integer.MAX_VALUE : 0);
416         }
417     }
418 
419     /**
420      * Returns the corner radius that should be applied to the target so that it matches the
421      * TaskView
422      */
getCurrentCornerRadius()423     public float getCurrentCornerRadius() {
424         float visibleRadius = mCurrentFullscreenParams.mCurrentDrawnCornerRadius;
425         mTempPoint[0] = visibleRadius;
426         mTempPoint[1] = 0;
427         mInversePositionMatrix.mapVectors(mTempPoint);
428 
429         // Ideally we should use square-root. This is an optimization as one of the dimension is 0.
430         return Math.max(Math.abs(mTempPoint[0]), Math.abs(mTempPoint[1]));
431     }
432 
433     /**
434      * TODO(b/254378592): Remove this after consolidation of classes
435      */
convertSplitBounds(SplitBounds bounds)436     public static com.android.wm.shell.util.SplitBounds convertSplitBounds(SplitBounds bounds) {
437         return new com.android.wm.shell.util.SplitBounds(
438                 bounds.leftTopBounds,
439                 bounds.rightBottomBounds,
440                 bounds.leftTopTaskId,
441                 bounds.rightBottomTaskId
442         );
443     }
444 }
445