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.states.RotationHelper.deltaRotation;
19 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
20 import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
21 import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
22 
23 import android.animation.TimeInterpolator;
24 import android.content.Context;
25 import android.content.res.Configuration;
26 import android.graphics.Matrix;
27 import android.graphics.Point;
28 import android.graphics.PointF;
29 import android.graphics.Rect;
30 import android.graphics.RectF;
31 import android.util.IntProperty;
32 
33 import com.android.launcher3.DeviceProfile;
34 import com.android.launcher3.R;
35 import com.android.launcher3.Utilities;
36 import com.android.launcher3.anim.PendingAnimation;
37 import com.android.launcher3.touch.PagedOrientationHandler;
38 import com.android.quickstep.AnimatedFloat;
39 import com.android.quickstep.BaseActivityInterface;
40 import com.android.quickstep.views.RecentsView.ScrollState;
41 import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
42 import com.android.quickstep.views.TaskView;
43 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
44 import com.android.systemui.shared.recents.model.ThumbnailData;
45 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
46 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
47 
48 /**
49  * A utility class which emulates the layout behavior of TaskView and RecentsView
50  */
51 public class TaskViewSimulator implements TransformParams.BuilderProxy {
52 
53     public static final IntProperty<TaskViewSimulator> SCROLL =
54             new IntProperty<TaskViewSimulator>("scroll") {
55         @Override
56         public void setValue(TaskViewSimulator simulator, int i) {
57             simulator.setScroll(i);
58         }
59 
60         @Override
61         public Integer get(TaskViewSimulator simulator) {
62             return simulator.mScrollState.scroll;
63         }
64     };
65 
66     private final Rect mTmpCropRect = new Rect();
67     private final RectF mTempRectF = new RectF();
68     private final float[] mTempPoint = new float[2];
69 
70     private final RecentsOrientedState mOrientationState;
71     private final Context mContext;
72     private final BaseActivityInterface mSizeStrategy;
73 
74     private final Rect mTaskRect = new Rect();
75     private final PointF mPivot = new PointF();
76     private DeviceProfile mDp;
77 
78     private final Matrix mMatrix = new Matrix();
79     private final Point mRunningTargetWindowPosition = new Point();
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     private float mCurveScale = 1;
90 
91     // RecentsView properties
92     public final AnimatedFloat recentsViewScale = new AnimatedFloat();
93     public final AnimatedFloat fullScreenProgress = new AnimatedFloat();
94     private final ScrollState mScrollState = new ScrollState();
95     private final int mPageSpacing;
96 
97     // Cached calculations
98     private boolean mLayoutValid = false;
99     private boolean mScrollValid = false;
100 
TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy)101     public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
102         mContext = context;
103         mSizeStrategy = sizeStrategy;
104 
105         mOrientationState = new RecentsOrientedState(context, sizeStrategy, i -> { });
106         mOrientationState.setGestureActive(true);
107 
108         mCurrentFullscreenParams = new FullscreenDrawParams(context);
109         mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
110     }
111 
112     /**
113      * Sets the device profile for the current state
114      */
setDp(DeviceProfile dp)115     public void setDp(DeviceProfile dp) {
116         mDp = dp;
117         mOrientationState.setMultiWindowMode(mDp.isMultiWindowMode);
118         mLayoutValid = false;
119     }
120 
121     /**
122      * @see com.android.quickstep.views.RecentsView#setLayoutRotation(int, int)
123      */
setLayoutRotation(int touchRotation, int displayRotation)124     public void setLayoutRotation(int touchRotation, int displayRotation) {
125         mOrientationState.update(touchRotation, displayRotation);
126         mLayoutValid = false;
127     }
128 
129     /**
130      * @see com.android.quickstep.views.RecentsView#onConfigurationChanged(Configuration)
131      */
setRecentsConfiguration(Configuration configuration)132     public void setRecentsConfiguration(Configuration configuration) {
133         mOrientationState.setActivityConfiguration(configuration);
134         mLayoutValid = false;
135     }
136 
137     /**
138      * @see com.android.quickstep.views.RecentsView#FULLSCREEN_PROGRESS
139      */
getFullScreenScale()140     public float getFullScreenScale() {
141         if (mDp == null) {
142             return 1;
143         }
144         mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect,
145                 mOrientationState.getOrientationHandler());
146         return mOrientationState.getFullScreenScaleAndPivot(mTaskRect, mDp, mPivot);
147     }
148 
149     /**
150      * Sets the targets which the simulator will control
151      */
setPreview(RemoteAnimationTargetCompat runningTarget)152     public void setPreview(RemoteAnimationTargetCompat runningTarget) {
153         setPreviewBounds(runningTarget.screenSpaceBounds, runningTarget.contentInsets);
154         mRunningTargetWindowPosition.set(runningTarget.screenSpaceBounds.left,
155                 runningTarget.screenSpaceBounds.top);
156     }
157 
158     /**
159      * Sets the targets which the simulator will control
160      */
setPreviewBounds(Rect bounds, Rect insets)161     public void setPreviewBounds(Rect bounds, Rect insets) {
162         mThumbnailData.insets.set(insets);
163         // TODO: What is this?
164         mThumbnailData.windowingMode = WINDOWING_MODE_FULLSCREEN;
165 
166         mThumbnailPosition.set(bounds);
167         mLayoutValid = false;
168     }
169 
170     /**
171      * Updates the scroll for RecentsView
172      */
setScroll(int scroll)173     public void setScroll(int scroll) {
174         if (mScrollState.scroll != scroll) {
175             mScrollState.scroll = scroll;
176             mScrollValid = false;
177         }
178     }
179 
180     /**
181      * Adds animation for all the components corresponding to transition from an app to overview
182      */
addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator)183     public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) {
184         pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator);
185         pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1, interpolator);
186     }
187 
188     /**
189      * Returns the current clipped/visible window bounds in the window coordinate space
190      */
getCurrentCropRect()191     public RectF getCurrentCropRect() {
192         // Crop rect is the inverse of thumbnail matrix
193         RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
194         mTempRectF.set(-insets.left, -insets.top,
195                 mTaskRect.width() + insets.right, mTaskRect.height() + insets.bottom);
196         mInversePositionMatrix.mapRect(mTempRectF);
197         return mTempRectF;
198     }
199 
getOrientationState()200     public RecentsOrientedState getOrientationState() {
201         return mOrientationState;
202     }
203 
204     /**
205      * Returns the current transform applied to the window
206      */
getCurrentMatrix()207     public Matrix getCurrentMatrix() {
208         return mMatrix;
209     }
210 
211     /**
212      * Applies the rotation on the matrix to so that it maps from launcher coordinate space to
213      * window coordinate space.
214      */
applyWindowToHomeRotation(Matrix matrix)215     public void applyWindowToHomeRotation(Matrix matrix) {
216         mMatrix.postTranslate(mDp.windowX, mDp.windowY);
217         postDisplayRotation(deltaRotation(
218                 mOrientationState.getRecentsActivityRotation(),
219                 mOrientationState.getDisplayRotation()),
220                 mDp.widthPx, mDp.heightPx, matrix);
221         matrix.postTranslate(-mRunningTargetWindowPosition.x, -mRunningTargetWindowPosition.y);
222     }
223 
224     /**
225      * Applies the target to the previously set parameters
226      */
apply(TransformParams params)227     public void apply(TransformParams params) {
228         if (mDp == null || mThumbnailPosition.isEmpty()) {
229             return;
230         }
231         if (!mLayoutValid) {
232             mLayoutValid = true;
233 
234             getFullScreenScale();
235             mThumbnailData.rotation = mOrientationState.getDisplayRotation();
236 
237             mPositionHelper.updateThumbnailMatrix(
238                     mThumbnailPosition, mThumbnailData,
239                     mTaskRect.width(), mTaskRect.height(),
240                     mDp, mOrientationState.getRecentsActivityRotation());
241             mPositionHelper.getMatrix().invert(mInversePositionMatrix);
242 
243             PagedOrientationHandler poh = mOrientationState.getOrientationHandler();
244             mScrollState.halfPageSize =
245                     poh.getPrimaryValue(mTaskRect.width(), mTaskRect.height()) / 2;
246             mScrollState.halfScreenSize = poh.getPrimaryValue(mDp.widthPx, mDp.heightPx) / 2;
247             mScrollValid = false;
248         }
249 
250         if (!mScrollValid) {
251             mScrollValid = true;
252             int start = mOrientationState.getOrientationHandler()
253                     .getPrimaryValue(mTaskRect.left, mTaskRect.top);
254             mScrollState.screenCenter = start + mScrollState.scroll + mScrollState.halfPageSize;
255             mScrollState.updateInterpolation(start, mPageSpacing);
256             mCurveScale = TaskView.getCurveScaleForInterpolation(mScrollState.linearInterpolation);
257         }
258 
259         float progress = Utilities.boundToRange(fullScreenProgress.value, 0, 1);
260         mCurrentFullscreenParams.setProgress(
261                 progress, recentsViewScale.value, mTaskRect.width(), mDp, mPositionHelper);
262 
263         // Apply thumbnail matrix
264         RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
265         float scale = mCurrentFullscreenParams.mScale;
266         float taskWidth = mTaskRect.width();
267         float taskHeight = mTaskRect.height();
268 
269         mMatrix.set(mPositionHelper.getMatrix());
270         mMatrix.postTranslate(insets.left, insets.top);
271         mMatrix.postScale(scale, scale);
272 
273         // Apply TaskView matrix: translate, scale, scroll
274         mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
275         mMatrix.postScale(mCurveScale, mCurveScale, taskWidth / 2, taskHeight / 2);
276         mOrientationState.getOrientationHandler().set(
277                 mMatrix, MATRIX_POST_TRANSLATE, mScrollState.scroll);
278 
279         // Apply recensView matrix
280         mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);
281         applyWindowToHomeRotation(mMatrix);
282 
283         // Crop rect is the inverse of thumbnail matrix
284         mTempRectF.set(-insets.left, -insets.top,
285                 taskWidth + insets.right, taskHeight + insets.bottom);
286         mInversePositionMatrix.mapRect(mTempRectF);
287         mTempRectF.roundOut(mTmpCropRect);
288 
289         params.applySurfaceParams(params.createSurfaceParams(this));
290     }
291 
292     @Override
onBuildTargetParams( Builder builder, RemoteAnimationTargetCompat app, TransformParams params)293     public void onBuildTargetParams(
294             Builder builder, RemoteAnimationTargetCompat app, TransformParams params) {
295         builder.withMatrix(mMatrix)
296                 .withWindowCrop(mTmpCropRect)
297                 .withCornerRadius(getCurrentCornerRadius());
298     }
299 
300     /**
301      * Returns the corner radius that should be applied to the target so that it matches the
302      * TaskView
303      */
getCurrentCornerRadius()304     public float getCurrentCornerRadius() {
305         float visibleRadius = mCurrentFullscreenParams.mCurrentDrawnCornerRadius;
306         mTempPoint[0] = visibleRadius;
307         mTempPoint[1] = 0;
308         mInversePositionMatrix.mapVectors(mTempPoint);
309 
310         // Ideally we should use square-root. This is an optimization as one of the dimension is 0.
311         return Math.max(Math.abs(mTempPoint[0]), Math.abs(mTempPoint[1]));
312     }
313 
314 }
315