• 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         // TODO(b/187074722): Don't create this per-TaskViewSimulator
116         mOrientationState = TraceHelper.allowIpcs("",
117                 () -> new RecentsOrientedState(context, sizeStrategy, i -> { }));
118         mOrientationState.setGestureActive(true);
119         mCurrentFullscreenParams = new FullscreenDrawParams(context);
120         mOrientationStateId = mOrientationState.getStateId();
121         Resources resources = context.getResources();
122         mIsRecentsRtl = mOrientationState.getOrientationHandler().getRecentsRtlSetting(resources);
123     }
124 
125     /**
126      * Sets the device profile for the current state
127      */
setDp(DeviceProfile dp)128     public void setDp(DeviceProfile dp) {
129         mDp = dp;
130         mLayoutValid = false;
131         mOrientationState.setDeviceProfile(dp);
132     }
133 
134     /**
135      * Sets the orientation state used for this animation
136      */
setOrientationState(@onNull RecentsOrientedState orientationState)137     public void setOrientationState(@NonNull RecentsOrientedState orientationState) {
138         mOrientationState = orientationState;
139         mLayoutValid = false;
140     }
141 
142     /**
143      * @see com.android.quickstep.views.RecentsView#FULLSCREEN_PROGRESS
144      */
getFullScreenScale()145     public float getFullScreenScale() {
146         if (mDp == null) {
147             return 1;
148         }
149 
150         if (mIsDesktopTask) {
151             mTaskRect.set(mThumbnailPosition);
152             mPivot.set(mTaskRect.centerX(), mTaskRect.centerY());
153             return 1;
154         }
155 
156         if (mIsGridTask) {
157             mSizeStrategy.calculateGridTaskSize(mContext, mDp, mTaskRect,
158                     mOrientationState.getOrientationHandler());
159         } else {
160             mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect,
161                     mOrientationState.getOrientationHandler());
162         }
163 
164         Rect fullTaskSize;
165         if (mSplitBounds != null) {
166             // The task rect changes according to the staged split task sizes, but recents
167             // fullscreen scale and pivot remains the same since the task fits into the existing
168             // sized task space bounds
169             fullTaskSize = new Rect(mTaskRect);
170             mOrientationState.getOrientationHandler()
171                     .setSplitTaskSwipeRect(mDp, mTaskRect, mSplitBounds, mStagePosition);
172             mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY);
173         } else {
174             fullTaskSize = mTaskRect;
175         }
176         fullTaskSize.offset(mTaskRectTranslationX, mTaskRectTranslationY);
177         return mOrientationState.getFullScreenScaleAndPivot(fullTaskSize, mDp, mPivot);
178     }
179 
180     /**
181      * Sets the targets which the simulator will control
182      */
setPreview(RemoteAnimationTarget runningTarget)183     public void setPreview(RemoteAnimationTarget runningTarget) {
184         setPreviewBounds(
185                 runningTarget.startBounds == null
186                         ? runningTarget.screenSpaceBounds : runningTarget.startBounds,
187                 runningTarget.contentInsets);
188     }
189 
190     /**
191      * Sets the targets which the simulator will control specifically for targets to animate when
192      * in split screen
193      *
194      * @param splitInfo set to {@code null} when not in staged split mode
195      */
setPreview(RemoteAnimationTarget runningTarget, SplitBounds splitInfo)196     public void setPreview(RemoteAnimationTarget runningTarget, SplitBounds splitInfo) {
197         setPreview(runningTarget);
198         mSplitBounds = splitInfo;
199         if (mSplitBounds == null) {
200             mStagePosition = STAGE_POSITION_UNDEFINED;
201             return;
202         }
203         mStagePosition = mThumbnailPosition.equals(splitInfo.leftTopBounds) ?
204                 STAGE_POSITION_TOP_OR_LEFT :
205                 STAGE_POSITION_BOTTOM_OR_RIGHT;
206         mPositionHelper.setSplitBounds(convertSplitBounds(mSplitBounds), mStagePosition);
207     }
208 
209     /**
210      * Sets the targets which the simulator will control
211      */
setPreviewBounds(Rect bounds, Rect insets)212     public void setPreviewBounds(Rect bounds, Rect insets) {
213         mThumbnailData.insets.set(insets);
214         // TODO: What is this?
215         mThumbnailData.windowingMode = WINDOWING_MODE_FULLSCREEN;
216 
217         mThumbnailPosition.set(bounds);
218         mLayoutValid = false;
219     }
220 
221     /**
222      * Updates the scroll for RecentsView
223      */
setScroll(float scroll)224     public void setScroll(float scroll) {
225         recentsViewScroll.value = scroll;
226     }
227 
setDrawsBelowRecents(boolean drawsBelowRecents)228     public void setDrawsBelowRecents(boolean drawsBelowRecents) {
229         mDrawsBelowRecents = drawsBelowRecents;
230     }
231 
232     /**
233      * Sets whether the task is part of overview grid and not being focused.
234      */
setIsGridTask(boolean isGridTask)235     public void setIsGridTask(boolean isGridTask) {
236         mIsGridTask = isGridTask;
237     }
238 
239     /**
240      * Sets whether this task is part of desktop tasks in overview.
241      */
setIsDesktopTask(boolean desktop)242     public void setIsDesktopTask(boolean desktop) {
243         mIsDesktopTask = desktop;
244     }
245 
246     /**
247      * Apply translations on TaskRect's starting location.
248      */
setTaskRectTranslation(int taskRectTranslationX, int taskRectTranslationY)249     public void setTaskRectTranslation(int taskRectTranslationX, int taskRectTranslationY) {
250         mTaskRectTranslationX = taskRectTranslationX;
251         mTaskRectTranslationY = taskRectTranslationY;
252     }
253 
254     /**
255      * Adds animation for all the components corresponding to transition from an app to overview.
256      */
addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator)257     public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) {
258         pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator);
259         pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1, interpolator);
260     }
261 
262     /**
263      * Adds animation for all the components corresponding to transition from overview to the app.
264      */
addOverviewToAppAnim(PendingAnimation pa, TimeInterpolator interpolator)265     public void addOverviewToAppAnim(PendingAnimation pa, TimeInterpolator interpolator) {
266         pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 0, 1, interpolator);
267         pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, 1, getFullScreenScale(), interpolator);
268     }
269 
270     /**
271      * Returns the current clipped/visible window bounds in the window coordinate space
272      */
getCurrentCropRect()273     public RectF getCurrentCropRect() {
274         // Crop rect is the inverse of thumbnail matrix
275         mTempRectF.set(0, 0, mTaskRect.width(), mTaskRect.height());
276         mInversePositionMatrix.mapRect(mTempRectF);
277         return mTempRectF;
278     }
279 
280     /**
281      * Returns the current task bounds in the Launcher coordinate space.
282      */
getCurrentRect()283     public RectF getCurrentRect() {
284         RectF result = getCurrentCropRect();
285         mMatrixTmp.set(mMatrix);
286         preDisplayRotation(mOrientationState.getDisplayRotation(), mDp.widthPx, mDp.heightPx,
287                 mMatrixTmp);
288         mMatrixTmp.mapRect(result);
289         return result;
290     }
291 
getOrientationState()292     public RecentsOrientedState getOrientationState() {
293         return mOrientationState;
294     }
295 
296     /**
297      * Returns the current transform applied to the window
298      */
getCurrentMatrix()299     public Matrix getCurrentMatrix() {
300         return mMatrix;
301     }
302 
303     /**
304      * Applies the rotation on the matrix to so that it maps from launcher coordinate space to
305      * window coordinate space.
306      */
applyWindowToHomeRotation(Matrix matrix)307     public void applyWindowToHomeRotation(Matrix matrix) {
308         matrix.postTranslate(mDp.windowX, mDp.windowY);
309         postDisplayRotation(deltaRotation(
310                 mOrientationState.getRecentsActivityRotation(),
311                 mOrientationState.getDisplayRotation()),
312                 mDp.widthPx, mDp.heightPx, matrix);
313     }
314 
315     /**
316      * Applies the target to the previously set parameters
317      */
apply(TransformParams params)318     public void apply(TransformParams params) {
319         if (mDp == null || mThumbnailPosition.isEmpty()) {
320             return;
321         }
322         if (!mLayoutValid || mOrientationStateId != mOrientationState.getStateId()) {
323             mLayoutValid = true;
324             mOrientationStateId = mOrientationState.getStateId();
325 
326             getFullScreenScale();
327             if (TaskAnimationManager.SHELL_TRANSITIONS_ROTATION) {
328                 // With shell transitions, the display is rotated early so we need to actually use
329                 // the rotation when the gesture starts
330                 mThumbnailData.rotation = mOrientationState.getTouchRotation();
331             } else {
332                 mThumbnailData.rotation = mOrientationState.getDisplayRotation();
333             }
334 
335             // mIsRecentsRtl is the inverse of TaskView RTL.
336             boolean isRtlEnabled = !mIsRecentsRtl;
337             mPositionHelper.updateThumbnailMatrix(
338                     mThumbnailPosition, mThumbnailData, mTaskRect.width(), mTaskRect.height(),
339                     mDp.widthPx, mDp.heightPx, mDp.taskbarHeight, mDp.isTablet,
340                     mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
341             mPositionHelper.getMatrix().invert(mInversePositionMatrix);
342             if (DEBUG) {
343                 Log.d(TAG, " taskRect: " + mTaskRect);
344             }
345         }
346 
347         float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
348         mCurrentFullscreenParams.setProgress(fullScreenProgress, recentsViewScale.value,
349                 /* taskViewScale= */1f, mTaskRect.width(), mDp, mPositionHelper);
350 
351         // Apply thumbnail matrix
352         float taskWidth = mTaskRect.width();
353         float taskHeight = mTaskRect.height();
354 
355         mMatrix.set(mPositionHelper.getMatrix());
356 
357         // Apply TaskView matrix: taskRect, translate
358         mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
359         mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE,
360                 taskPrimaryTranslation.value);
361         mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
362                 taskSecondaryTranslation.value);
363         mOrientationState.getOrientationHandler().setPrimary(
364                 mMatrix, MATRIX_POST_TRANSLATE, recentsViewScroll.value);
365 
366         // Apply RecentsView matrix
367         mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);
368         mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
369                 recentsViewSecondaryTranslation.value);
370         mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE,
371                 recentsViewPrimaryTranslation.value);
372         applyWindowToHomeRotation(mMatrix);
373 
374         // Crop rect is the inverse of thumbnail matrix
375         mTempRectF.set(0, 0, taskWidth, taskHeight);
376         mInversePositionMatrix.mapRect(mTempRectF);
377         mTempRectF.roundOut(mTmpCropRect);
378 
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 + 1
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