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