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