1 /* 2 * Copyright (C) 2018 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.systemui.shared.system.QuickStepContract.getWindowCornerRadius; 20 import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows; 21 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; 22 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; 23 24 import android.annotation.TargetApi; 25 import android.content.Context; 26 import android.graphics.Matrix; 27 import android.graphics.Matrix.ScaleToFit; 28 import android.graphics.Rect; 29 import android.graphics.RectF; 30 import android.os.Build; 31 import android.os.RemoteException; 32 33 import androidx.annotation.Nullable; 34 35 import com.android.launcher3.BaseDraggingActivity; 36 import com.android.launcher3.DeviceProfile; 37 import com.android.launcher3.LauncherState; 38 import com.android.launcher3.R; 39 import com.android.launcher3.Utilities; 40 import com.android.launcher3.anim.Interpolators; 41 import com.android.launcher3.views.BaseDragLayer; 42 import com.android.quickstep.RecentsModel; 43 import com.android.quickstep.views.RecentsView; 44 import com.android.quickstep.views.TaskThumbnailView; 45 import com.android.quickstep.views.TaskView; 46 import com.android.systemui.shared.recents.ISystemUiProxy; 47 import com.android.systemui.shared.recents.utilities.RectFEvaluator; 48 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 49 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; 50 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; 51 import com.android.systemui.shared.system.TransactionCompat; 52 import com.android.systemui.shared.system.WindowManagerWrapper; 53 54 /** 55 * Utility class to handle window clip animation 56 */ 57 @TargetApi(Build.VERSION_CODES.P) 58 public class ClipAnimationHelper { 59 60 // The bounds of the source app in device coordinates 61 private final Rect mSourceStackBounds = new Rect(); 62 // The insets of the source app 63 private final Rect mSourceInsets = new Rect(); 64 // The source app bounds with the source insets applied, in the source app window coordinates 65 private final RectF mSourceRect = new RectF(); 66 // The bounds of the task view in launcher window coordinates 67 private final RectF mTargetRect = new RectF(); 68 // The insets to be used for clipping the app window, which can be larger than mSourceInsets 69 // if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In 70 // app window coordinates. 71 private final RectF mSourceWindowClipInsets = new RectF(); 72 // The insets to be used for clipping the app window. For live tile, we don't transform the clip 73 // relative to the target rect. 74 private final RectF mSourceWindowClipInsetsForLiveTile = new RectF(); 75 76 // The bounds of launcher (not including insets) in device coordinates 77 public final Rect mHomeStackBounds = new Rect(); 78 79 // The clip rect in source app window coordinates 80 private final RectF mClipRectF = new RectF(); 81 private final RectFEvaluator mRectFEvaluator = new RectFEvaluator(); 82 private final Matrix mTmpMatrix = new Matrix(); 83 private final Rect mTmpRect = new Rect(); 84 private final RectF mTmpRectF = new RectF(); 85 private final RectF mCurrentRectWithInsets = new RectF(); 86 // Corner radius of windows, in pixels 87 private final float mWindowCornerRadius; 88 // Corner radius of windows when they're in overview mode. 89 private final float mTaskCornerRadius; 90 // If windows can have real time rounded corners. 91 private final boolean mSupportsRoundedCornersOnWindows; 92 // Whether or not to actually use the rounded cornders on windows 93 private boolean mUseRoundedCornersOnWindows; 94 95 // Corner radius currently applied to transformed window. 96 private float mCurrentCornerRadius; 97 98 // Whether to boost the opening animation target layers, or the closing 99 private int mBoostModeTargetLayers = -1; 100 101 private TargetAlphaProvider mTaskAlphaCallback = (t, a) -> a; 102 private TargetAlphaProvider mBaseAlphaCallback = (t, a) -> 1; 103 ClipAnimationHelper(Context context)104 public ClipAnimationHelper(Context context) { 105 mWindowCornerRadius = getWindowCornerRadius(context.getResources()); 106 mSupportsRoundedCornersOnWindows = supportsRoundedCornersOnWindows(context.getResources()); 107 mTaskCornerRadius = TaskCornerRadius.get(context); 108 mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows; 109 } 110 updateSourceStack(RemoteAnimationTargetCompat target)111 private void updateSourceStack(RemoteAnimationTargetCompat target) { 112 mSourceInsets.set(target.contentInsets); 113 mSourceStackBounds.set(target.sourceContainerBounds); 114 115 // TODO: Should sourceContainerBounds already have this offset? 116 mSourceStackBounds.offsetTo(target.position.x, target.position.y); 117 118 } 119 updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target)120 public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) { 121 updateSourceStack(target); 122 updateHomeBounds(homeStackBounds); 123 } 124 updateHomeBounds(Rect homeStackBounds)125 public void updateHomeBounds(Rect homeStackBounds) { 126 mHomeStackBounds.set(homeStackBounds); 127 } 128 updateTargetRect(Rect targetRect)129 public void updateTargetRect(Rect targetRect) { 130 mSourceRect.set(mSourceInsets.left, mSourceInsets.top, 131 mSourceStackBounds.width() - mSourceInsets.right, 132 mSourceStackBounds.height() - mSourceInsets.bottom); 133 mTargetRect.set(targetRect); 134 mTargetRect.offset(mHomeStackBounds.left - mSourceStackBounds.left, 135 mHomeStackBounds.top - mSourceStackBounds.top); 136 137 // Calculate the clip based on the target rect (since the content insets and the 138 // launcher insets may differ, so the aspect ratio of the target rect can differ 139 // from the source rect. The difference between the target rect (scaled to the 140 // source rect) is the amount to clip on each edge. 141 RectF scaledTargetRect = new RectF(mTargetRect); 142 Utilities.scaleRectFAboutCenter(scaledTargetRect, 143 mSourceRect.width() / mTargetRect.width()); 144 scaledTargetRect.offsetTo(mSourceRect.left, mSourceRect.top); 145 mSourceWindowClipInsets.set( 146 Math.max(scaledTargetRect.left, 0), 147 Math.max(scaledTargetRect.top, 0), 148 Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0), 149 Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0)); 150 mSourceWindowClipInsetsForLiveTile.set(mSourceWindowClipInsets); 151 mSourceRect.set(scaledTargetRect); 152 } 153 prepareAnimation(DeviceProfile dp, boolean isOpening)154 public void prepareAnimation(DeviceProfile dp, boolean isOpening) { 155 mBoostModeTargetLayers = isOpening ? MODE_OPENING : MODE_CLOSING; 156 mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows && !dp.isMultiWindowMode; 157 } 158 applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params)159 public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params) { 160 return applyTransform(targetSet, params, true /* launcherOnTop */); 161 } 162 applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params, boolean launcherOnTop)163 public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params, 164 boolean launcherOnTop) { 165 float progress = params.progress; 166 if (params.currentRect == null) { 167 RectF currentRect; 168 mTmpRectF.set(mTargetRect); 169 Utilities.scaleRectFAboutCenter(mTmpRectF, params.offsetScale); 170 currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF); 171 currentRect.offset(params.offsetX, 0); 172 173 // Don't clip past progress > 1. 174 progress = Math.min(1, progress); 175 final RectF sourceWindowClipInsets = params.forLiveTile 176 ? mSourceWindowClipInsetsForLiveTile : mSourceWindowClipInsets; 177 mClipRectF.left = sourceWindowClipInsets.left * progress; 178 mClipRectF.top = sourceWindowClipInsets.top * progress; 179 mClipRectF.right = 180 mSourceStackBounds.width() - (sourceWindowClipInsets.right * progress); 181 mClipRectF.bottom = 182 mSourceStackBounds.height() - (sourceWindowClipInsets.bottom * progress); 183 params.setCurrentRectAndTargetAlpha(currentRect, 1); 184 } 185 186 SurfaceParams[] surfaceParams = new SurfaceParams[targetSet.unfilteredApps.length]; 187 for (int i = 0; i < targetSet.unfilteredApps.length; i++) { 188 RemoteAnimationTargetCompat app = targetSet.unfilteredApps[i]; 189 mTmpMatrix.setTranslate(app.position.x, app.position.y); 190 Rect crop = mTmpRect; 191 crop.set(app.sourceContainerBounds); 192 crop.offsetTo(0, 0); 193 float alpha; 194 int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers); 195 float cornerRadius = 0f; 196 float scale = Math.max(params.currentRect.width(), mTargetRect.width()) / crop.width(); 197 if (app.mode == targetSet.targetMode) { 198 alpha = mTaskAlphaCallback.getAlpha(app, params.targetAlpha); 199 if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) { 200 mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL); 201 mTmpMatrix.postTranslate(app.position.x, app.position.y); 202 mClipRectF.roundOut(crop); 203 if (mSupportsRoundedCornersOnWindows) { 204 if (params.cornerRadius > -1) { 205 cornerRadius = params.cornerRadius; 206 scale = params.currentRect.width() / crop.width(); 207 } else { 208 float windowCornerRadius = mUseRoundedCornersOnWindows 209 ? mWindowCornerRadius : 0; 210 cornerRadius = Utilities.mapRange(progress, windowCornerRadius, 211 mTaskCornerRadius); 212 } 213 mCurrentCornerRadius = cornerRadius; 214 } 215 // Fade out Assistant overlay. 216 if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT 217 && app.isNotInRecents) { 218 alpha = 1 - Interpolators.DEACCEL_2_5.getInterpolation(progress); 219 } 220 } else if (targetSet.hasRecents) { 221 // If home has a different target then recents, reverse anim the 222 // home target. 223 alpha = 1 - (progress * params.targetAlpha); 224 } 225 } else { 226 alpha = mBaseAlphaCallback.getAlpha(app, progress); 227 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && launcherOnTop) { 228 crop = null; 229 layer = Integer.MAX_VALUE; 230 } 231 } 232 233 // Since radius is in Surface space, but we draw the rounded corners in screen space, we 234 // have to undo the scale. 235 surfaceParams[i] = new SurfaceParams(app.leash, alpha, mTmpMatrix, crop, layer, 236 cornerRadius / scale); 237 } 238 applySurfaceParams(params.syncTransactionApplier, surfaceParams); 239 return params.currentRect; 240 } 241 getCurrentRectWithInsets()242 public RectF getCurrentRectWithInsets() { 243 mTmpMatrix.mapRect(mCurrentRectWithInsets, mClipRectF); 244 return mCurrentRectWithInsets; 245 } 246 applySurfaceParams(@ullable SyncRtSurfaceTransactionApplierCompat syncTransactionApplier, SurfaceParams[] params)247 private void applySurfaceParams(@Nullable SyncRtSurfaceTransactionApplierCompat 248 syncTransactionApplier, SurfaceParams[] params) { 249 if (syncTransactionApplier != null) { 250 syncTransactionApplier.scheduleApply(params); 251 } else { 252 TransactionCompat t = new TransactionCompat(); 253 for (SurfaceParams param : params) { 254 SyncRtSurfaceTransactionApplierCompat.applyParams(t, param); 255 } 256 t.setEarlyWakeup(); 257 t.apply(); 258 } 259 } 260 setTaskAlphaCallback(TargetAlphaProvider callback)261 public void setTaskAlphaCallback(TargetAlphaProvider callback) { 262 mTaskAlphaCallback = callback; 263 } 264 setBaseAlphaCallback(TargetAlphaProvider callback)265 public void setBaseAlphaCallback(TargetAlphaProvider callback) { 266 mBaseAlphaCallback = callback; 267 } 268 fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv)269 public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv) { 270 fromTaskThumbnailView(ttv, rv, null); 271 } 272 fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv, @Nullable RemoteAnimationTargetCompat target)273 public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv, 274 @Nullable RemoteAnimationTargetCompat target) { 275 BaseDraggingActivity activity = BaseDraggingActivity.fromContext(ttv.getContext()); 276 BaseDragLayer dl = activity.getDragLayer(); 277 278 int[] pos = new int[2]; 279 dl.getLocationOnScreen(pos); 280 mHomeStackBounds.set(0, 0, dl.getWidth(), dl.getHeight()); 281 mHomeStackBounds.offset(pos[0], pos[1]); 282 283 if (target != null) { 284 updateSourceStack(target); 285 } else if (rv.shouldUseMultiWindowTaskSizeStrategy()) { 286 updateStackBoundsToMultiWindowTaskSize(activity); 287 } else { 288 mSourceStackBounds.set(mHomeStackBounds); 289 Rect fallback = dl.getInsets(); 290 mSourceInsets.set(ttv.getInsets(fallback)); 291 } 292 293 Rect targetRect = new Rect(); 294 dl.getDescendantRectRelativeToSelf(ttv, targetRect); 295 updateTargetRect(targetRect); 296 297 if (target == null) { 298 // Transform the clip relative to the target rect. Only do this in the case where we 299 // aren't applying the insets to the app windows (where the clip should be in target app 300 // space) 301 float scale = mTargetRect.width() / mSourceRect.width(); 302 mSourceWindowClipInsets.left = mSourceWindowClipInsets.left * scale; 303 mSourceWindowClipInsets.top = mSourceWindowClipInsets.top * scale; 304 mSourceWindowClipInsets.right = mSourceWindowClipInsets.right * scale; 305 mSourceWindowClipInsets.bottom = mSourceWindowClipInsets.bottom * scale; 306 } 307 } 308 309 /** 310 * Compute scale and translation y such that the specified task view fills the screen. 311 */ updateForFullscreenOverview(TaskView v)312 public ClipAnimationHelper updateForFullscreenOverview(TaskView v) { 313 TaskThumbnailView thumbnailView = v.getThumbnail(); 314 RecentsView recentsView = v.getRecentsView(); 315 fromTaskThumbnailView(thumbnailView, recentsView); 316 Rect taskSize = new Rect(); 317 recentsView.getTaskSize(taskSize); 318 updateTargetRect(taskSize); 319 return this; 320 } 321 322 /** 323 * @return The source rect's scale and translation relative to the target rect. 324 */ getScaleAndTranslation()325 public LauncherState.ScaleAndTranslation getScaleAndTranslation() { 326 float scale = mSourceRect.width() / mTargetRect.width(); 327 float translationY = mSourceRect.centerY() - mSourceRect.top - mTargetRect.centerY(); 328 return new LauncherState.ScaleAndTranslation(scale, 0, translationY); 329 } 330 updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity)331 private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) { 332 ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy(); 333 if (sysUiProxy != null) { 334 try { 335 mSourceStackBounds.set(sysUiProxy.getNonMinimizedSplitScreenSecondaryBounds()); 336 return; 337 } catch (RemoteException e) { 338 // Use half screen size 339 } 340 } 341 342 // Assume that the task size is half screen size (minus the insets and the divider size) 343 DeviceProfile fullDp = activity.getDeviceProfile().getFullScreenProfile(); 344 // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to 345 // account for system insets 346 int taskWidth = fullDp.availableWidthPx; 347 int taskHeight = fullDp.availableHeightPx; 348 int halfDividerSize = activity.getResources() 349 .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2; 350 351 Rect insets = new Rect(); 352 WindowManagerWrapper.getInstance().getStableInsets(insets); 353 if (fullDp.isLandscape) { 354 taskWidth = taskWidth / 2 - halfDividerSize; 355 } else { 356 taskHeight = taskHeight / 2 - halfDividerSize; 357 } 358 359 // Align the task to bottom left/right edge (closer to nav bar). 360 int left = activity.getDeviceProfile().isSeascape() ? insets.left 361 : (insets.left + fullDp.availableWidthPx - taskWidth); 362 mSourceStackBounds.set(0, 0, taskWidth, taskHeight); 363 mSourceStackBounds.offset(left, insets.top + fullDp.availableHeightPx - taskHeight); 364 } 365 getTargetRect()366 public RectF getTargetRect() { 367 return mTargetRect; 368 } 369 getCurrentCornerRadius()370 public float getCurrentCornerRadius() { 371 return mCurrentCornerRadius; 372 } 373 374 public interface TargetAlphaProvider { getAlpha(RemoteAnimationTargetCompat target, float expectedAlpha)375 float getAlpha(RemoteAnimationTargetCompat target, float expectedAlpha); 376 } 377 378 public static class TransformParams { 379 float progress; 380 public float offsetX; 381 public float offsetScale; 382 @Nullable RectF currentRect; 383 float targetAlpha; 384 boolean forLiveTile; 385 float cornerRadius; 386 387 SyncRtSurfaceTransactionApplierCompat syncTransactionApplier; 388 TransformParams()389 public TransformParams() { 390 progress = 0; 391 offsetX = 0; 392 offsetScale = 1; 393 currentRect = null; 394 targetAlpha = 0; 395 forLiveTile = false; 396 cornerRadius = -1; 397 } 398 setProgress(float progress)399 public TransformParams setProgress(float progress) { 400 this.progress = progress; 401 this.currentRect = null; 402 return this; 403 } 404 getProgress()405 public float getProgress() { 406 return progress; 407 } 408 setCornerRadius(float cornerRadius)409 public TransformParams setCornerRadius(float cornerRadius) { 410 this.cornerRadius = cornerRadius; 411 return this; 412 } 413 setCurrentRectAndTargetAlpha(RectF currentRect, float targetAlpha)414 public TransformParams setCurrentRectAndTargetAlpha(RectF currentRect, float targetAlpha) { 415 this.currentRect = currentRect; 416 this.targetAlpha = targetAlpha; 417 return this; 418 } 419 setOffsetX(float offsetX)420 public TransformParams setOffsetX(float offsetX) { 421 this.offsetX = offsetX; 422 return this; 423 } 424 setOffsetScale(float offsetScale)425 public TransformParams setOffsetScale(float offsetScale) { 426 this.offsetScale = offsetScale; 427 return this; 428 } 429 setForLiveTile(boolean forLiveTile)430 public TransformParams setForLiveTile(boolean forLiveTile) { 431 this.forLiveTile = forLiveTile; 432 return this; 433 } 434 setSyncTransactionApplier( SyncRtSurfaceTransactionApplierCompat applier)435 public TransformParams setSyncTransactionApplier( 436 SyncRtSurfaceTransactionApplierCompat applier) { 437 this.syncTransactionApplier = applier; 438 return this; 439 } 440 } 441 } 442 443