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