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