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 17 package com.android.server.wm; 18 19 import static android.app.ActivityOptions.ANIM_CUSTOM; 20 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; 21 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION; 22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 23 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 24 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 25 import static android.app.WindowConfiguration.ROTATION_UNDEFINED; 26 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 27 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 28 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; 29 import static android.view.Display.DEFAULT_DISPLAY; 30 import static android.view.Display.INVALID_DISPLAY; 31 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; 32 import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; 33 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; 34 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; 35 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; 36 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; 37 import static android.view.WindowManager.TRANSIT_CHANGE; 38 import static android.view.WindowManager.TRANSIT_CLOSE; 39 import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING; 40 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; 41 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; 42 import static android.view.WindowManager.TRANSIT_OPEN; 43 import static android.view.WindowManager.TRANSIT_TO_BACK; 44 import static android.view.WindowManager.TRANSIT_TO_FRONT; 45 import static android.view.WindowManager.TransitionFlags; 46 import static android.view.WindowManager.TransitionType; 47 import static android.view.WindowManager.transitTypeToString; 48 import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR; 49 import static android.window.TransitionInfo.AnimationOptions; 50 import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION; 51 import static android.window.TransitionInfo.FLAG_CONFIG_AT_END; 52 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; 53 import static android.window.TransitionInfo.FLAG_FILLS_TASK; 54 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; 55 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; 56 import static android.window.TransitionInfo.FLAG_IS_DISPLAY; 57 import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD; 58 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; 59 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; 60 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; 61 import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; 62 import static android.window.TransitionInfo.FLAG_NO_ANIMATION; 63 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; 64 import static android.window.TransitionInfo.FLAG_TASK_LAUNCHING_BEHIND; 65 import static android.window.TransitionInfo.FLAG_TRANSLUCENT; 66 import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN; 67 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT; 68 69 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; 70 import static com.android.server.wm.ActivityRecord.State.RESUMED; 71 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM; 72 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN; 73 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN; 74 import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE; 75 import static com.android.server.wm.StartingData.AFTER_TRANSITION_FINISH; 76 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK; 77 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; 78 import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION; 79 import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; 80 81 import android.annotation.ColorInt; 82 import android.annotation.IntDef; 83 import android.annotation.NonNull; 84 import android.annotation.Nullable; 85 import android.app.ActivityManager; 86 import android.app.ActivityOptions; 87 import android.app.IApplicationThread; 88 import android.content.pm.ActivityInfo; 89 import android.graphics.Point; 90 import android.graphics.Rect; 91 import android.hardware.HardwareBuffer; 92 import android.os.Binder; 93 import android.os.Bundle; 94 import android.os.IBinder; 95 import android.os.IRemoteCallback; 96 import android.os.Looper; 97 import android.os.RemoteException; 98 import android.os.SystemClock; 99 import android.os.Trace; 100 import android.util.ArrayMap; 101 import android.util.ArraySet; 102 import android.util.Slog; 103 import android.util.SparseArray; 104 import android.view.Display; 105 import android.view.SurfaceControl; 106 import android.view.WindowManager; 107 import android.window.ScreenCapture; 108 import android.window.TaskFragmentAnimationParams; 109 import android.window.TransitionInfo; 110 import android.window.WindowContainerTransaction; 111 112 import com.android.internal.annotations.VisibleForTesting; 113 import com.android.internal.graphics.ColorUtils; 114 import com.android.internal.policy.TransitionAnimation; 115 import com.android.internal.protolog.ProtoLog; 116 import com.android.internal.protolog.WmProtoLogGroups; 117 import com.android.internal.util.function.pooled.PooledLambda; 118 import com.android.server.inputmethod.InputMethodManagerInternal; 119 import com.android.server.statusbar.StatusBarManagerInternal; 120 import com.android.window.flags.Flags; 121 122 import java.lang.annotation.Retention; 123 import java.lang.annotation.RetentionPolicy; 124 import java.lang.ref.WeakReference; 125 import java.util.ArrayList; 126 import java.util.List; 127 import java.util.Objects; 128 import java.util.function.Predicate; 129 130 /** 131 * Represents a logical transition. This keeps track of all the changes associated with a logical 132 * WM state -> state transition. 133 * @see TransitionController 134 * 135 * In addition to tracking individual container changes, this also tracks ordering-changes (just 136 * on-top for now). However, since order is a "global" property, the mechanics of order-change 137 * detection/reporting is non-trivial when transitions are collecting in parallel. See 138 * {@link #collectOrderChanges} for more details. 139 */ 140 class Transition implements BLASTSyncEngine.TransactionReadyListener { 141 private static final String TAG = "Transition"; 142 private static final String TRACE_NAME_PLAY_TRANSITION = "playing"; 143 144 /** The transition has been created but isn't collecting yet. */ 145 private static final int STATE_PENDING = -1; 146 147 /** The transition has been created and is collecting, but hasn't formally started. */ 148 private static final int STATE_COLLECTING = 0; 149 150 /** 151 * The transition has formally started. It is still collecting but will stop once all 152 * participants are ready to animate (finished drawing). 153 */ 154 private static final int STATE_STARTED = 1; 155 156 /** 157 * This transition is currently playing its animation and can no longer collect or be changed. 158 */ 159 private static final int STATE_PLAYING = 2; 160 161 /** 162 * This transition is aborting or has aborted. No animation will play nor will anything get 163 * sent to the player. 164 */ 165 private static final int STATE_ABORT = 3; 166 167 /** 168 * This transition has finished playing successfully. 169 */ 170 private static final int STATE_FINISHED = 4; 171 172 @IntDef(prefix = { "STATE_" }, value = { 173 STATE_PENDING, 174 STATE_COLLECTING, 175 STATE_STARTED, 176 STATE_PLAYING, 177 STATE_ABORT, 178 STATE_FINISHED 179 }) 180 @Retention(RetentionPolicy.SOURCE) 181 @interface TransitionState {} 182 183 final @TransitionType int mType; 184 private int mSyncId = -1; 185 private @TransitionFlags int mFlags; 186 final TransitionController mController; 187 private final BLASTSyncEngine mSyncEngine; 188 private final Token mToken; 189 190 private @Nullable ActivityRecord mPipActivity; 191 192 /** Only use for clean-up after binder death! */ 193 private SurfaceControl.Transaction mStartTransaction = null; 194 private SurfaceControl.Transaction mFinishTransaction = null; 195 196 /** Used for failsafe clean-up to prevent leaks due to misbehaving player impls. */ 197 private SurfaceControl.Transaction mCleanupTransaction = null; 198 199 /** 200 * Contains change infos for both participants and all remote-animatable ancestors. The 201 * ancestors can be the promotion candidates so their start-states need to be captured. 202 * @see #getAnimatableParent 203 */ 204 final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>(); 205 206 /** The collected participants in the transition. */ 207 final ArraySet<WindowContainer> mParticipants = new ArraySet<>(); 208 209 /** The final animation targets derived from participants after promotion. */ 210 ArrayList<ChangeInfo> mTargets; 211 212 /** The displays that this transition is running on. */ 213 private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>(); 214 215 /** 216 * The (non alwaysOnTop) tasks which were on-top of their display before the transition. If 217 * tasks are nested, all the tasks that are parents of the on-top task are also included. 218 */ 219 private final ArrayList<Task> mOnTopTasksStart = new ArrayList<>(); 220 221 /** 222 * The (non alwaysOnTop) tasks which were on-top of their display when this transition became 223 * ready (via setReady, not animation-ready). 224 */ 225 private final ArrayList<Task> mOnTopTasksAtReady = new ArrayList<>(); 226 227 /** 228 * Tracks the top display like top tasks so we can trigger a MOVED_TO_TOP transition even when 229 * a display gets moved to front but there's no change in per-display focused tasks. 230 */ 231 private DisplayContent mOnTopDisplayStart = null; 232 private DisplayContent mOnTopDisplayAtReady = null; 233 234 /** 235 * Set of participating windowtokens (activity/wallpaper) which are visible at the end of 236 * the transition animation. 237 */ 238 private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>(); 239 240 /** 241 * Map of transient activities (lifecycle initially tied to this transition) to their 242 * restore-below tasks. 243 */ 244 private ArrayMap<ActivityRecord, Task> mTransientLaunches = null; 245 246 /** 247 * The tasks that may be occluded by the transient activity. Assume the task stack is 248 * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), and Home is started in a 249 * transient-launch activity, then A is the restore-below task, and [B, C] are the 250 * transient-hide tasks. 251 */ 252 private ArrayList<Task> mTransientHideTasks; 253 254 @VisibleForTesting 255 ArrayList<Runnable> mTransactionCompletedListeners = null; 256 257 private ArrayList<Runnable> mTransitionEndedListeners = null; 258 259 /** Custom activity-level animation options and callbacks. */ 260 private AnimationOptions mOverrideOptions; 261 262 /** 263 * Custom background color 264 */ 265 @ColorInt 266 private int mOverrideBackgroundColor; 267 268 private IRemoteCallback mClientAnimationStartCallback = null; 269 private IRemoteCallback mClientAnimationFinishCallback = null; 270 271 private @TransitionState int mState = STATE_PENDING; 272 private final ReadyTrackerOld mReadyTrackerOld = new ReadyTrackerOld(); 273 final ReadyTracker mReadyTracker = new ReadyTracker(this); 274 275 private int mRecentsDisplayId = INVALID_DISPLAY; 276 277 /** The delay for light bar appearance animation. */ 278 long mStatusBarTransitionDelay; 279 280 /** @see #setCanPipOnFinish */ 281 private boolean mCanPipOnFinish = true; 282 283 private boolean mIsSeamlessRotation = false; 284 private IContainerFreezer mContainerFreezer = null; 285 286 /** 287 * {@code true} if some other operation may have caused the originally-recorded state (in 288 * mChanges) to be dirty. This is usually due to finishTransition being called mid-collect; 289 * and, the reason that finish can alter the "start" state of other transitions is because 290 * setVisible(false) is deferred until then. 291 * Instead of adding this conditional, we could re-check always; but, this situation isn't 292 * common so it'd be wasted work. 293 */ 294 boolean mPriorVisibilityMightBeDirty = false; 295 296 final TransitionController.Logger mLogger = new TransitionController.Logger(); 297 298 /** Whether this transition was forced to play early (eg for a SLEEP signal). */ 299 private boolean mForcePlaying = false; 300 301 /** 302 * {@code false} if this transition runs purely in WMCore (meaning Shell is completely unaware 303 * of it). Currently, this happens before the display is ready since nothing can be seen yet. 304 */ 305 boolean mIsPlayerEnabled = true; 306 307 /** This transition doesn't run in parallel. */ 308 static final int PARALLEL_TYPE_NONE = 0; 309 310 /** Any 2 transitions of this type can run in parallel with each other. Used for testing. */ 311 static final int PARALLEL_TYPE_MUTUAL = 1; 312 313 /** This is a recents transition. */ 314 static final int PARALLEL_TYPE_RECENTS = 2; 315 316 317 @IntDef(prefix = { "PARALLEL_TYPE_" }, value = { 318 PARALLEL_TYPE_NONE, 319 PARALLEL_TYPE_MUTUAL, 320 PARALLEL_TYPE_RECENTS 321 }) 322 @Retention(RetentionPolicy.SOURCE) 323 @interface ParallelType {} 324 325 /** 326 * What category of parallel-collect support this transition has. The value of this is used 327 * by {@link TransitionController} to determine which transitions can collect in parallel. If 328 * a transition can collect in parallel, it means that it will start collecting as soon as the 329 * prior collecting transition is {@link #isPopulated}. This is a shortcut for supporting 330 * a couple specific situations before we have full-fledged support for parallel transitions. 331 */ 332 @ParallelType int mParallelCollectType = PARALLEL_TYPE_NONE; 333 334 /** 335 * A "Track" is a set of animations which must cooperate with each other to play smoothly. If 336 * animations can play independently of each other, then they can be in different tracks. If 337 * a transition must cooperate with transitions in >1 other track, then it must be marked 338 * FLAG_SYNC and it will end-up flushing all animations before it starts. 339 */ 340 int mAnimationTrack = 0; 341 342 /** 343 * List of activities whose configurations are sent to the client at the end of the transition 344 * instead of immediately when the configuration changes. 345 */ 346 ArrayList<ActivityRecord> mConfigAtEndActivities = null; 347 348 /** The current head of the chain of actions related to this transition. */ 349 ActionChain mChainHead = null; 350 351 @VisibleForTesting Transition(@ransitionType int type, @TransitionFlags int flags, TransitionController controller, BLASTSyncEngine syncEngine)352 Transition(@TransitionType int type, @TransitionFlags int flags, 353 TransitionController controller, BLASTSyncEngine syncEngine) { 354 mType = type; 355 mFlags = flags; 356 mController = controller; 357 mSyncEngine = syncEngine; 358 mToken = new Token(this); 359 360 mLogger.mCreateWallTimeMs = System.currentTimeMillis(); 361 mLogger.mCreateTimeNs = SystemClock.elapsedRealtimeNanos(); 362 } 363 364 @Nullable fromBinder(@ullable IBinder token)365 static Transition fromBinder(@Nullable IBinder token) { 366 if (token == null) return null; 367 try { 368 return ((Token) token).mTransition.get(); 369 } catch (ClassCastException e) { 370 Slog.w(TAG, "Invalid transition token: " + token, e); 371 return null; 372 } 373 } 374 375 @NonNull getToken()376 IBinder getToken() { 377 return mToken; 378 } 379 addFlag(@ransitionFlags int flags)380 void addFlag(@TransitionFlags int flags) { 381 mFlags |= flags; 382 } 383 removeFlag(@ransitionFlags int flags)384 void removeFlag(@TransitionFlags int flags) { 385 mFlags &= ~flags; 386 } 387 calcParallelCollectType(WindowContainerTransaction wct)388 void calcParallelCollectType(WindowContainerTransaction wct) { 389 for (int i = 0; i < wct.getHierarchyOps().size(); ++i) { 390 final WindowContainerTransaction.HierarchyOp hop = wct.getHierarchyOps().get(i); 391 if (hop.getType() != HIERARCHY_OP_TYPE_PENDING_INTENT) continue; 392 final Bundle b = hop.getLaunchOptions(); 393 if (b == null || b.isEmpty()) continue; 394 final boolean transientLaunch = b.getBoolean(ActivityOptions.KEY_TRANSIENT_LAUNCH); 395 if (transientLaunch) { 396 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 397 "Starting a Recents transition which can be parallel."); 398 mParallelCollectType = PARALLEL_TYPE_RECENTS; 399 } 400 } 401 } 402 403 /** Records an activity as transient-launch. This activity must be already collected. */ setTransientLaunch(@onNull ActivityRecord activity, @Nullable Task restoreBelow)404 void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) { 405 if (mTransientLaunches == null) { 406 mTransientLaunches = new ArrayMap<>(); 407 mTransientHideTasks = new ArrayList<>(); 408 } 409 mTransientLaunches.put(activity, restoreBelow); 410 setTransientLaunchToChanges(activity); 411 412 final int restoreBelowTaskId = restoreBelow != null ? restoreBelow.mTaskId : -1; 413 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as " 414 + "transient-launch restoreBelowTaskId=%d", mSyncId, activity, restoreBelowTaskId); 415 416 final Task transientLaunchRootTask = activity.getRootTask(); 417 final WindowContainer<?> parent = restoreBelow != null ? restoreBelow.getParent() 418 : (transientLaunchRootTask != null ? transientLaunchRootTask.getParent() : null); 419 if (parent != null) { 420 // Collect all visible tasks which can be occluded by the transient activity to 421 // make sure they are in the participants so their visibilities can be updated when 422 // finishing transition. 423 // Note: This currently assumes that the parent is a DA containing the full set of 424 // visible tasks 425 parent.forAllTasks(t -> { 426 // Skip transient-launch task 427 if (t == transientLaunchRootTask) return false; 428 if (t.isVisibleRequested() && !t.isAlwaysOnTop()) { 429 if (t.isRootTask()) { 430 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 431 " transient hide: taskId=%d", t.mTaskId); 432 mTransientHideTasks.add(t); 433 } 434 if (t.isLeafTask()) { 435 collect(t); 436 } 437 } 438 return restoreBelow != null 439 // Stop at the restoreBelow task 440 ? t == restoreBelow 441 // Or stop at the last visible task if no restore-below (new task) 442 : (t.isRootTask() && t.fillsParent()); 443 }); 444 // Add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide tasks, 445 // so ChangeInfo#hasChanged() can return true to report the transition info. 446 for (int i = mChanges.size() - 1; i >= 0; --i) { 447 updateTransientFlags(mChanges.valueAt(i)); 448 } 449 } 450 451 // TODO(b/188669821): Remove once legacy recents behavior is moved to shell. 452 // Also interpret HOME transient launch as recents 453 if (activity.isActivityTypeHomeOrRecents()) { 454 addFlag(TRANSIT_FLAG_IS_RECENTS); 455 // When starting recents animation, we assume the recents activity is behind the app 456 // task and should not affect system bar appearance, 457 // until WMS#setRecentsAppBehindSystemBars be called from launcher when passing 458 // the gesture threshold. 459 activity.getTask().setCanAffectSystemUiFlags(false); 460 } 461 } 462 463 /** @return whether `wc` is a descendent of a transient-hide window. */ isInTransientHide(@onNull WindowContainer wc)464 boolean isInTransientHide(@NonNull WindowContainer wc) { 465 if (mTransientHideTasks == null) return false; 466 for (int i = mTransientHideTasks.size() - 1; i >= 0; --i) { 467 final Task task = mTransientHideTasks.get(i); 468 if (wc == task || wc.isDescendantOf(task)) { 469 return true; 470 } 471 } 472 return false; 473 } 474 475 /** 476 * This ensures that all changes for previously transient-hide containers are flagged such that 477 * they will report changes and be included in this transition. 478 */ updateChangesForRestoreTransientHideTasks(Transition transientLaunchTransition)479 void updateChangesForRestoreTransientHideTasks(Transition transientLaunchTransition) { 480 if (transientLaunchTransition.mTransientHideTasks == null) { 481 // Skip if the transient-launch transition has no transient-hide tasks 482 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 483 "Skipping update changes for restore transient hide tasks"); 484 return; 485 } 486 487 // For each change, if it was previously transient-hidden, then we should force a flag to 488 // ensure that it is included in the next transition 489 for (int i = 0; i < mChanges.size(); i++) { 490 final WindowContainer container = mChanges.keyAt(i); 491 if (transientLaunchTransition.isInTransientHide(container)) { 492 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 493 "Force update transient hide task for restore %d: %s", mSyncId, container); 494 final ChangeInfo info = mChanges.valueAt(i); 495 info.mRestoringTransientHide = true; 496 } 497 } 498 } 499 500 /** Returns {@code true} if the task should keep visible if this is a transient transition. */ isTransientVisible(@onNull Task task)501 boolean isTransientVisible(@NonNull Task task) { 502 if (mTransientLaunches == null) return false; 503 504 // Check if all the transient-launch activities are occluded 505 int occludedCount = 0; 506 final int numTransient = mTransientLaunches.size(); 507 for (int i = numTransient - 1; i >= 0; --i) { 508 final Task transientRoot = mTransientLaunches.keyAt(i).getRootTask(); 509 if (transientRoot == null) continue; 510 final WindowContainer<?> rootParent = transientRoot.getParent(); 511 if (rootParent == null || rootParent.getTopChild() == transientRoot) continue; 512 for (int j = rootParent.getChildCount() - 1; j >= 0; --j) { 513 final WindowContainer<?> sibling = rootParent.getChildAt(j); 514 if (sibling == transientRoot) break; 515 if (!sibling.getWindowConfiguration().isAlwaysOnTop() && mController.mAtm 516 .mTaskSupervisor.mOpaqueContainerHelper.isOpaque(sibling)) { 517 occludedCount++; 518 break; 519 } 520 } 521 } 522 if (occludedCount == numTransient) { 523 // Let transient-hide activities pause before transition is finished. 524 return false; 525 } 526 527 // If this task is currently transient-hide, then keep it visible 528 return isInTransientHide(task); 529 } 530 canApplyDim(@onNull Task task)531 boolean canApplyDim(@NonNull Task task) { 532 if (mTransientLaunches == null) return true; 533 if (Flags.useTasksDimOnly()) { 534 if (task.isSuitableForDimming()) { 535 // Always allow to dim if the dimming occurs at task level (dim parented to task) 536 return true; 537 } 538 } else { 539 final Dimmer dimmer = task.getDimmer(); 540 if (dimmer == null) { 541 return false; 542 } 543 if (dimmer.hostIsTask()) { 544 // Always allow to dim if the host only affects its task. 545 return true; 546 } 547 } 548 549 // The dimmer host of a translucent task can be a display, then it is not in transient-hide. 550 for (int i = mTransientLaunches.size() - 1; i >= 0; --i) { 551 // The transient task is usually the task of recents/home activity. 552 final Task transientTask = mTransientLaunches.keyAt(i).getTask(); 553 if (transientTask != null && transientTask.canAffectSystemUiFlags()) { 554 // It usually means that the recents animation has moved the transient-hide task 555 // an noticeable distance, then the display level dimmer should not show. 556 return false; 557 } 558 } 559 return true; 560 } 561 hasTransientLaunch()562 boolean hasTransientLaunch() { 563 return mTransientLaunches != null && !mTransientLaunches.isEmpty(); 564 } 565 isTransientLaunch(@onNull ActivityRecord activity)566 boolean isTransientLaunch(@NonNull ActivityRecord activity) { 567 return mTransientLaunches != null && mTransientLaunches.containsKey(activity); 568 } 569 getTransientLaunchRestoreTarget(@onNull WindowContainer container)570 Task getTransientLaunchRestoreTarget(@NonNull WindowContainer container) { 571 if (mTransientLaunches == null) return null; 572 for (int i = 0; i < mTransientLaunches.size(); ++i) { 573 if (mTransientLaunches.keyAt(i).isDescendantOf(container)) { 574 return mTransientLaunches.valueAt(i); 575 } 576 } 577 return null; 578 } 579 isOnDisplay(@onNull DisplayContent dc)580 boolean isOnDisplay(@NonNull DisplayContent dc) { 581 return mTargetDisplays.contains(dc); 582 } 583 setConfigAtEnd(@onNull WindowContainer<?> wc)584 void setConfigAtEnd(@NonNull WindowContainer<?> wc) { 585 wc.forAllActivities(ar -> { 586 if (!ar.isVisible() || !ar.isVisibleRequested()) return; 587 if (mConfigAtEndActivities == null) { 588 mConfigAtEndActivities = new ArrayList<>(); 589 } else if (mConfigAtEndActivities.contains(ar)) { 590 return; 591 } 592 mConfigAtEndActivities.add(ar); 593 ar.pauseConfigurationDispatch(); 594 collect(ar); 595 mChanges.get(ar).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END; 596 }); 597 } 598 599 /** Set a transition to be a back gesture animation. */ setBackGestureAnimation(@onNull WindowContainer wc, boolean isTop)600 void setBackGestureAnimation(@NonNull WindowContainer wc, boolean isTop) { 601 final ChangeInfo info = mChanges.get(wc); 602 if (info == null) return; 603 info.mFlags = info.mFlags | (isTop ? ChangeInfo.FLAG_BACK_GESTURE_ANIMATION 604 : ChangeInfo.FLAG_BELOW_BACK_GESTURE_ANIMATION); 605 } 606 607 /** Set a transition to be a seamless-rotation. */ setSeamlessRotation(@onNull WindowContainer wc)608 void setSeamlessRotation(@NonNull WindowContainer wc) { 609 final ChangeInfo info = mChanges.get(wc); 610 if (info == null) return; 611 info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION; 612 onSeamlessRotating(wc.getDisplayContent()); 613 } 614 615 /** 616 * Called when it's been determined that this is transition is a seamless rotation. This should 617 * be called before any WM changes have happened. 618 */ onSeamlessRotating(@onNull DisplayContent dc)619 void onSeamlessRotating(@NonNull DisplayContent dc) { 620 // Don't need to do anything special if everything is using BLAST sync already. 621 if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) return; 622 if (mContainerFreezer == null) { 623 mContainerFreezer = new ScreenshotFreezer(); 624 } 625 final WindowState top = dc.getDisplayPolicy().getTopFullscreenOpaqueWindow(); 626 if (top != null) { 627 mIsSeamlessRotation = true; 628 top.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST; 629 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Override sync-method for %s " 630 + "because seamless rotating", top.getName()); 631 } 632 } 633 634 /** 635 * Set the pip-able activity participating in this transition. 636 * @param pipActivity activity about to enter pip 637 */ setPipActivity(@ullable ActivityRecord pipActivity)638 void setPipActivity(@Nullable ActivityRecord pipActivity) { 639 mPipActivity = pipActivity; 640 } 641 642 /** 643 * @return pip-able activity participating in this transition. 644 */ getPipActivity()645 @Nullable ActivityRecord getPipActivity() { 646 return mPipActivity; 647 } 648 649 /** 650 * Sets the FLAG_TRANSIENT_LAUNCH flag to all changes associated with the given activity 651 * container and parent tasks. 652 */ setTransientLaunchToChanges(@onNull WindowContainer wc)653 private void setTransientLaunchToChanges(@NonNull WindowContainer wc) { 654 for (WindowContainer curr = wc; curr != null && mChanges.containsKey(curr); 655 curr = curr.getParent()) { 656 if (curr.asTask() == null && curr.asActivityRecord() == null) { 657 return; 658 } 659 final ChangeInfo info = mChanges.get(curr); 660 info.mFlags = info.mFlags | ChangeInfo.FLAG_TRANSIENT_LAUNCH; 661 } 662 } 663 664 /** Only for testing. */ setContainerFreezer(IContainerFreezer freezer)665 void setContainerFreezer(IContainerFreezer freezer) { 666 mContainerFreezer = freezer; 667 } 668 669 @TransitionState getState()670 int getState() { 671 return mState; 672 } 673 getSyncId()674 int getSyncId() { 675 return mSyncId; 676 } 677 678 @TransitionFlags getFlags()679 int getFlags() { 680 return mFlags; 681 } 682 683 @VisibleForTesting getStartTransaction()684 SurfaceControl.Transaction getStartTransaction() { 685 return mStartTransaction; 686 } 687 688 @VisibleForTesting getFinishTransaction()689 SurfaceControl.Transaction getFinishTransaction() { 690 return mFinishTransaction; 691 } 692 isPending()693 boolean isPending() { 694 return mState == STATE_PENDING; 695 } 696 isCollecting()697 boolean isCollecting() { 698 return mState == STATE_COLLECTING || mState == STATE_STARTED; 699 } 700 isAborted()701 boolean isAborted() { 702 return mState == STATE_ABORT; 703 } 704 isStarted()705 boolean isStarted() { 706 return mState == STATE_STARTED; 707 } 708 isPlaying()709 boolean isPlaying() { 710 return mState == STATE_PLAYING; 711 } 712 isFinished()713 boolean isFinished() { 714 return mState == STATE_FINISHED; 715 } 716 717 /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */ startCollecting(long timeoutMs)718 void startCollecting(long timeoutMs) { 719 if (mState != STATE_PENDING) { 720 throw new IllegalStateException("Attempting to re-use a transition"); 721 } 722 mState = STATE_COLLECTING; 723 mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, 724 TAG + "-" + transitTypeToString(mType), 725 mParallelCollectType != PARALLEL_TYPE_NONE); 726 mSyncEngine.setSyncMethod(mSyncId, TransitionController.SYNC_METHOD); 727 728 mLogger.mSyncId = mSyncId; 729 mLogger.mCollectTimeNs = SystemClock.elapsedRealtimeNanos(); 730 } 731 732 /** 733 * Formally starts the transition. Participants can be collected before this is started, 734 * but this won't consider itself ready until started -- even if all the participants have 735 * drawn. 736 */ start()737 void start() { 738 if (mState < STATE_COLLECTING) { 739 throw new IllegalStateException("Can't start Transition which isn't collecting."); 740 } else if (mState >= STATE_STARTED) { 741 Slog.w(TAG, "Transition already started id=" + mSyncId + " state=" + mState); 742 // The transition may be aborted (STATE_ABORT) or timed out (STATE_PLAYING by 743 // SyncGroup#finishNow), so do not revert the state to STATE_STARTED. 744 return; 745 } 746 mState = STATE_STARTED; 747 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d", 748 mSyncId); 749 applyReady(); 750 751 mLogger.mStartTimeNs = SystemClock.elapsedRealtimeNanos(); 752 753 mController.updateAnimatingState(); 754 } 755 756 /** 757 * Adds wc to set of WindowContainers participating in this transition. 758 */ collect(@onNull WindowContainer wc)759 void collect(@NonNull WindowContainer wc) { 760 if (mState < STATE_COLLECTING) { 761 throw new IllegalStateException("Transition hasn't started collecting."); 762 } 763 if (!isCollecting()) { 764 // Too late, transition already started playing, so don't collect. 765 return; 766 } 767 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s", 768 mSyncId, wc); 769 // Snapshot before checking if this is a participant in case it has been re-parented. 770 snapshotStartState(getAnimatableParent(wc)); 771 if (mParticipants.contains(wc)) return; 772 // Transient-hide may be hidden later, so no need to request redraw. 773 if (!isInTransientHide(wc)) { 774 mSyncEngine.addToSyncSet(mSyncId, wc); 775 } 776 if (wc.asWindowToken() != null && wc.asWindowToken().mRoundedCornerOverlay) { 777 // Only need to sync the transaction (SyncSet) without ChangeInfo because cutout and 778 // rounded corner overlay never need animations. Especially their surfaces may be put 779 // in root (null, see WindowToken#makeSurface()) that cannot reparent. 780 return; 781 } 782 ChangeInfo info = mChanges.get(wc); 783 if (info == null) { 784 info = new ChangeInfo(wc); 785 updateTransientFlags(info); 786 mChanges.put(wc, info); 787 } 788 mParticipants.add(wc); 789 recordDisplay(wc.getDisplayContent()); 790 if (info.mShowWallpaper) { 791 // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set. 792 wc.mDisplayContent.mWallpaperController.collectTopWallpapers(this); 793 } 794 } 795 796 /** "snapshot" `wc` and all its parents (as potential promotion targets). */ snapshotStartState(@onNull WindowContainer<?> wc)797 private void snapshotStartState(@NonNull WindowContainer<?> wc) { 798 for (WindowContainer<?> curr = wc; 799 curr != null && !mChanges.containsKey(curr); 800 curr = getAnimatableParent(curr)) { 801 final ChangeInfo info = new ChangeInfo(curr); 802 updateTransientFlags(info); 803 mChanges.put(curr, info); 804 if (isReadyGroup(curr)) { 805 mReadyTrackerOld.addGroup(curr); 806 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for" 807 + " Transition %d with root=%s", mSyncId, curr); 808 } 809 } 810 } 811 updateTransientFlags(@onNull ChangeInfo info)812 private void updateTransientFlags(@NonNull ChangeInfo info) { 813 final WindowContainer<?> wc = info.mContainer; 814 // Only look at tasks, taskfragments, or activities 815 if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) return; 816 if (!isInTransientHide(wc)) return; 817 info.mFlags |= ChangeInfo.FLAG_TRANSIENT_HIDE; 818 } 819 recordDisplay(DisplayContent dc)820 private void recordDisplay(DisplayContent dc) { 821 if (dc == null || mTargetDisplays.contains(dc)) return; 822 mTargetDisplays.add(dc); 823 addOnTopTasks(dc, mOnTopTasksStart); 824 if (mOnTopDisplayStart == null) { 825 mOnTopDisplayStart = 826 mController.mAtm.mRootWindowContainer.getTopFocusedDisplayContent(); 827 } 828 // Handle the case {transition.start(); applyTransaction(wct);} that the animating state 829 // is set before collecting participants. 830 if (mController.isAnimating()) { 831 dc.enableHighPerfTransition(true); 832 } 833 mController.dispatchLegacyAppTransitionPending(dc.mDisplayId); 834 } 835 836 /** 837 * Records information about the initial task order. This does NOT collect anything. Call this 838 * before any ordering changes *could* occur, but it is not known yet if it will occur. 839 */ recordTaskOrder(WindowContainer from)840 void recordTaskOrder(WindowContainer from) { 841 recordDisplay(from.getDisplayContent()); 842 } 843 844 /** Adds the top non-alwaysOnTop tasks within `task` to `out`. */ addOnTopTasks(Task task, ArrayList<Task> out)845 private static void addOnTopTasks(Task task, ArrayList<Task> out) { 846 for (int i = task.getChildCount() - 1; i >= 0; --i) { 847 final Task child = task.getChildAt(i).asTask(); 848 if (child == null) return; 849 if (child.getWindowConfiguration().isAlwaysOnTop()) continue; 850 out.add(child); 851 addOnTopTasks(child, out); 852 break; 853 } 854 } 855 856 /** Get the top non-alwaysOnTop leaf task on the display `dc`. */ addOnTopTasks(DisplayContent dc, ArrayList<Task> out)857 private static void addOnTopTasks(DisplayContent dc, ArrayList<Task> out) { 858 final Task topNotAlwaysOnTop = dc.getRootTask( 859 t -> !t.getWindowConfiguration().isAlwaysOnTop()); 860 if (topNotAlwaysOnTop == null) return; 861 out.add(topNotAlwaysOnTop); 862 addOnTopTasks(topNotAlwaysOnTop, out); 863 } 864 865 /** 866 * Records wc as changing its state of existence during this transition. For example, a new 867 * task is considered an existence change while moving a task to front is not. wc is added 868 * to the collection set. Note: Existence is NOT a promotable characteristic. 869 * 870 * This must be explicitly recorded because there are o number of situations where the actual 871 * hierarchy operations don't align with the intent (eg. re-using a task with a new activity 872 * or waiting until after the animation to close). 873 */ collectExistenceChange(@onNull WindowContainer wc)874 void collectExistenceChange(@NonNull WindowContainer wc) { 875 if (mState >= STATE_PLAYING) { 876 // Too late to collect. Don't check too-early here since `collect` will check that. 877 return; 878 } 879 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 880 "Existence Changed in transition %d: %s", mSyncId, wc); 881 collect(wc); 882 mChanges.get(wc).mExistenceChanged = true; 883 } 884 885 /** 886 * Records that a particular container is changing visibly (ie. something about it is changing 887 * while it remains visible). This only effects windows that are already in the collecting 888 * transition. 889 */ collectVisibleChange(WindowContainer wc)890 void collectVisibleChange(WindowContainer wc) { 891 if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) { 892 // All windows are synced already. 893 return; 894 } 895 if (wc.mDisplayContent == null || !isInTransition(wc)) return; 896 if (!wc.mDisplayContent.getDisplayPolicy().isScreenOnFully() 897 || wc.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF) { 898 mFlags |= WindowManager.TRANSIT_FLAG_INVISIBLE; 899 return; 900 } 901 // Activity doesn't need to capture snapshot if the starting window has associated to task. 902 if (wc.asActivityRecord() != null) { 903 final ActivityRecord activityRecord = wc.asActivityRecord(); 904 if (activityRecord.mStartingData != null 905 && activityRecord.mStartingData.mAssociatedTask != null) { 906 return; 907 } 908 } 909 910 if (mContainerFreezer == null) { 911 mContainerFreezer = new ScreenshotFreezer(); 912 } 913 Transition.ChangeInfo change = mChanges.get(wc); 914 if (change == null || !change.mVisible || !wc.isVisibleRequested()) return; 915 // Note: many more tests have already been done by caller. 916 mContainerFreezer.freeze(wc, change.mAbsoluteBounds); 917 } 918 919 /** 920 * Records that a particular container has been reparented. This only effects windows that have 921 * already been collected in the transition. This should be called before reparenting because 922 * the old parent may be removed during reparenting, for example: 923 * {@link Task#shouldRemoveSelfOnLastChildRemoval} 924 */ collectReparentChange(@onNull WindowContainer wc, @NonNull WindowContainer newParent)925 void collectReparentChange(@NonNull WindowContainer wc, @NonNull WindowContainer newParent) { 926 if (!mChanges.containsKey(wc)) { 927 // #collectReparentChange() will be called when the window is reparented. Skip if it is 928 // a window that has not been collected, which means we don't care about this window for 929 // the current transition. 930 return; 931 } 932 final ChangeInfo change = mChanges.get(wc); 933 // Use the current common ancestor if there are multiple reparent, and the original parent 934 // has been detached. Otherwise, use the original parent before the transition. 935 final WindowContainer prevParent = 936 change.mStartParent == null || change.mStartParent.isAttached() 937 ? change.mStartParent 938 : change.mCommonAncestor; 939 if (prevParent == null || !prevParent.isAttached()) { 940 Slog.w(TAG, "Trying to collect reparenting of a window after the previous parent has" 941 + " been detached: " + wc); 942 return; 943 } 944 if (prevParent == newParent) { 945 Slog.w(TAG, "Trying to collect reparenting of a window that has not been reparented: " 946 + wc); 947 return; 948 } 949 if (!newParent.isAttached()) { 950 Slog.w(TAG, "Trying to collect reparenting of a window that is not attached after" 951 + " reparenting: " + wc); 952 return; 953 } 954 WindowContainer ancestor = newParent; 955 while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) { 956 ancestor = ancestor.getParent(); 957 } 958 change.mCommonAncestor = ancestor; 959 } 960 961 /** 962 * Collects a window container which will be removed or invisible. 963 */ collectClose(@onNull WindowContainer<?> wc)964 void collectClose(@NonNull WindowContainer<?> wc) { 965 if (wc.isVisibleRequested()) { 966 collectExistenceChange(wc); 967 } else { 968 // Removing a non-visible window doesn't require a transition, but if there is one 969 // collecting, this should be a member just in case. 970 collect(wc); 971 } 972 } 973 974 /** 975 * @return {@code true} if `wc` is a participant or is a descendant of one. 976 */ isInTransition(WindowContainer wc)977 boolean isInTransition(WindowContainer wc) { 978 for (WindowContainer p = wc; p != null; p = p.getParent()) { 979 if (mParticipants.contains(p)) return true; 980 } 981 return false; 982 } 983 isInAodAppearTransition()984 boolean isInAodAppearTransition() { 985 return (mFlags & TRANSIT_FLAG_AOD_APPEARING) != 0; 986 } 987 988 /** 989 * Specifies configuration change explicitly for the window container, so it can be chosen as 990 * transition target. This is usually used with transition mode 991 * {@link android.view.WindowManager#TRANSIT_CHANGE}. 992 */ setKnownConfigChanges(WindowContainer<?> wc, @ActivityInfo.Config int changes)993 void setKnownConfigChanges(WindowContainer<?> wc, @ActivityInfo.Config int changes) { 994 final ChangeInfo changeInfo = mChanges.get(wc); 995 if (changeInfo != null) { 996 changeInfo.mKnownConfigChanges = changes; 997 } 998 } 999 sendRemoteCallback(@ullable IRemoteCallback callback)1000 private void sendRemoteCallback(@Nullable IRemoteCallback callback) { 1001 if (callback == null) return; 1002 mController.mAtm.mH.sendMessage(PooledLambda.obtainMessage(cb -> { 1003 try { 1004 cb.sendResult(null); 1005 } catch (RemoteException e) { } 1006 }, callback)); 1007 } 1008 1009 /** 1010 * Set animation options for collecting transition by ActivityRecord. 1011 * @param options AnimationOptions captured from ActivityOptions 1012 */ setOverrideAnimation(@ullable AnimationOptions options, @NonNull ActivityRecord r, @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback)1013 void setOverrideAnimation(@Nullable AnimationOptions options, @NonNull ActivityRecord r, 1014 @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) { 1015 if (!isCollecting()) return; 1016 mOverrideOptions = options; 1017 if (mOverrideOptions != null) { 1018 mOverrideOptions.setUserId(r.mUserId); 1019 } 1020 sendRemoteCallback(mClientAnimationStartCallback); 1021 mClientAnimationStartCallback = startCallback; 1022 mClientAnimationFinishCallback = finishCallback; 1023 } 1024 1025 /** 1026 * Set background color for collecting transition. 1027 */ setOverrideBackgroundColor(@olorInt int backgroundColor)1028 void setOverrideBackgroundColor(@ColorInt int backgroundColor) { 1029 mOverrideBackgroundColor = backgroundColor; 1030 } 1031 1032 /** 1033 * Call this when all known changes related to this transition have been applied. Until 1034 * all participants have finished drawing, the transition can still collect participants. 1035 * 1036 * If this is called before the transition is started, it will be deferred until start. 1037 * 1038 * @param wc A reference point to determine which ready-group to update. For now, each display 1039 * has its own ready-group, so this is used to look-up which display to mark ready. 1040 * The transition will wait for all groups to be ready. 1041 */ setReady(WindowContainer wc, boolean ready)1042 void setReady(WindowContainer wc, boolean ready) { 1043 if (!isCollecting() || mSyncId < 0) return; 1044 mReadyTrackerOld.setReadyFrom(wc, ready); 1045 applyReady(); 1046 } 1047 applyReady()1048 private void applyReady() { 1049 if (mState < STATE_STARTED) return; 1050 final boolean ready; 1051 if (mController.useFullReadyTracking()) { 1052 ready = mReadyTracker.isReady(); 1053 } else { 1054 ready = mReadyTrackerOld.allReady(); 1055 } 1056 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 1057 "Set transition ready=%b %d", ready, mSyncId); 1058 boolean changed = mSyncEngine.setReady(mSyncId, ready); 1059 if (changed && ready) { 1060 mLogger.mReadyTimeNs = SystemClock.elapsedRealtimeNanos(); 1061 mOnTopTasksAtReady.clear(); 1062 for (int i = 0; i < mTargetDisplays.size(); ++i) { 1063 addOnTopTasks(mTargetDisplays.get(i), mOnTopTasksAtReady); 1064 } 1065 mOnTopDisplayAtReady = 1066 mController.mAtm.mRootWindowContainer.getTopFocusedDisplayContent(); 1067 mController.onTransitionPopulated(this); 1068 } 1069 } 1070 1071 /** 1072 * Sets all possible ready groups to ready. 1073 * @see ReadyTrackerOld#setAllReady 1074 */ setAllReady()1075 void setAllReady() { 1076 if (!isCollecting() || mSyncId < 0) return; 1077 mReadyTrackerOld.setAllReady(); 1078 applyReady(); 1079 } 1080 1081 @VisibleForTesting allReady()1082 boolean allReady() { 1083 return mReadyTrackerOld.allReady(); 1084 } 1085 1086 /** This transition has all of its expected participants. */ isPopulated()1087 boolean isPopulated() { 1088 return mState >= STATE_STARTED && mReadyTrackerOld.allReady(); 1089 } 1090 1091 /** 1092 * Populates `t` with instructions to reset surface transform of `change` so it matches 1093 * the WM hierarchy. This "undoes" lingering state left by the animation. 1094 */ resetSurfaceTransform(SurfaceControl.Transaction t, WindowContainer target, SurfaceControl targetLeash)1095 private void resetSurfaceTransform(SurfaceControl.Transaction t, WindowContainer target, 1096 SurfaceControl targetLeash) { 1097 final Point tmpPos = new Point(); 1098 target.getRelativePosition(tmpPos); 1099 t.setPosition(targetLeash, tmpPos.x, tmpPos.y); 1100 // No need to clip the display in case seeing the clipped content when during the 1101 // display rotation. No need to clip activities because they rely on clipping on 1102 // task layers. 1103 if (target.asTaskFragment() == null) { 1104 t.setCrop(targetLeash, null /* crop */); 1105 } else { 1106 // Crop to the resolved override bounds. 1107 final Rect clipRect = target.getResolvedOverrideBounds(); 1108 t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height()); 1109 } 1110 t.setMatrix(targetLeash, 1, 0, 0, 1); 1111 // The bounds sent to the transition is always a real bounds. This means we lose 1112 // information about "null" bounds (inheriting from parent). Core will fix-up 1113 // non-organized window surface bounds; however, since Core can't touch organized 1114 // surfaces, add the "inherit from parent" restoration here. 1115 if (target.isOrganized() && target.matchParentBounds()) { 1116 t.setWindowCrop(targetLeash, -1, -1); 1117 } 1118 } 1119 1120 /** 1121 * Build a transaction that "resets" all the re-parenting and layer changes. This is 1122 * intended to be applied at the end of the transition but before the finish callback. This 1123 * needs to be passed/applied in shell because until finish is called, shell owns the surfaces. 1124 * Additionally, this gives shell the ability to better deal with merged transitions. 1125 */ buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info, DisplayContent[] participantDisplays)1126 private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info, 1127 DisplayContent[] participantDisplays) { 1128 for (int i = mTargets.size() - 1; i >= 0; --i) { 1129 final WindowContainer<?> target = mTargets.get(i).mContainer; 1130 if (target.getParent() == null) continue; 1131 final SurfaceControl targetLeash = getLeashSurface(target, null /* t */); 1132 final SurfaceControl origParent = getOrigParentSurface(target); 1133 // Ensure surfaceControls are re-parented back into the hierarchy. 1134 t.reparent(targetLeash, origParent); 1135 t.setLayer(targetLeash, target.getLastLayer()); 1136 t.setCornerRadius(targetLeash, 0); 1137 t.setShadowRadius(targetLeash, 0); 1138 t.setAlpha(targetLeash, 1); 1139 // For config-at-end, the end-transform will be reset after the config is actually 1140 // applied in the client (since the transform depends on config). The other properties 1141 // remain here because shell might want to persistently override them. 1142 if (target.asActivityRecord() == null 1143 || (mTargets.get(i).mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) { 1144 resetSurfaceTransform(t, target, targetLeash); 1145 } 1146 } 1147 // Remove screenshot layers if necessary 1148 if (mContainerFreezer != null) { 1149 mContainerFreezer.cleanUp(t); 1150 } 1151 // Need to update layers on involved displays since they were all paused while 1152 // the animation played. This puts the layers back into the correct order. 1153 for (int i = participantDisplays.length - 1; i >= 0; --i) { 1154 assignLayers(participantDisplays[i], t); 1155 } 1156 1157 for (int i = 0; i < info.getRootCount(); ++i) { 1158 t.reparent(info.getRoot(i).getLeash(), null); 1159 } 1160 } 1161 1162 /** Assigns the layers for the start or end state of transition. */ assignLayers(WindowContainer<?> wc, SurfaceControl.Transaction t)1163 static void assignLayers(WindowContainer<?> wc, SurfaceControl.Transaction t) { 1164 wc.mTransitionController.mBuildingFinishLayers = true; 1165 try { 1166 wc.assignChildLayers(t); 1167 } finally { 1168 wc.mTransitionController.mBuildingFinishLayers = false; 1169 } 1170 } 1171 1172 /** 1173 * Build a transaction that cleans-up transition-only surfaces (transition root and snapshots). 1174 * This will ALWAYS be applied on transition finish just in-case 1175 */ buildCleanupTransaction(SurfaceControl.Transaction t, TransitionInfo info)1176 private static void buildCleanupTransaction(SurfaceControl.Transaction t, TransitionInfo info) { 1177 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 1178 final TransitionInfo.Change c = info.getChanges().get(i); 1179 if (c.getSnapshot() != null) { 1180 t.reparent(c.getSnapshot(), null); 1181 } 1182 // The fixed transform hint was set in DisplayContent#applyRotation(). Make sure to 1183 // clear the hint in case the start transaction is not applied. 1184 if (c.hasFlags(FLAG_IS_DISPLAY) && c.getStartRotation() != c.getEndRotation() 1185 && c.getContainer() != null) { 1186 t.unsetFixedTransformHint(WindowContainer.fromBinder(c.getContainer().asBinder()) 1187 .asDisplayContent().mSurfaceControl); 1188 } 1189 } 1190 for (int i = info.getRootCount() - 1; i >= 0; --i) { 1191 final SurfaceControl leash = info.getRoot(i).getLeash(); 1192 if (leash == null) continue; 1193 t.reparent(leash, null); 1194 } 1195 } 1196 1197 /** 1198 * Set whether this transition can start a pip-enter transition when finished. This is usually 1199 * true, but gets set to false when recents decides that it wants to finish its animation but 1200 * not actually finish its animation (yeah...). 1201 */ setCanPipOnFinish(boolean canPipOnFinish)1202 void setCanPipOnFinish(boolean canPipOnFinish) { 1203 mCanPipOnFinish = canPipOnFinish; 1204 } 1205 didCommitTransientLaunch()1206 private boolean didCommitTransientLaunch() { 1207 if (mTransientLaunches == null) return false; 1208 for (int j = 0; j < mTransientLaunches.size(); ++j) { 1209 if (mTransientLaunches.keyAt(j).isVisibleRequested()) { 1210 return true; 1211 } 1212 } 1213 return false; 1214 } 1215 1216 /** 1217 * Check if pip-entry is possible after finishing and enter-pip if it is. 1218 * 1219 * @return true if we are *guaranteed* to enter-pip. This means we return false if there's 1220 * a chance we won't thus legacy-entry (via pause+userLeaving) will return false. 1221 */ checkEnterPipOnFinish(@onNull ActivityRecord ar)1222 private boolean checkEnterPipOnFinish(@NonNull ActivityRecord ar) { 1223 if (!mCanPipOnFinish || !ar.isVisible() || ar.getTask() == null || !ar.isState(RESUMED)) { 1224 return false; 1225 } 1226 1227 final ActivityRecord resuming = getVisibleTransientLaunch(ar.getTaskDisplayArea()); 1228 if (ar.pictureInPictureArgs != null && ar.pictureInPictureArgs.isAutoEnterEnabled()) { 1229 if (!ar.getTask().isVisibleRequested() || didCommitTransientLaunch()) { 1230 // force enable pip-on-task-switch now that we've committed to actually launching 1231 // to the transient activity. 1232 ar.supportsEnterPipOnTaskSwitch = true; 1233 } 1234 // Make sure this activity can enter pip under the current circumstances. 1235 // `enterPictureInPicture` internally checks, but with beforeStopping=false which 1236 // is specifically for non-auto-enter. 1237 if (!ar.checkEnterPictureInPictureState("enterPictureInPictureMode", 1238 true /* beforeStopping */)) { 1239 return false; 1240 } 1241 final int prevMode = ar.getTask().getWindowingMode(); 1242 final boolean inPip = mController.mAtm.enterPictureInPictureMode(ar, 1243 ar.pictureInPictureArgs, false /* fromClient */, true /* isAutoEnter */); 1244 final int currentMode = ar.getTask().getWindowingMode(); 1245 if (prevMode == WINDOWING_MODE_FULLSCREEN && currentMode == WINDOWING_MODE_PINNED 1246 && mTransientLaunches != null 1247 && ar.mDisplayContent.hasTopFixedRotationLaunchingApp()) { 1248 // There will be a display configuration change after finishing this transition. 1249 // Skip dispatching the change for PiP task to avoid its activity drawing for the 1250 // intermediate state which will cause flickering. The final PiP bounds in new 1251 // rotation will be applied by PipTransition. 1252 ar.mDisplayContent.mPinnedTaskController.setEnterPipWithRotatedTransientLaunch(); 1253 } 1254 return inPip; 1255 } 1256 1257 // Legacy pip-entry (not via isAutoEnterEnabled). 1258 if ((!ar.getTask().isVisibleRequested() || didCommitTransientLaunch()) 1259 && ar.supportsPictureInPicture()) { 1260 // force enable pip-on-task-switch now that we've committed to actually launching to the 1261 // transient activity, and then recalculate whether we can attempt pip. 1262 ar.supportsEnterPipOnTaskSwitch = true; 1263 } 1264 1265 try { 1266 // If not going auto-pip, the activity should be paused with user-leaving. 1267 mController.mAtm.mTaskSupervisor.mUserLeaving = true; 1268 ar.getTaskFragment().startPausing(false /* uiSleeping */, resuming, "finishTransition"); 1269 } finally { 1270 mController.mAtm.mTaskSupervisor.mUserLeaving = false; 1271 } 1272 // Return false anyway because there's no guarantee that the app will enter pip. 1273 return false; 1274 } 1275 1276 /** 1277 * The transition has finished animating and is ready to finalize WM state. This should not 1278 * be called directly; use {@link TransitionController#finishTransition} instead. 1279 */ finishTransition(@onNull ActionChain chain)1280 void finishTransition(@NonNull ActionChain chain) { 1281 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) { 1282 asyncTraceEnd(System.identityHashCode(this)); 1283 } 1284 if (!chain.isFinishing()) { 1285 throw new IllegalStateException("Can't finish on a non-finishing transition " 1286 + chain.mTransition); 1287 } 1288 mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos(); 1289 mController.mLoggerHandler.post(mLogger::logOnFinish); 1290 mController.mTransitionTracer.logFinishedTransition(this); 1291 // Close the transactions now. They were originally copied to Shell in case we needed to 1292 // apply them due to a remote failure. Since we don't need to apply them anymore, free them 1293 // immediately. 1294 if (mStartTransaction != null) mStartTransaction.close(); 1295 if (mFinishTransaction != null) mFinishTransaction.close(); 1296 mStartTransaction = mFinishTransaction = null; 1297 if (mCleanupTransaction != null) { 1298 mCleanupTransaction.apply(); 1299 mCleanupTransaction = null; 1300 } 1301 if (mState < STATE_PLAYING) { 1302 throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId); 1303 } 1304 mController.mFinishingTransition = this; 1305 if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) { 1306 // The transient hide tasks could be occluded now, e.g. returning to home. So trigger 1307 // the update to make the activities in the tasks invisible-requested, then the next 1308 // step can continue to commit the visibility. 1309 mController.mAtm.mRootWindowContainer.ensureActivitiesVisible(); 1310 // Record all the now-hiding activities so that they are committed. Just use 1311 // mParticipants because we can avoid a new list this way. 1312 for (int i = 0; i < mTransientHideTasks.size(); ++i) { 1313 final Task rootTask = mTransientHideTasks.get(i); 1314 rootTask.forAllActivities(r -> { 1315 // Only check leaf-tasks that were collected 1316 if (!mParticipants.contains(r.getTask())) return; 1317 if (rootTask.isVisibleRequested()) { 1318 // This transient-hide didn't hide, so don't commit anything (otherwise we 1319 // could prematurely commit invisible on unrelated activities). To be safe, 1320 // though, notify the controller to prevent degenerate cases. 1321 if (!r.isVisibleRequested()) { 1322 mController.mValidateCommitVis.add(r); 1323 } else { 1324 // Make sure onAppTransitionFinished can be notified. 1325 mParticipants.add(r); 1326 } 1327 return; 1328 } 1329 // This did hide: commit immediately so that other transitions know about it. 1330 mParticipants.add(r); 1331 }); 1332 } 1333 } 1334 1335 boolean hasParticipatedDisplay = false; 1336 boolean hasVisibleTransientLaunch = false; 1337 boolean enterAutoPip = false; 1338 boolean committedSomeInvisible = false; 1339 // Commit all going-invisible containers 1340 for (int i = 0; i < mParticipants.size(); ++i) { 1341 final WindowContainer<?> participant = mParticipants.valueAt(i); 1342 final ActivityRecord ar = participant.asActivityRecord(); 1343 if (ar != null) { 1344 final Task task = ar.getTask(); 1345 if (task == null) continue; 1346 boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar); 1347 // visibleAtTransitionEnd is used to guard against pre-maturely committing 1348 // invisible on a window which is actually hidden by a later transition and not this 1349 // one. However, for a transient launch, we can't use this mechanism because the 1350 // visibility is determined at finish. Instead, use a different heuristic: don't 1351 // commit invisible if the window is already in a later transition. That later 1352 // transition will then handle the commit. 1353 if (isTransientLaunch(ar) && !ar.isVisibleRequested() 1354 && mController.inCollectingTransition(ar)) { 1355 visibleAtTransitionEnd = true; 1356 } 1357 // We need both the expected visibility AND current requested-visibility to be 1358 // false. If it is expected-visible but not currently visible, it means that 1359 // another animation is queued-up to animate this to invisibility, so we can't 1360 // remove the surfaces yet. If it is currently visible, but not expected-visible, 1361 // then doing commitVisibility here would actually be out-of-order and leave the 1362 // activity in a bad state. 1363 // TODO (b/243755838) Create a screen off transition to correct the visible status 1364 // of activities. 1365 final boolean isScreenOff = ar.mDisplayContent == null 1366 || ar.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF; 1367 if ((!visibleAtTransitionEnd || isScreenOff) && !ar.isVisibleRequested()) { 1368 final boolean commitVisibility = !checkEnterPipOnFinish(ar); 1369 // Avoid commit visibility if entering pip or else we will get a sudden 1370 // "flash" / surface going invisible for a split second. 1371 if (commitVisibility) { 1372 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 1373 " Commit activity becoming invisible: %s", ar); 1374 final SnapshotController snapController = mController.mSnapshotController; 1375 if (mTransientLaunches != null && !task.isVisibleRequested() 1376 && !task.isActivityTypeHome()) { 1377 final long startTimeNs = mLogger.mSendTimeNs; 1378 final long lastSnapshotTimeNs = snapController.mTaskSnapshotController 1379 .getSnapshotCaptureTime(task.mTaskId); 1380 // If transition is transient, then snapshots are taken at end of 1381 // transition only if a snapshot was not already captured by request 1382 // during the transition 1383 if (lastSnapshotTimeNs < startTimeNs) { 1384 snapController.mTaskSnapshotController.recordSnapshot(task); 1385 } else { 1386 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 1387 " Skipping post-transition snapshot for task %d", 1388 task.mTaskId); 1389 } 1390 } 1391 ar.commitVisibility(false /* visible */, false /* performLayout */, 1392 true /* fromTransition */); 1393 committedSomeInvisible = true; 1394 } else { 1395 enterAutoPip = true; 1396 } 1397 } 1398 1399 if (ar.mStartingData != null && ar.mStartingData.mRemoveAfterTransaction 1400 == AFTER_TRANSITION_FINISH 1401 && (!ar.isVisible() || !ar.mTransitionController.inTransition(ar))) { 1402 ar.mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE; 1403 ar.removeStartingWindow(); 1404 } 1405 final ChangeInfo changeInfo = mChanges.get(ar); 1406 // Due to transient-hide, there may be some activities here which weren't in the 1407 // transition. 1408 if (changeInfo != null && changeInfo.mVisible != visibleAtTransitionEnd) { 1409 // Legacy dispatch relies on this (for now). 1410 ar.mEnteringAnimation = visibleAtTransitionEnd; 1411 } else if (mTransientLaunches != null && mTransientLaunches.containsKey(ar) 1412 && ar.isVisible()) { 1413 // Transient launch was committed, so report enteringAnimation 1414 ar.mEnteringAnimation = true; 1415 hasVisibleTransientLaunch = true; 1416 1417 // Since transient launches don't automatically take focus, make sure we 1418 // synchronize focus since we committed to the launch. 1419 if (!task.isFocused() && ar.isTopRunningActivity()) { 1420 mController.mAtm.setLastResumedActivityUncheckLocked(ar, 1421 "transitionFinished"); 1422 } 1423 1424 // Prevent spurious background app switches. 1425 if (ar.mDisplayContent.mFocusedApp == ar) { 1426 mController.mAtm.stopAppSwitches(); 1427 } 1428 } 1429 continue; 1430 } 1431 if (participant.asDisplayContent() != null) { 1432 hasParticipatedDisplay = true; 1433 continue; 1434 } 1435 final Task tr = participant.asTask(); 1436 if (tr != null && tr.isVisibleRequested() && tr.inPinnedWindowingMode()) { 1437 final ActivityRecord top = tr.getTopNonFinishingActivity(); 1438 if (top != null && !top.inPinnedWindowingMode()) { 1439 mController.mStateValidators.add(() -> { 1440 if (!tr.isAttached() || !tr.isVisibleRequested() 1441 || !tr.inPinnedWindowingMode()) return; 1442 final ActivityRecord currTop = tr.getTopNonFinishingActivity(); 1443 if (currTop == null) return; 1444 if (currTop.inPinnedWindowingMode()) return; 1445 Slog.e(TAG, "Enter-PIP was started but not completed, this is a Shell/SysUI" 1446 + " bug. This state breaks gesture-nav, so attempting clean-up."); 1447 // We don't know the destination bounds, so we can't actually finish the 1448 // operation. So, to prevent the half-pipped task from covering everything, 1449 // abort the action (which moves the task to back). 1450 tr.abortPipEnter(currTop); 1451 }); 1452 } 1453 } 1454 } 1455 // Commit wallpaper visibility after activity, because usually the wallpaper target token is 1456 // an activity, and wallpaper's visibility depends on activity's visibility. 1457 for (int i = mParticipants.size() - 1; i >= 0; --i) { 1458 final WindowContainer<?> wc = mParticipants.valueAt(i); 1459 WallpaperWindowToken wt = wc.asWallpaperToken(); 1460 if (!Flags.ensureWallpaperInTransitions()) { 1461 if (wt == null) { 1462 final WindowState windowState = wc.asWindowState(); 1463 if (windowState != null) { 1464 wt = windowState.mToken.asWallpaperToken(); 1465 } 1466 } 1467 } 1468 if (wt == null || !wt.isVisible()) continue; 1469 final WindowState target = wt.mDisplayContent.mWallpaperController.getWallpaperTarget(); 1470 final boolean isTargetInvisible = target == null || !target.mToken.isVisible(); 1471 final boolean isWallpaperVisibleAtEnd = 1472 wt.isVisibleRequested() || mVisibleAtTransitionEndTokens.contains(wt); 1473 if (isTargetInvisible || !isWallpaperVisibleAtEnd) { 1474 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 1475 " Commit wallpaper becoming invisible: %s", wt); 1476 wt.commitVisibility(false /* visible */); 1477 } 1478 if (isTargetInvisible) { 1479 // Our original target went invisible, so we should look for a new target. 1480 wt.mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; 1481 } 1482 } 1483 if (committedSomeInvisible) { 1484 mController.onCommittedInvisibles(); 1485 } 1486 1487 if (hasVisibleTransientLaunch) { 1488 // Notify the change about the transient-below task if entering auto-pip. 1489 if (enterAutoPip) { 1490 mController.mAtm.getTaskChangeNotificationController().notifyTaskStackChanged(); 1491 } 1492 // The end of transient launch may not reorder task, so make sure to compute the latest 1493 // task rank according to the current visibility. 1494 mController.mAtm.mRootWindowContainer.rankTaskLayers(); 1495 } 1496 1497 commitConfigAtEndActivities(); 1498 1499 // dispatch legacy callback in a different loop. This is because multiple legacy handlers 1500 // (fixed-rotation/displaycontent) make global changes, so we want to ensure that we've 1501 // processed all the participants first (in particular, we want to trigger pip-enter first) 1502 for (int i = 0; i < mParticipants.size(); ++i) { 1503 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 1504 if (ar == null) continue; 1505 1506 // If the activity was just inserted to an invisible task, it will keep INITIALIZING 1507 // state. Then no need to notify the callback to avoid clearing some states 1508 // unexpectedly, e.g. launch-task-behind. 1509 // However, skip dispatch to predictive back animation target, because it only set 1510 // launch-task-behind to make the activity become visible. 1511 if ((ar.isVisibleRequested() || !ar.isState(ActivityRecord.State.INITIALIZING)) 1512 && !ar.isAnimating(PARENTS, ANIMATION_TYPE_PREDICT_BACK)) { 1513 mController.dispatchLegacyAppTransitionFinished(ar); 1514 } 1515 1516 // Reset the ActivityRecord#mCurrentLaunchCanTurnScreenOn state if it is not the top 1517 // running activity. Doing so in case the state is not yet consumed during rapid 1518 // activity launch. 1519 if (ar.currentLaunchCanTurnScreenOn() && ar.getDisplayContent() != null 1520 && ar.getDisplayContent().topRunningActivity() != ar) { 1521 ar.setCurrentLaunchCanTurnScreenOn(false); 1522 } 1523 } 1524 1525 // Update the input-sink (touch-blocking) state now that the animation is finished. 1526 boolean scheduleAnimation = false; 1527 for (int i = 0; i < mParticipants.size(); ++i) { 1528 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 1529 if (ar == null || !ar.isVisible() || ar.getParent() == null) continue; 1530 scheduleAnimation = true; 1531 ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(ar.getPendingTransaction()); 1532 } 1533 // To apply pending transactions. 1534 if (scheduleAnimation) mController.mAtm.mWindowManager.scheduleAnimationLocked(); 1535 1536 // Always schedule stop processing when transition finishes because activities don't 1537 // stop while they are in a transition thus their stop could still be pending. 1538 mController.mAtm.mTaskSupervisor 1539 .scheduleProcessStoppingAndFinishingActivitiesIfNeeded(); 1540 1541 sendRemoteCallback(mClientAnimationFinishCallback); 1542 1543 legacyRestoreNavigationBarFromApp(); 1544 1545 if (mRecentsDisplayId != INVALID_DISPLAY) { 1546 // Clean up input monitors (for recents) 1547 final DisplayContent dc = 1548 mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId); 1549 dc.getInputMonitor().setActiveRecents(null /* task */, null /* layer */); 1550 dc.getInputMonitor().updateInputWindowsLw(false /* force */); 1551 } 1552 if (mTransientLaunches != null) { 1553 for (int i = mTransientLaunches.size() - 1; i >= 0; --i) { 1554 // Reset the ability of controlling SystemUi which might be changed by 1555 // setTransientLaunch or setRecentsAppBehindSystemBars. 1556 final Task task = mTransientLaunches.keyAt(i).getTask(); 1557 if (task != null) { 1558 task.setCanAffectSystemUiFlags(true); 1559 } 1560 } 1561 } 1562 1563 for (int i = 0; i < mTargetDisplays.size(); ++i) { 1564 final DisplayContent dc = mTargetDisplays.get(i); 1565 final AsyncRotationController asyncRotationController = dc.getAsyncRotationController(); 1566 if (asyncRotationController != null && containsChangeFor(dc, mTargets)) { 1567 asyncRotationController.onTransitionFinished(); 1568 } 1569 dc.onTransitionFinished(); 1570 if (hasParticipatedDisplay) { 1571 final ChangeInfo changeInfo = mChanges.get(dc); 1572 if (changeInfo != null 1573 && changeInfo.mRotation != dc.getWindowConfiguration().getRotation()) { 1574 dc.mAppCompatCameraPolicy.onScreenRotationAnimationFinished(); 1575 } 1576 } 1577 if (mTransientLaunches != null) { 1578 TaskDisplayArea transientTDA = null; 1579 for (int t = 0; t < mTransientLaunches.size(); ++t) { 1580 if (mTransientLaunches.keyAt(t).getDisplayContent() == dc) { 1581 if (hasVisibleTransientLaunch) { 1582 updateImeForVisibleTransientLaunch(dc); 1583 } 1584 transientTDA = mTransientLaunches.keyAt(i).getTaskDisplayArea(); 1585 break; 1586 } 1587 } 1588 if (!hasVisibleTransientLaunch && mRecentsDisplayId == dc.mDisplayId) { 1589 // Restore IME icon only when moving the original app task to front from 1590 // recents, in case IME icon may missing if the moving task has already been 1591 // the current focused task. 1592 InputMethodManagerInternal.get().updateImeWindowStatus( 1593 false /* disableImeIcon */, dc.getDisplayId()); 1594 } 1595 // An uncommitted transient launch can leave incomplete lifecycles if visibilities 1596 // didn't change (eg. re-ordering with translucent tasks will leave launcher 1597 // in RESUMED state), so force an update here. 1598 if (!hasVisibleTransientLaunch && transientTDA != null) { 1599 transientTDA.pauseBackTasks(null /* resuming */); 1600 } 1601 } 1602 dc.removeImeSurfaceImmediately(); 1603 dc.handleCompleteDeferredRemoval(); 1604 } 1605 validateKeyguardOcclusion(); 1606 1607 mState = STATE_FINISHED; 1608 // Rotation change may be deferred while there is a display change transition, so check 1609 // again in case there is a new pending change. 1610 if (hasParticipatedDisplay && !mController.useShellTransitionsRotation()) { 1611 mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */, 1612 false /* forceRelayout */); 1613 } 1614 cleanUpInternal(); 1615 1616 // Handle back animation if it's already started. 1617 mController.mAtm.mBackNavigationController.onTransitionFinish(this); 1618 mController.mFinishingTransition = null; 1619 mController.mSnapshotController.onTransitionFinish(mType, mTargets); 1620 // Resume snapshot persist thread after snapshot controller analysis this transition. 1621 mController.updateAnimatingState(); 1622 1623 invokeTransitionEndedListeners(); 1624 } 1625 invokeTransitionEndedListeners()1626 private void invokeTransitionEndedListeners() { 1627 if (mTransitionEndedListeners == null) { 1628 return; 1629 } 1630 for (int i = 0; i < mTransitionEndedListeners.size(); i++) { 1631 mTransitionEndedListeners.get(i).run(); 1632 } 1633 mTransitionEndedListeners = null; 1634 } 1635 commitConfigAtEndActivities()1636 private void commitConfigAtEndActivities() { 1637 if (mConfigAtEndActivities == null || mConfigAtEndActivities.isEmpty()) { 1638 return; 1639 } 1640 // Now resume the configuration dispatch, wait until the now resumed configs have been 1641 // drawn, and then apply everything together. Any activities that are already in an 1642 // active sync will remain on that sync instead of the new one. 1643 int syncId = -1; 1644 for (int i = 0; i < mConfigAtEndActivities.size(); ++i) { 1645 final ActivityRecord target = mConfigAtEndActivities.get(i); 1646 final SurfaceControl targetLeash = target.getSurfaceControl(); 1647 if (targetLeash == null) { 1648 // activity may have been removed. In this case, no need to sync, just update state. 1649 target.resumeConfigurationDispatch(); 1650 continue; 1651 } 1652 if (target.getSyncGroup() == null || target.getSyncGroup().isIgnoring(target)) { 1653 if (syncId < 0) { 1654 final BLASTSyncEngine.SyncGroup sg = mSyncEngine.prepareSyncSet( 1655 (mSyncId, transaction) -> transaction.apply(), 1656 "ConfigAtTransitEnd"); 1657 syncId = sg.mSyncId; 1658 mSyncEngine.startSyncSet(sg, BLAST_TIMEOUT_DURATION, true /* parallel */); 1659 mSyncEngine.setSyncMethod(syncId, BLASTSyncEngine.METHOD_BLAST); 1660 } 1661 mSyncEngine.addToSyncSet(syncId, target); 1662 } else { 1663 // If there is an existing sync group for the commit-at-end activity, 1664 // enforce BLAST sync method for its windows, before resuming config dispatch. 1665 target.forAllWindows(windowState -> { 1666 windowState.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST; 1667 }, true /* traverseTopToBottom */); 1668 } 1669 // Reset surface state here (since it was skipped in buildFinishTransaction). Since 1670 // we are resuming config to the "current" state, we have to calculate the matching 1671 // surface state now (rather than snapshotting it at animation start). 1672 resetSurfaceTransform(target.getSyncTransaction(), target, targetLeash); 1673 target.resumeConfigurationDispatch(); 1674 } 1675 if (syncId >= 0) { 1676 mSyncEngine.setReady(syncId); 1677 } 1678 } 1679 1680 @Nullable getVisibleTransientLaunch(TaskDisplayArea taskDisplayArea)1681 private ActivityRecord getVisibleTransientLaunch(TaskDisplayArea taskDisplayArea) { 1682 if (mTransientLaunches == null) return null; 1683 for (int i = mTransientLaunches.size() - 1; i >= 0; --i) { 1684 final ActivityRecord candidateActivity = mTransientLaunches.keyAt(i); 1685 if (candidateActivity.getTaskDisplayArea() != taskDisplayArea) { 1686 continue; 1687 } 1688 if (!candidateActivity.isVisibleRequested()) { 1689 continue; 1690 } 1691 return candidateActivity; 1692 } 1693 return null; 1694 } 1695 1696 /** 1697 * Transient-launch activities cannot be IME target (see {@link WindowState#canBeImeTarget}), 1698 * so re-compute in case the IME target is changed after transition. 1699 */ updateImeForVisibleTransientLaunch(@onNull DisplayContent dc)1700 private void updateImeForVisibleTransientLaunch(@NonNull DisplayContent dc) { 1701 final WindowState imeTarget = dc.computeImeTarget(true /* updateImeTarget */); 1702 final WindowState imeWindow = dc.mInputMethodWindow; 1703 if (imeWindow == null || imeTarget == null 1704 || !mController.hasCollectingRotationChange(dc, dc.getRotation())) { 1705 return; 1706 } 1707 // Drop the insets leash if it is still controlled by previous (invisible) app. This avoids 1708 // showing IME with old rotation on an app with new rotation if IME parent is updated 1709 // but insets leash hasn't been refreshed, i.e. DisplayContent#updateImeParent is called 1710 // but InsetsStateController#notifyControlTargetChanged still waits for IME to redraw. 1711 final InsetsSourceProvider sourceProvider = imeWindow.getControllableInsetProvider(); 1712 if (sourceProvider == null || sourceProvider.mControl == null 1713 || !sourceProvider.isClientVisible() 1714 || imeTarget == sourceProvider.getControlTarget()) { 1715 return; 1716 } 1717 final SurfaceControl imeInsetsLeash = sourceProvider.mControl.getLeash(); 1718 final InsetsControlTarget controlTarget = sourceProvider.getControlTarget(); 1719 if (imeInsetsLeash != null && controlTarget != null && controlTarget.getWindow() != null 1720 && !controlTarget.getWindow().mToken.isVisible()) { 1721 dc.getSyncTransaction().reparent(imeInsetsLeash, null); 1722 } 1723 } 1724 abort()1725 void abort() { 1726 // This calls back into itself via controller.abort, so just early return here. 1727 if (mState == STATE_ABORT) return; 1728 if (mState == STATE_PENDING) { 1729 // hasn't started collecting, so can jump directly to aborted state. 1730 mState = STATE_ABORT; 1731 return; 1732 } 1733 if (mState != STATE_COLLECTING && mState != STATE_STARTED) { 1734 throw new IllegalStateException("Too late to abort. state=" + mState); 1735 } 1736 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 1737 "Aborting Transition: %d", mSyncId); 1738 mState = STATE_ABORT; 1739 mLogger.mAbortTimeNs = SystemClock.elapsedRealtimeNanos(); 1740 mController.mTransitionTracer.logAbortedTransition(this); 1741 // Syncengine abort will call through to onTransactionReady() 1742 mSyncEngine.abort(mSyncId); 1743 mController.dispatchLegacyAppTransitionCancelled(mTargetDisplays); 1744 invokeTransitionEndedListeners(); 1745 } 1746 1747 /** Immediately moves this to playing even if it isn't started yet. */ playNow()1748 void playNow() { 1749 if (!(mState == STATE_COLLECTING || mState == STATE_STARTED)) { 1750 return; 1751 } 1752 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Force Playing Transition: %d", 1753 mSyncId); 1754 mForcePlaying = true; 1755 // backwards since conditions are removed. 1756 for (int i = mReadyTracker.mConditions.size() - 1; i >= 0; --i) { 1757 mReadyTracker.mConditions.get(i).meetAlternate("play-now"); 1758 } 1759 final ReadyCondition forcePlay = new ReadyCondition("force-play-now"); 1760 mReadyTracker.add(forcePlay); 1761 forcePlay.meet(); 1762 setAllReady(); 1763 if (mState == STATE_COLLECTING) { 1764 start(); 1765 } 1766 // Don't wait for actual surface-placement. We don't want anything else collected in this 1767 // transition. 1768 mSyncEngine.onSurfacePlacement(); 1769 } 1770 isForcePlaying()1771 boolean isForcePlaying() { 1772 return mForcePlaying; 1773 } 1774 1775 /** Adjusts the priority of the process which will run the transition animation. */ setRemoteAnimationApp(IApplicationThread app)1776 void setRemoteAnimationApp(IApplicationThread app) { 1777 final WindowProcessController wpc = mController.mAtm.getProcessController(app); 1778 if (wpc != null) { 1779 // This is an early prediction. If the process doesn't ack the animation in 200 ms, 1780 // the priority will be restored. 1781 mController.mRemotePlayer.update(wpc, true /* running */, true /* predict */); 1782 } 1783 } 1784 setNoAnimation(WindowContainer wc)1785 void setNoAnimation(WindowContainer wc) { 1786 final ChangeInfo change = mChanges.get(wc); 1787 if (change == null) { 1788 throw new IllegalStateException("Can't set no-animation property of non-participant"); 1789 } 1790 change.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION; 1791 } 1792 containsChangeFor(WindowContainer wc, ArrayList<ChangeInfo> list)1793 static boolean containsChangeFor(WindowContainer wc, ArrayList<ChangeInfo> list) { 1794 for (int i = list.size() - 1; i >= 0; --i) { 1795 if (list.get(i).mContainer == wc) return true; 1796 } 1797 return false; 1798 } 1799 1800 @Override onTransactionReady(int syncId, SurfaceControl.Transaction transaction)1801 public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) { 1802 if (syncId != mSyncId) { 1803 Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId); 1804 return; 1805 } 1806 1807 if (mController.useFullReadyTracking()) { 1808 for (int i = 0; i < mReadyTracker.mMet.size(); ++i) { 1809 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "#%d: Met condition: %s", 1810 mSyncId, mReadyTracker.mMet.get(i)); 1811 } 1812 } 1813 1814 // Commit the visibility of visible activities before calculateTransitionInfo(), so the 1815 // TaskInfo can be visible. Also it needs to be done before moveToPlaying(), otherwise 1816 // ActivityRecord#canShowWindows() may reject to show its window. The visibility also 1817 // needs to be updated for STATE_ABORT. 1818 commitVisibleActivities(transaction); 1819 commitVisibleWallpapers(transaction); 1820 1821 if (mTransactionCompletedListeners != null) { 1822 for (int i = 0; i < mTransactionCompletedListeners.size(); i++) { 1823 final Runnable listener = mTransactionCompletedListeners.get(i); 1824 transaction.addTransactionCompletedListener(Runnable::run, 1825 (stats) -> listener.run()); 1826 } 1827 mTransactionCompletedListeners = null; 1828 } 1829 1830 // Fall-back to the default display if there isn't one participating. 1831 final DisplayContent primaryDisplay = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0) 1832 : mController.mAtm.mRootWindowContainer.getDefaultDisplay(); 1833 1834 if (mState == STATE_ABORT) { 1835 mController.onAbort(this); 1836 if (mConfigAtEndActivities != null) { 1837 for (int i = 0; i < mConfigAtEndActivities.size(); ++i) { 1838 mConfigAtEndActivities.get(i).resumeConfigurationDispatch(); 1839 } 1840 mConfigAtEndActivities = null; 1841 } 1842 primaryDisplay.getPendingTransaction().merge(transaction); 1843 mSyncId = -1; 1844 mOverrideOptions = null; 1845 cleanUpInternal(); 1846 return; 1847 } 1848 1849 if (mState != STATE_STARTED) { 1850 Slog.e(TAG, "Playing a Transition which hasn't started! #" + mSyncId + " This will " 1851 + "likely cause an exception in Shell"); 1852 } 1853 1854 mState = STATE_PLAYING; 1855 mStartTransaction = transaction; 1856 mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get(); 1857 1858 // Flags must be assigned before calculateTransitionInfo. Otherwise it won't take effect. 1859 if (primaryDisplay.isKeyguardLocked()) { 1860 mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED; 1861 } 1862 1863 // This is the only (or last) transition that is collecting, so we need to report any 1864 // leftover order changes. 1865 collectOrderChanges(mController.mWaitingTransitions.isEmpty()); 1866 1867 if (mPriorVisibilityMightBeDirty) { 1868 updatePriorVisibility(); 1869 } 1870 1871 // Resolve the animating targets from the participants. 1872 mTargets = calculateTargets(mParticipants, mChanges); 1873 1874 // Check whether the participants were animated from back navigation. 1875 mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets, 1876 transaction, mFinishTransaction); 1877 final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction); 1878 info.setDebugId(mSyncId); 1879 mController.assignTrack(this, info); 1880 1881 mController.moveToPlaying(this); 1882 1883 // Repopulate the displays based on the resolved targets. 1884 final DisplayContent[] participantDisplays = mTargetDisplays.toArray( 1885 new DisplayContent[mTargetDisplays.size()]); 1886 mTargetDisplays.clear(); 1887 for (int i = 0; i < info.getRootCount(); ++i) { 1888 final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent( 1889 info.getRoot(i).getDisplayId()); 1890 mTargetDisplays.add(dc); 1891 } 1892 1893 for (int i = 0; i < mTargets.size(); ++i) { 1894 final WindowContainer<?> wc = mTargets.get(i).mContainer; 1895 final WallpaperWindowToken wp = wc.asWallpaperToken(); 1896 if (wp != null) { 1897 // If on a rotation leash, the wallpaper token surface needs to be shown explicitly 1898 // because shell only gets the leash and the wallpaper token surface is not allowed 1899 // to be changed by non-transition logic until the transition is finished. 1900 if (wp.mWmService.mFlags.mEnsureWallpaperInTransitions && wp.isVisibleRequested() 1901 && wp.getFixedRotationLeash() != null) { 1902 transaction.show(wp.mSurfaceControl); 1903 } 1904 continue; 1905 } 1906 final DisplayArea<?> da = wc.asDisplayArea(); 1907 if (da == null) continue; 1908 if (da.isVisibleRequested()) { 1909 final int inValidateList = mController.mValidateDisplayVis.indexOf(da); 1910 if (inValidateList >= 0 1911 // The display-area is visible, but if we only detect a non-visibility 1912 // change, then we shouldn't remove the validator. 1913 && !mChanges.get(da).mVisible) { 1914 mController.mValidateDisplayVis.remove(inValidateList); 1915 } 1916 } else { 1917 // In case something accidentally hides a displayarea and nothing shows it again. 1918 mController.mValidateDisplayVis.add(da); 1919 } 1920 } 1921 overrideAnimationOptionsToInfoIfNecessary(info); 1922 1923 // TODO(b/188669821): Move to animation impl in shell. 1924 for (int i = 0; i < mTargetDisplays.size(); ++i) { 1925 handleLegacyRecentsStartBehavior(mTargetDisplays.get(i), info); 1926 if (mRecentsDisplayId != INVALID_DISPLAY) break; 1927 } 1928 1929 // The callback is only populated for custom activity-level client animations 1930 sendRemoteCallback(mClientAnimationStartCallback); 1931 1932 // Manually show any activities that are visibleRequested. This is needed to properly 1933 // support simultaneous animation queueing/merging. Specifically, if transition A makes 1934 // an activity invisible, it's finishTransaction (which is applied *after* the animation) 1935 // will hide the activity surface. If transition B then makes the activity visible again, 1936 // the normal surfaceplacement logic won't add a show to this start transaction because 1937 // the activity visibility hasn't been committed yet. To deal with this, we have to manually 1938 // show here in the same way that we manually hide in finishTransaction. 1939 for (int i = mParticipants.size() - 1; i >= 0; --i) { 1940 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 1941 if (ar == null || !ar.isVisibleRequested()) continue; 1942 transaction.show(ar.getSurfaceControl()); 1943 1944 // Also manually show any non-reported parents. This is necessary in a few cases 1945 // where a task is NOT organized but had its visibility changed within its direct 1946 // parent. An example of this is if an alternate home leaf-task HB is started atop the 1947 // normal home leaf-task HA: these are both in the Home root-task HR, so there will be a 1948 // transition containing HA and HB where HA surface is hidden. If a standard task SA is 1949 // launched on top, then HB finishes, no transition will happen since neither home is 1950 // visible. When SA finishes, the transition contains HR rather than HA. Since home 1951 // leaf-tasks are NOT organized, HA won't be in the transition and thus its surface 1952 // wouldn't be shown. Just show is safe here since all other properties will have 1953 // already been reset by the original hiding-transition's finishTransaction (we can't 1954 // show in the finishTransaction because by then the activity doesn't hide until 1955 // surface placement). 1956 for (WindowContainer p = ar.getParent(); 1957 p != null && !containsChangeFor(p, mTargets) && !p.isOrganized(); 1958 p = p.getParent()) { 1959 if (p.getSurfaceControl() != null) { 1960 transaction.show(p.getSurfaceControl()); 1961 } 1962 } 1963 } 1964 1965 // Record windowtokens (activity/wallpaper) that are expected to be visible after the 1966 // transition animation. This will be used in finishTransition to prevent prematurely 1967 // committing visibility. Skip transient launches since those are only temporarily visible. 1968 if (mTransientLaunches == null) { 1969 for (int i = mParticipants.size() - 1; i >= 0; --i) { 1970 final WindowContainer wc = mParticipants.valueAt(i); 1971 if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue; 1972 mVisibleAtTransitionEndTokens.add(wc.asWindowToken()); 1973 } 1974 } 1975 1976 // This is non-null only if display has changes. It handles the visible windows that don't 1977 // need to be participated in the transition. 1978 for (int i = 0; i < mTargetDisplays.size(); ++i) { 1979 final DisplayContent dc = mTargetDisplays.get(i); 1980 final AsyncRotationController controller = dc.getAsyncRotationController(); 1981 if (controller != null && containsChangeFor(dc, mTargets)) { 1982 controller.setupStartTransaction(transaction); 1983 } 1984 } 1985 // Use participant displays here (rather than just targets) because it's possible for 1986 // there to be order changes between non-top tasks in an otherwise no-op transition. 1987 buildFinishTransaction(mFinishTransaction, info, participantDisplays); 1988 mCleanupTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get(); 1989 buildCleanupTransaction(mCleanupTransaction, info); 1990 if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) { 1991 mController.dispatchLegacyAppTransitionStarting(participantDisplays, 1992 mStatusBarTransitionDelay); 1993 try { 1994 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 1995 "Calling onTransitionReady: %s", info); 1996 mLogger.mSendTimeNs = SystemClock.elapsedRealtimeNanos(); 1997 mLogger.mInfo = info; 1998 mController.getTransitionPlayer().onTransitionReady( 1999 mToken, info, transaction, mFinishTransaction); 2000 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { 2001 asyncTraceBegin(TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this)); 2002 } 2003 } catch (RemoteException e) { 2004 // If there's an exception when trying to send the mergedTransaction to the 2005 // client, we should finish and apply it here so the transactions aren't lost. 2006 postCleanupOnFailure(); 2007 } 2008 for (int i = 0; i < mTargetDisplays.size(); ++i) { 2009 final DisplayContent dc = mTargetDisplays.get(i); 2010 final AccessibilityController accessibilityController = 2011 dc.mWmService.mAccessibilityController; 2012 if (accessibilityController.hasCallbacks()) { 2013 accessibilityController.onWMTransition(dc.getDisplayId(), mType, mFlags); 2014 } 2015 } 2016 } else { 2017 // No player registered or it's not enabled, so just finish/apply immediately 2018 if (!mIsPlayerEnabled) { 2019 mLogger.mSendTimeNs = SystemClock.elapsedRealtimeNanos(); 2020 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 2021 "Apply and finish immediately because player is disabled " 2022 + "for transition #%d .", mSyncId); 2023 } 2024 postCleanupOnFailure(); 2025 } 2026 mOverrideOptions = null; 2027 2028 reportStartReasonsToLogger(); 2029 2030 // Take snapshots for closing tasks/activities before the animation finished but after 2031 // dispatching onTransitionReady, so IME (if there is) can be captured together and the 2032 // time spent on snapshot won't delay the start of animation. Note that if this transition 2033 // is transient (mTransientLaunches != null), the snapshot will be captured at the end of 2034 // the transition, because IME won't move be moved during the transition and the tasks are 2035 // still live. 2036 if (mTransientLaunches == null) { 2037 mController.mSnapshotController.onTransactionReady(mType, mTargets); 2038 } 2039 2040 // Since we created root-leash but no longer reference it from core, release it now 2041 info.releaseAnimSurfaces(); 2042 2043 if (mLogger.mInfo != null) { 2044 mLogger.logOnSendAsync(mController.mLoggerHandler); 2045 mController.mTransitionTracer.logSentTransition(this, mTargets); 2046 } 2047 } 2048 2049 @VisibleForTesting overrideAnimationOptionsToInfoIfNecessary(@onNull TransitionInfo info)2050 void overrideAnimationOptionsToInfoIfNecessary(@NonNull TransitionInfo info) { 2051 if (mOverrideOptions == null) { 2052 return; 2053 } 2054 final List<TransitionInfo.Change> changes = info.getChanges(); 2055 for (int i = changes.size() - 1; i >= 0; --i) { 2056 final WindowContainer<?> container = mTargets.get(i).mContainer; 2057 if (container.asActivityRecord() != null 2058 || shouldApplyAnimOptionsToTask(container.asTask())) { 2059 changes.get(i).setAnimationOptions(mOverrideOptions); 2060 changes.get(i).setBackgroundColor(mOverrideBackgroundColor); 2061 } else if (shouldApplyAnimOptionsToEmbeddedTf(container.asTaskFragment())) { 2062 // We only override AnimationOptions because backgroundColor should be from 2063 // TaskFragmentAnimationParams. 2064 changes.get(i).setAnimationOptions(mOverrideOptions); 2065 } 2066 } 2067 updateActivityTargetForCrossProfileAnimation(info); 2068 } 2069 shouldApplyAnimOptionsToTask(@ullable Task task)2070 private boolean shouldApplyAnimOptionsToTask(@Nullable Task task) { 2071 if (task == null || mOverrideOptions == null) { 2072 return false; 2073 } 2074 final int animType = mOverrideOptions.getType(); 2075 // Only apply AnimationOptions to Task if it is specified in #getOverrideTaskTransition 2076 // or it's ANIM_SCENE_TRANSITION. 2077 return animType == ANIM_SCENE_TRANSITION || mOverrideOptions.getOverrideTaskTransition(); 2078 } 2079 shouldApplyAnimOptionsToEmbeddedTf(@ullable TaskFragment taskFragment)2080 private boolean shouldApplyAnimOptionsToEmbeddedTf(@Nullable TaskFragment taskFragment) { 2081 if (taskFragment == null || !taskFragment.isEmbedded()) { 2082 return false; 2083 } 2084 if (taskFragment.getAnimationParams().hasOverrideAnimation()) { 2085 // Always respect animation overrides from TaskFragmentAnimationParams. 2086 return false; 2087 } 2088 // ActivityEmbedding animation adapter only support custom animation 2089 return mOverrideOptions != null && mOverrideOptions.getType() == ANIM_CUSTOM; 2090 } 2091 2092 /** 2093 * Updates activity open target if {@link #mOverrideOptions} is 2094 * {@link ANIM_OPEN_CROSS_PROFILE_APPS}. 2095 */ updateActivityTargetForCrossProfileAnimation(@onNull TransitionInfo info)2096 private void updateActivityTargetForCrossProfileAnimation(@NonNull TransitionInfo info) { 2097 if (mOverrideOptions.getType() != ANIM_OPEN_CROSS_PROFILE_APPS) { 2098 return; 2099 } 2100 for (int i = 0; i < mTargets.size(); ++i) { 2101 final ActivityRecord activity = mTargets.get(i).mContainer.asActivityRecord(); 2102 final TransitionInfo.Change change = info.getChanges().get(i); 2103 if (activity == null || change.getMode() != TRANSIT_OPEN) { 2104 continue; 2105 } 2106 2107 int flags = change.getFlags(); 2108 flags |= activity.mUserId == activity.mWmService.mCurrentUserId 2109 ? TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL 2110 : TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL; 2111 change.setFlags(flags); 2112 break; 2113 } 2114 } 2115 2116 // Note that this method is not called in WM lock. 2117 @Override onTransactionCommitted()2118 public void onTransactionCommitted() { 2119 mLogger.mTransactionCommitTimeNs = SystemClock.elapsedRealtimeNanos(); 2120 } 2121 2122 @Override onTransactionCommitTimeout()2123 public void onTransactionCommitTimeout() { 2124 if (mCleanupTransaction == null) return; 2125 for (int i = mTargetDisplays.size() - 1; i >= 0; --i) { 2126 final DisplayContent dc = mTargetDisplays.get(i); 2127 final AsyncRotationController asyncRotationController = dc.getAsyncRotationController(); 2128 if (asyncRotationController != null && containsChangeFor(dc, mTargets)) { 2129 asyncRotationController.onTransactionCommitTimeout(mCleanupTransaction); 2130 } 2131 } 2132 } 2133 2134 /** 2135 * Adds a listener that will be executed after the start transaction of this transition 2136 * is presented on the screen, the listener will be executed on a binder thread 2137 */ addTransactionCompletedListener(Runnable listener)2138 void addTransactionCompletedListener(Runnable listener) { 2139 if (mTransactionCompletedListeners == null) { 2140 mTransactionCompletedListeners = new ArrayList<>(); 2141 } 2142 mTransactionCompletedListeners.add(listener); 2143 } 2144 2145 /** 2146 * Adds a listener that will be executed after the transition is finished or aborted. 2147 */ addTransitionEndedListener(Runnable listener)2148 void addTransitionEndedListener(Runnable listener) { 2149 if (mState != STATE_COLLECTING && mState != STATE_STARTED) { 2150 throw new IllegalStateException( 2151 "Can't register listeners if the transition isn't collecting. state=" + mState); 2152 } 2153 if (mTransitionEndedListeners == null) { 2154 mTransitionEndedListeners = new ArrayList<>(); 2155 } 2156 mTransitionEndedListeners.add(listener); 2157 } 2158 2159 /** 2160 * Checks if the transition contains order changes. 2161 * 2162 * This is a shallow check that doesn't account for collection in parallel, unlike 2163 * {@code collectOrderChanges} 2164 */ hasOrderChanges()2165 boolean hasOrderChanges() { 2166 ArrayList<Task> onTopTasks = new ArrayList<>(); 2167 // Iterate over target displays to get up to date on top tasks. 2168 // Cannot use `mOnTopTasksAtReady` as it's not populated before the `applyReady` is called. 2169 for (DisplayContent dc : mTargetDisplays) { 2170 addOnTopTasks(dc, onTopTasks); 2171 } 2172 for (Task task : onTopTasks) { 2173 if (!mOnTopTasksStart.contains(task)) { 2174 return true; 2175 } 2176 } 2177 if (enableDisplayFocusInShellTransitions() && mOnTopDisplayStart 2178 != mController.mAtm.mRootWindowContainer.getTopFocusedDisplayContent()) { 2179 return true; 2180 } 2181 return false; 2182 } 2183 2184 /** 2185 * Collect tasks which moved-to-top as part of this transition. This also updates the 2186 * controller's latest-reported when relevant. 2187 * 2188 * This is a non-trivial operation because transition can collect in parallel; however, it can 2189 * be made tenable by acknowledging that the "setup" part of collection (phase 1) is still 2190 * globally serial; so, we can build some reasonable rules around it. 2191 * 2192 * First, we record the "start" on-top state (to compare against). Then, when this becomes 2193 * ready (via allReady, NOT onTransactionReady), we also record the "onReady" on-top state 2194 * -- the idea here is that upon "allReady", all the actual WM changes should be done and we 2195 * are now just waiting for window content to become ready (finish drawing). 2196 * 2197 * Then, in this function (during onTransactionReady), we compare the two orders and include 2198 * any changes to the order in the reported transition-info. Unfortunately, because of parallel 2199 * collection, the order can change in unexpected ways by now. To resolve this, we ALSO keep a 2200 * global "latest reported order" in TransitionController and use that to make decisions. 2201 */ 2202 @VisibleForTesting collectOrderChanges(boolean reportCurrent)2203 void collectOrderChanges(boolean reportCurrent) { 2204 if (mOnTopTasksStart.isEmpty()) return; 2205 boolean includesOrderChange = false; 2206 for (int i = 0; i < mOnTopTasksAtReady.size(); ++i) { 2207 final Task task = mOnTopTasksAtReady.get(i); 2208 if (mOnTopTasksStart.contains(task)) continue; 2209 includesOrderChange = true; 2210 break; 2211 } 2212 includesOrderChange |= enableDisplayFocusInShellTransitions() 2213 && mOnTopDisplayStart != mOnTopDisplayAtReady; 2214 if (!includesOrderChange && !reportCurrent) { 2215 // This transition doesn't include an order change, so if it isn't required to report 2216 // the current focus (eg. it's the last of a cluster of transitions), then don't 2217 // report. 2218 return; 2219 } 2220 // The transition included an order change, but it may not be up-to-date, so grab the 2221 // latest state and compare with the last reported state (or our start state if no 2222 // reported state exists). 2223 ArrayList<Task> onTopTasksEnd = new ArrayList<>(); 2224 final DisplayContent onTopDisplayEnd = 2225 mController.mAtm.mRootWindowContainer.getTopFocusedDisplayContent(); 2226 for (int d = 0; d < mTargetDisplays.size(); ++d) { 2227 addOnTopTasks(mTargetDisplays.get(d), onTopTasksEnd); 2228 final int displayId = mTargetDisplays.get(d).mDisplayId; 2229 ArrayList<Task> reportedOnTop = mController.mLatestOnTopTasksReported.get(displayId); 2230 for (int i = onTopTasksEnd.size() - 1; i >= 0; --i) { 2231 final Task task = onTopTasksEnd.get(i); 2232 if (task.getDisplayId() != displayId) continue; 2233 if (reportedOnTop == null) { 2234 if (mOnTopTasksStart.contains(task)) continue; 2235 } else if (reportedOnTop.contains(task)) { 2236 continue; 2237 } 2238 addToTopChange(task); 2239 } 2240 // Swap in the latest on-top tasks. 2241 mController.mLatestOnTopTasksReported.put(displayId, onTopTasksEnd); 2242 onTopTasksEnd = reportedOnTop != null ? reportedOnTop : new ArrayList<>(); 2243 onTopTasksEnd.clear(); 2244 2245 if (enableDisplayFocusInShellTransitions() 2246 && mOnTopDisplayStart != onTopDisplayEnd 2247 && displayId == onTopDisplayEnd.mDisplayId) { 2248 addToTopChange(onTopDisplayEnd); 2249 } 2250 } 2251 } 2252 addToTopChange(@onNull WindowContainer wc)2253 private void addToTopChange(@NonNull WindowContainer wc) { 2254 mParticipants.add(wc); 2255 if (!mChanges.containsKey(wc)) { 2256 mChanges.put(wc, new ChangeInfo(wc)); 2257 } 2258 mChanges.get(wc).mFlags |= ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP; 2259 } 2260 postCleanupOnFailure()2261 private void postCleanupOnFailure() { 2262 mController.mAtm.mH.post(() -> { 2263 synchronized (mController.mAtm.mGlobalLock) { 2264 cleanUpOnFailure(); 2265 } 2266 }); 2267 } 2268 2269 /** 2270 * If the remote failed for any reason, use this to do any appropriate clean-up. Do not call 2271 * this directly, it's designed to by called by {@link TransitionController} only. 2272 */ cleanUpOnFailure()2273 void cleanUpOnFailure() { 2274 // No need to clean-up if this isn't playing yet. 2275 if (mState < STATE_PLAYING) return; 2276 2277 if (mStartTransaction != null) { 2278 mStartTransaction.apply(); 2279 } 2280 if (mFinishTransaction != null) { 2281 mFinishTransaction.apply(); 2282 } 2283 mController.finishTransition(mController.mAtm.mChainTracker.startFinish("clean-up", this)); 2284 } 2285 cleanUpInternal()2286 private void cleanUpInternal() { 2287 // Clean-up any native references. 2288 for (int i = 0; i < mChanges.size(); ++i) { 2289 final ChangeInfo ci = mChanges.valueAt(i); 2290 if (ci.mSnapshot != null) { 2291 ci.mSnapshot.release(); 2292 } 2293 } 2294 if (mCleanupTransaction != null) { 2295 mCleanupTransaction.apply(); 2296 mCleanupTransaction = null; 2297 } 2298 } 2299 2300 /** The transition is ready to play. Make the start transaction show the surfaces. */ commitVisibleActivities(SurfaceControl.Transaction transaction)2301 private void commitVisibleActivities(SurfaceControl.Transaction transaction) { 2302 for (int i = mParticipants.size() - 1; i >= 0; --i) { 2303 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 2304 if (ar == null || ar.getTask() == null) { 2305 continue; 2306 } 2307 if (ar.isVisibleRequested()) { 2308 ar.commitVisibility(true /* visible */, false /* performLayout */, 2309 true /* fromTransition */); 2310 ar.commitFinishDrawing(transaction); 2311 } 2312 ar.getTask().setDeferTaskAppear(false); 2313 } 2314 } 2315 commitVisibleWallpapers(SurfaceControl.Transaction t)2316 private void commitVisibleWallpapers(SurfaceControl.Transaction t) { 2317 boolean showWallpaper = shouldWallpaperBeVisible(); 2318 for (int i = mParticipants.size() - 1; i >= 0; --i) { 2319 final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken(); 2320 if (wallpaper != null) { 2321 if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) { 2322 wallpaper.commitVisibility(showWallpaper); 2323 } 2324 if (showWallpaper && wallpaper.isVisibleRequested()) { 2325 for (int j = wallpaper.mChildren.size() - 1; j >= 0; --j) { 2326 wallpaper.mChildren.get(j).mWinAnimator.prepareSurfaceLocked(t); 2327 } 2328 } 2329 } 2330 } 2331 } 2332 shouldWallpaperBeVisible()2333 private boolean shouldWallpaperBeVisible() { 2334 for (int i = mParticipants.size() - 1; i >= 0; --i) { 2335 WindowContainer participant = mParticipants.valueAt(i); 2336 if (participant.showWallpaper()) return true; 2337 } 2338 return false; 2339 } 2340 2341 // TODO(b/188595497): Remove after migrating to shell. 2342 /** @see RecentsAnimationController#attachNavigationBarToApp */ handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info)2343 private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) { 2344 if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) { 2345 return; 2346 } 2347 2348 // Recents has an input-consumer to grab input from the "live tile" app. Set that up here 2349 final InputConsumerImpl recentsAnimationInputConsumer = 2350 dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); 2351 Task recentsTask = null; 2352 if (recentsAnimationInputConsumer != null) { 2353 // Find the top-most going-away task and the recents activity. The top-most 2354 // is used as layer reference while the recents is used for registering the consumer 2355 // override. 2356 Task topNonRecentsTask = null; 2357 for (int i = 0; i < info.getChanges().size(); ++i) { 2358 final ActivityManager.RunningTaskInfo taskInfo = 2359 info.getChanges().get(i).getTaskInfo(); 2360 if (taskInfo == null) continue; 2361 final Task task = Task.fromWindowContainerToken(taskInfo.token); 2362 if (task == null) continue; 2363 final int activityType = taskInfo.topActivityType; 2364 final boolean isRecents = activityType == ACTIVITY_TYPE_HOME 2365 || activityType == ACTIVITY_TYPE_RECENTS; 2366 if (isRecents && recentsTask == null) { 2367 recentsTask = task; 2368 } else if (!isRecents && topNonRecentsTask == null) { 2369 topNonRecentsTask = task; 2370 } 2371 } 2372 if (recentsTask != null && topNonRecentsTask != null) { 2373 recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set( 2374 topNonRecentsTask.getBounds()); 2375 dc.getInputMonitor().setActiveRecents(recentsTask, topNonRecentsTask); 2376 } 2377 } 2378 2379 if (recentsTask == null) { 2380 // No recents activity on `dc`, its probably on a different display. 2381 return; 2382 } 2383 mRecentsDisplayId = dc.mDisplayId; 2384 2385 // The rest of this function handles nav-bar reparenting 2386 2387 if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition() 2388 // Skip the case where the nav bar is controlled by fade rotation. 2389 || dc.getAsyncRotationController() != null) { 2390 return; 2391 } 2392 2393 WindowContainer topWC = null; 2394 // Find the top-most non-home, closing app. 2395 for (int i = 0; i < info.getChanges().size(); ++i) { 2396 final TransitionInfo.Change c = info.getChanges().get(i); 2397 if (c.getTaskInfo() == null || c.getTaskInfo().displayId != mRecentsDisplayId 2398 || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD 2399 || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) { 2400 continue; 2401 } 2402 topWC = WindowContainer.fromBinder(c.getContainer().asBinder()); 2403 break; 2404 } 2405 if (topWC == null || topWC.inMultiWindowMode()) { 2406 return; 2407 } 2408 2409 final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar(); 2410 if (navWindow == null || navWindow.mToken == null) { 2411 return; 2412 } 2413 mController.mNavigationBarAttachedToApp = true; 2414 navWindow.mToken.cancelAnimation(); 2415 final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction(); 2416 final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl(); 2417 t.reparent(navSurfaceControl, topWC.getSurfaceControl()); 2418 t.show(navSurfaceControl); 2419 2420 final WindowContainer imeContainer = dc.getImeContainer(); 2421 if (imeContainer.isVisible()) { 2422 t.setRelativeLayer(navSurfaceControl, imeContainer.getSurfaceControl(), 1); 2423 } else { 2424 // Place the nav bar on top of anything else in the top activity. 2425 t.setLayer(navSurfaceControl, Integer.MAX_VALUE); 2426 } 2427 sendLumaSamplingEnabledToStatusBarInternal(dc, false); 2428 } 2429 2430 /** @see RecentsAnimationController#restoreNavigationBarFromApp */ legacyRestoreNavigationBarFromApp()2431 void legacyRestoreNavigationBarFromApp() { 2432 if (!mController.mNavigationBarAttachedToApp) { 2433 return; 2434 } 2435 mController.mNavigationBarAttachedToApp = false; 2436 2437 int recentsDisplayId = mRecentsDisplayId; 2438 if (recentsDisplayId == INVALID_DISPLAY) { 2439 Slog.i(TAG, "Restore parent surface of navigation bar by another transition"); 2440 recentsDisplayId = DEFAULT_DISPLAY; 2441 } 2442 2443 final DisplayContent dc = 2444 mController.mAtm.mRootWindowContainer.getDisplayContent(recentsDisplayId); 2445 sendLumaSamplingEnabledToStatusBarInternal(dc, true); 2446 final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar(); 2447 if (navWindow == null) return; 2448 navWindow.setSurfaceTranslationY(0); 2449 2450 final WindowToken navToken = navWindow.mToken; 2451 if (navToken == null) return; 2452 final SurfaceControl.Transaction t = dc.getPendingTransaction(); 2453 final WindowContainer parent = navToken.getParent(); 2454 t.setLayer(navToken.getSurfaceControl(), navToken.getLastLayer()); 2455 2456 boolean animate = false; 2457 // Search for the home task. If it is supposed to be visible, then the navbar is not at 2458 // the bottom of the screen, so we need to animate it. 2459 for (int i = 0; i < mTargets.size(); ++i) { 2460 final Task task = mTargets.get(i).mContainer.asTask(); 2461 if (task == null || !task.isActivityTypeHomeOrRecents()) continue; 2462 animate = task.isVisibleRequested(); 2463 break; 2464 } 2465 2466 if (animate) { 2467 final NavBarFadeAnimationController controller = 2468 new NavBarFadeAnimationController(dc); 2469 controller.fadeWindowToken(true); 2470 } else { 2471 // Reparent the SurfaceControl of nav bar token back. 2472 t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl()); 2473 } 2474 2475 // To apply transactions. 2476 dc.mWmService.scheduleAnimationLocked(); 2477 } 2478 sendLumaSamplingEnabledToStatusBarInternal(@onNull DisplayContent dc, boolean enabled)2479 private void sendLumaSamplingEnabledToStatusBarInternal(@NonNull DisplayContent dc, 2480 boolean enabled) { 2481 final StatusBarManagerInternal bar = dc.getDisplayPolicy().getStatusBarManagerInternal(); 2482 if (bar != null) { 2483 bar.setNavigationBarLumaSamplingEnabled(dc.getDisplayId(), enabled); 2484 } 2485 } 2486 reportStartReasonsToLogger()2487 private void reportStartReasonsToLogger() { 2488 // Record transition start in metrics logger. We just assume everything is "DRAWN" 2489 // at this point since splash-screen is a presentation (shell) detail. 2490 ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>(); 2491 for (int i = mParticipants.size() - 1; i >= 0; --i) { 2492 ActivityRecord r = mParticipants.valueAt(i).asActivityRecord(); 2493 if (r == null || !r.isVisibleRequested()) continue; 2494 int transitionReason = APP_TRANSITION_WINDOWS_DRAWN; 2495 // At this point, r is "ready", but if it's not "ALL ready" then it is probably only 2496 // ready due to starting-window. 2497 if (r.mStartingData instanceof SplashScreenStartingData && !r.mLastAllReadyAtSync) { 2498 transitionReason = APP_TRANSITION_SPLASH_SCREEN; 2499 } else if (r.isActivityTypeHomeOrRecents() && isTransientLaunch(r)) { 2500 transitionReason = APP_TRANSITION_RECENTS_ANIM; 2501 } 2502 reasons.put(r, transitionReason); 2503 } 2504 mController.mAtm.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting( 2505 reasons); 2506 } 2507 2508 @Override toString()2509 public String toString() { 2510 StringBuilder sb = new StringBuilder(64); 2511 sb.append("TransitionRecord{"); 2512 sb.append(Integer.toHexString(System.identityHashCode(this))); 2513 sb.append(" id=" + mSyncId); 2514 sb.append(" type=" + transitTypeToString(mType)); 2515 sb.append(" flags=0x" + Integer.toHexString(mFlags)); 2516 if (mOverrideOptions != null) { 2517 sb.append(" overrideAnimOptions=" + mOverrideOptions); 2518 } 2519 if (mOverrideBackgroundColor != 0) { 2520 sb.append(" overrideBackgroundColor=" + mOverrideBackgroundColor); 2521 } 2522 if (!mChanges.isEmpty()) { 2523 sb.append(" c=["); 2524 for (int i = 0; i < mChanges.size(); i++) { 2525 sb.append("\n").append(" ").append(mChanges.valueAt(i).toString()); 2526 } 2527 sb.append("\n]\n"); 2528 } 2529 sb.append('}'); 2530 return sb.toString(); 2531 } 2532 2533 /** Returns the parent that the remote animator can animate or control. */ getAnimatableParent(WindowContainer<?> wc)2534 private static WindowContainer<?> getAnimatableParent(WindowContainer<?> wc) { 2535 WindowContainer<?> parent = wc.getParent(); 2536 while (parent != null 2537 && (!parent.canCreateRemoteAnimationTarget() && !parent.isOrganized())) { 2538 parent = parent.getParent(); 2539 } 2540 return parent; 2541 } 2542 reportIfNotTop(WindowContainer wc)2543 private static boolean reportIfNotTop(WindowContainer wc) { 2544 // Organized tasks need to be reported anyways because Core won't show() their surfaces 2545 // and we can't rely on onTaskAppeared because it isn't in sync. 2546 // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN. 2547 return wc.isOrganized(); 2548 } 2549 isWallpaper(WindowContainer wc)2550 private static boolean isWallpaper(WindowContainer wc) { 2551 return wc.asWallpaperToken() != null; 2552 } 2553 isInputMethod(WindowContainer wc)2554 private static boolean isInputMethod(WindowContainer wc) { 2555 return wc.getWindowType() == TYPE_INPUT_METHOD; 2556 } 2557 occludesKeyguard(WindowContainer wc)2558 private static boolean occludesKeyguard(WindowContainer wc) { 2559 final ActivityRecord ar = wc.asActivityRecord(); 2560 if (ar != null) { 2561 return ar.canShowWhenLocked(); 2562 } 2563 final Task t = wc.asTask(); 2564 if (t != null) { 2565 // Get the top activity which was visible (since this is going away, it will remain 2566 // client visible until the transition is finished). 2567 // skip hidden (or about to hide) apps 2568 final ActivityRecord top = t.getActivity(WindowToken::isClientVisible); 2569 return top != null && top.canShowWhenLocked(); 2570 } 2571 return false; 2572 } 2573 isTranslucent(@onNull WindowContainer wc)2574 private static boolean isTranslucent(@NonNull WindowContainer wc) { 2575 final TaskFragment taskFragment = wc.asTaskFragment(); 2576 if (taskFragment == null) { 2577 return !wc.fillsParent(); 2578 } 2579 2580 // Check containers differently as they are affected by child visibility. 2581 2582 if (taskFragment.isTranslucentForTransition()) { 2583 // TaskFragment doesn't contain occluded ActivityRecord. 2584 return true; 2585 } 2586 if (!taskFragment.hasAdjacentTaskFragment()) { 2587 // Non-filling without adjacent is considered as translucent. 2588 return !wc.fillsParent(); 2589 } 2590 // When the TaskFragment has an adjacent TaskFragment, sibling behind them should be 2591 // hidden unless any of them are translucent. 2592 return taskFragment.forOtherAdjacentTaskFragments(TaskFragment::isTranslucentForTransition); 2593 } 2594 updatePriorVisibility()2595 private void updatePriorVisibility() { 2596 for (int i = 0; i < mChanges.size(); ++i) { 2597 final ChangeInfo chg = mChanges.valueAt(i); 2598 // For task/activity, recalculate the current "real" visibility. 2599 if (chg.mContainer.asActivityRecord() == null && chg.mContainer.asTask() == null) { 2600 continue; 2601 } 2602 // This ONLY works in the visible -> invisible case (and is only needed for this case) 2603 // because commitVisible(false) is deferred until finish. 2604 if (!chg.mVisible) continue; 2605 chg.mVisible = chg.mContainer.isVisible(); 2606 } 2607 } 2608 2609 /** 2610 * Under some conditions (eg. all visible targets within a parent container are transitioning 2611 * the same way) the transition can be "promoted" to the parent container. This means an 2612 * animation can play just on the parent rather than all the individual children. 2613 * 2614 * @return {@code true} if transition in target can be promoted to its parent. 2615 */ canPromote(ChangeInfo targetChange, Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)2616 private static boolean canPromote(ChangeInfo targetChange, Targets targets, 2617 ArrayMap<WindowContainer, ChangeInfo> changes) { 2618 final WindowContainer<?> target = targetChange.mContainer; 2619 final WindowContainer<?> parent = target.getParent(); 2620 final ChangeInfo parentChange = changes.get(parent); 2621 if (!parent.canCreateRemoteAnimationTarget() 2622 || parentChange == null || !parentChange.hasChanged()) { 2623 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s", 2624 "parent can't be target " + parent); 2625 return false; 2626 } 2627 if (isWallpaper(target)) { 2628 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: is wallpaper"); 2629 return false; 2630 } 2631 2632 if (targetChange.mStartParent != null && target.getParent() != targetChange.mStartParent) { 2633 // When a window is reparented, the state change won't fit into any of the parents. 2634 // Don't promote such change so that we can animate the reparent if needed. 2635 return false; 2636 } 2637 2638 final @TransitionInfo.TransitionMode int mode = targetChange.getTransitMode(target); 2639 for (int i = parent.getChildCount() - 1; i >= 0; --i) { 2640 final WindowContainer<?> sibling = parent.getChildAt(i); 2641 if (target == sibling) continue; 2642 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s", 2643 sibling); 2644 final ChangeInfo siblingChange = changes.get(sibling); 2645 if (siblingChange == null || !targets.wasParticipated(siblingChange)) { 2646 if (sibling.isVisibleRequested()) { 2647 // Sibling is visible but not animating, so no promote. 2648 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 2649 " SKIP: sibling is visible but not part of transition"); 2650 return false; 2651 } 2652 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 2653 " unrelated invisible sibling %s", sibling); 2654 continue; 2655 } 2656 2657 final int siblingMode = siblingChange.getTransitMode(sibling); 2658 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 2659 " sibling is a participant with mode %s", 2660 TransitionInfo.modeToString(siblingMode)); 2661 if (reduceMode(mode) != reduceMode(siblingMode)) { 2662 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 2663 " SKIP: common mode mismatch. was %s", 2664 TransitionInfo.modeToString(mode)); 2665 return false; 2666 } 2667 } 2668 return true; 2669 } 2670 2671 /** "reduces" a mode into a smaller set of modes that uniquely represents visibility change. */ 2672 @TransitionInfo.TransitionMode reduceMode(@ransitionInfo.TransitionMode int mode)2673 private static int reduceMode(@TransitionInfo.TransitionMode int mode) { 2674 switch (mode) { 2675 case TRANSIT_TO_BACK: return TRANSIT_CLOSE; 2676 case TRANSIT_TO_FRONT: return TRANSIT_OPEN; 2677 default: return mode; 2678 } 2679 } 2680 2681 /** 2682 * Go through topTargets and try to promote (see {@link #canPromote}) one of them. 2683 * 2684 * @param targets all targets that will be sent to the player. 2685 */ tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)2686 private static void tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes) { 2687 WindowContainer<?> lastNonPromotableParent = null; 2688 // Go through from the deepest target. 2689 for (int i = targets.mArray.size() - 1; i >= 0; --i) { 2690 final ChangeInfo targetChange = targets.mArray.valueAt(i); 2691 final WindowContainer<?> target = targetChange.mContainer; 2692 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", target); 2693 final WindowContainer<?> parent = target.getParent(); 2694 if (parent == lastNonPromotableParent) { 2695 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 2696 " SKIP: its sibling was rejected"); 2697 continue; 2698 } 2699 if (!canPromote(targetChange, targets, changes)) { 2700 lastNonPromotableParent = parent; 2701 continue; 2702 } 2703 if (reportIfNotTop(target)) { 2704 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 2705 " keep as target %s", target); 2706 } else if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0) { 2707 // config-at-end activities do not match the end-state, so they should be treated 2708 // as independent. 2709 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 2710 " keep as cfg-at-end target %s", target); 2711 } else { 2712 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 2713 " remove from targets %s", target); 2714 targets.remove(i); 2715 } 2716 final ChangeInfo parentChange = changes.get(parent); 2717 if (targets.mArray.indexOfValue(parentChange) < 0) { 2718 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 2719 " CAN PROMOTE: promoting to parent %s", parent); 2720 // The parent has lower depth, so it will be checked in the later iteration. 2721 i++; 2722 targets.add(parentChange); 2723 } 2724 if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_NO_ANIMATION) != 0) { 2725 parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION; 2726 } else { 2727 parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION; 2728 } 2729 } 2730 } 2731 2732 /** 2733 * Find WindowContainers to be animated from a set of opening and closing apps. We will promote 2734 * animation targets to higher level in the window hierarchy if possible. 2735 */ 2736 @VisibleForTesting 2737 @NonNull calculateTargets(ArraySet<WindowContainer> participants, ArrayMap<WindowContainer, ChangeInfo> changes)2738 static ArrayList<ChangeInfo> calculateTargets(ArraySet<WindowContainer> participants, 2739 ArrayMap<WindowContainer, ChangeInfo> changes) { 2740 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 2741 "Start calculating TransitionInfo based on participants: %s", participants); 2742 2743 // Add all valid participants to the target container. 2744 final Targets targets = new Targets(); 2745 for (int i = participants.size() - 1; i >= 0; --i) { 2746 final WindowContainer<?> wc = participants.valueAt(i); 2747 if (!wc.isAttached()) { 2748 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 2749 " Rejecting as detached: %s", wc); 2750 continue; 2751 } 2752 // The level of transition target should be at least window token. 2753 if (wc.asWindowState() != null) continue; 2754 2755 final ChangeInfo changeInfo = changes.get(wc); 2756 // Reject no-ops 2757 if (!changeInfo.hasChanged()) { 2758 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 2759 " Rejecting as no-op: %s vis: %b", wc, wc.isVisibleRequested()); 2760 continue; 2761 } 2762 targets.add(changeInfo); 2763 } 2764 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " Initial targets: %s", 2765 targets.mArray); 2766 // Combine the targets from bottom to top if possible. 2767 tryPromote(targets, changes); 2768 // Establish the relationship between the targets and their top changes. 2769 populateParentChanges(targets, changes); 2770 2771 final ArrayList<ChangeInfo> targetList = targets.getListSortedByZ(); 2772 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " Final targets: %s", targetList); 2773 return targetList; 2774 } 2775 2776 /** Populates parent to the change info and collects intermediate targets. */ populateParentChanges(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)2777 private static void populateParentChanges(Targets targets, 2778 ArrayMap<WindowContainer, ChangeInfo> changes) { 2779 final ArrayList<ChangeInfo> intermediates = new ArrayList<>(); 2780 // Make a copy to iterate because the original array may be modified. 2781 final ArrayList<ChangeInfo> targetList = new ArrayList<>(targets.mArray.size()); 2782 for (int i = targets.mArray.size() - 1; i >= 0; --i) { 2783 targetList.add(targets.mArray.valueAt(i)); 2784 } 2785 for (int i = targetList.size() - 1; i >= 0; --i) { 2786 final ChangeInfo targetChange = targetList.get(i); 2787 final WindowContainer wc = targetChange.mContainer; 2788 // Wallpaper must belong to the top (regardless of how nested it is in DisplayAreas). 2789 final boolean skipIntermediateReports = isWallpaper(wc); 2790 intermediates.clear(); 2791 boolean foundParentInTargets = false; 2792 // Collect the intermediate parents between target and top changed parent. 2793 for (WindowContainer<?> p = getAnimatableParent(wc); p != null; 2794 p = getAnimatableParent(p)) { 2795 final ChangeInfo parentChange = changes.get(p); 2796 if (parentChange == null) { 2797 break; 2798 } 2799 if (!parentChange.hasChanged()) { 2800 // In case the target is collected after the parent has been changed, it could 2801 // be too late to snapshot the parent change. Skip to see if there is any 2802 // parent window further up to be considered as change parent. 2803 continue; 2804 } 2805 if (p.mRemoteToken == null) { 2806 // Intermediate parents must be those that has window to be managed by Shell. 2807 continue; 2808 } 2809 if (parentChange.mEndParent != null && !skipIntermediateReports) { 2810 targetChange.mEndParent = p; 2811 // The chain above the parent was processed. 2812 break; 2813 } 2814 if (targetList.contains(parentChange)) { 2815 if (skipIntermediateReports) { 2816 targetChange.mEndParent = p; 2817 } else { 2818 intermediates.add(parentChange); 2819 } 2820 foundParentInTargets = true; 2821 break; 2822 } else if (reportIfNotTop(p) && !skipIntermediateReports) { 2823 intermediates.add(parentChange); 2824 } 2825 } 2826 if (!foundParentInTargets || intermediates.isEmpty()) continue; 2827 // Add any always-report parents along the way. 2828 targetChange.mEndParent = intermediates.get(0).mContainer; 2829 for (int j = 0; j < intermediates.size() - 1; j++) { 2830 final ChangeInfo intermediate = intermediates.get(j); 2831 intermediate.mEndParent = intermediates.get(j + 1).mContainer; 2832 targets.add(intermediate); 2833 } 2834 } 2835 } 2836 2837 /** 2838 * Gets the leash surface for a window container. 2839 * @param t a transaction to create leashes on when necessary (fixed rotation at token-level). 2840 * If t is null, then this will not create any leashes, just use one if it is there -- 2841 * this is relevant for building the finishTransaction since it needs to match the 2842 * start state and not erroneously create a leash of its own. 2843 */ getLeashSurface(WindowContainer wc, @Nullable SurfaceControl.Transaction t)2844 private static SurfaceControl getLeashSurface(WindowContainer wc, 2845 @Nullable SurfaceControl.Transaction t) { 2846 final DisplayContent asDC = wc.asDisplayContent(); 2847 if (asDC != null) { 2848 // DisplayContent is the "root", so we use the windowing layer instead to avoid 2849 // hardware-screen-level surfaces. 2850 return asDC.getWindowingLayer(); 2851 } 2852 if (!wc.mTransitionController.useShellTransitionsRotation()) { 2853 final WindowToken asToken = wc.asWindowToken(); 2854 if (asToken != null) { 2855 // WindowTokens can have a fixed-rotation applied to them. In the current 2856 // implementation this fact is hidden from the player, so we must create a leash. 2857 final SurfaceControl leash = t != null ? asToken.getOrCreateFixedRotationLeash(t) 2858 : asToken.getFixedRotationLeash(); 2859 if (leash != null) return leash; 2860 } 2861 } 2862 return wc.getSurfaceControl(); 2863 } 2864 getOrigParentSurface(WindowContainer wc)2865 private static SurfaceControl getOrigParentSurface(WindowContainer wc) { 2866 if (wc.asDisplayContent() != null) { 2867 // DisplayContent is the "root", so we reinterpret it's wc as the window layer 2868 // making the parent surface the displaycontent's surface. 2869 return wc.getSurfaceControl(); 2870 } 2871 return wc.getParent().getSurfaceControl(); 2872 } 2873 2874 /** 2875 * A ready group is defined by a root window-container where all transitioning windows under 2876 * it are expected to animate together as a group. At the moment, this treats each display as 2877 * a ready-group to match the existing legacy transition behavior. 2878 */ isReadyGroup(WindowContainer wc)2879 private static boolean isReadyGroup(WindowContainer wc) { 2880 return wc instanceof DisplayContent; 2881 } 2882 getDisplayId(@onNull WindowContainer wc)2883 private static int getDisplayId(@NonNull WindowContainer wc) { 2884 return wc.getDisplayContent() != null 2885 ? wc.getDisplayContent().getDisplayId() : INVALID_DISPLAY; 2886 } 2887 2888 @VisibleForTesting calculateTransitionRoots(@onNull TransitionInfo outInfo, ArrayList<ChangeInfo> sortedTargets, @NonNull SurfaceControl.Transaction startT)2889 static void calculateTransitionRoots(@NonNull TransitionInfo outInfo, 2890 ArrayList<ChangeInfo> sortedTargets, 2891 @NonNull SurfaceControl.Transaction startT) { 2892 // There needs to be a root on each display. 2893 for (int i = 0; i < sortedTargets.size(); ++i) { 2894 final WindowContainer<?> wc = sortedTargets.get(i).mContainer; 2895 // Don't include wallpapers since they are in a different DA. 2896 if (isWallpaper(wc)) continue; 2897 final DisplayContent dc = wc.getDisplayContent(); 2898 if (dc == null) continue; 2899 final int endDisplayId = dc.getDisplayId(); 2900 2901 // Check if Root was already created for this display with a higher-Z window 2902 if (outInfo.findRootIndex(endDisplayId) >= 0) continue; 2903 2904 WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, wc); 2905 2906 // Make leash based on highest (z-order) direct child of ancestor with a participant. 2907 // Check whether the ancestor is belonged to last parent, shouldn't happen. 2908 final boolean hasReparent = !wc.isDescendantOf(ancestor); 2909 WindowContainer leashReference = wc; 2910 if (hasReparent) { 2911 Slog.e(TAG, "Did not find common ancestor! Ancestor= " + ancestor 2912 + " target= " + wc); 2913 } else { 2914 while (leashReference.getParent() != ancestor) { 2915 leashReference = leashReference.getParent(); 2916 } 2917 } 2918 if (wc == leashReference 2919 && sortedTargets.get(i).mWindowingMode == WINDOWING_MODE_PINNED) { 2920 // If a PiP task is the only target, we wanna make sure the transition root leash 2921 // is at the top in case PiP is sent to back. This is done because a pinned task is 2922 // meant to be always-on-top throughout a transition. 2923 leashReference = ancestor.getTopChild(); 2924 } 2925 final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName( 2926 "Transition Root: " + leashReference.getName()) 2927 .setCallsite("Transition.calculateTransitionRoots").build(); 2928 rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots"); 2929 // Update layers to start transaction because we prevent assignment during collect, so 2930 // the layer of transition root can be correct. 2931 assignLayers(dc, startT); 2932 startT.setLayer(rootLeash, leashReference.getLastLayer()); 2933 outInfo.addRootLeash(endDisplayId, rootLeash, 2934 ancestor.getBounds().left, ancestor.getBounds().top); 2935 } 2936 } 2937 2938 /** 2939 * Construct a TransitionInfo object from a set of targets and changes. Also populates the 2940 * root surface. 2941 * @param sortedTargets The targets sorted by z-order from top (index 0) to bottom. 2942 * @param startT The start transaction - used to set-up new leashes. 2943 */ 2944 @VisibleForTesting 2945 @NonNull calculateTransitionInfo(@ransitionType int type, int flags, ArrayList<ChangeInfo> sortedTargets, @NonNull SurfaceControl.Transaction startT)2946 static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags, 2947 ArrayList<ChangeInfo> sortedTargets, 2948 @NonNull SurfaceControl.Transaction startT) { 2949 final TransitionInfo out = new TransitionInfo(type, flags); 2950 calculateTransitionRoots(out, sortedTargets, startT); 2951 if (out.getRootCount() == 0) { 2952 return out; 2953 } 2954 2955 final AnimationOptions animOptionsForActivityTransition = 2956 calculateAnimationOptionsForActivityTransition(type, sortedTargets); 2957 2958 final ArraySet<WindowContainer> occludedAtEndContainers = new ArraySet<>(); 2959 // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order. 2960 final int count = sortedTargets.size(); 2961 for (int i = 0; i < count; ++i) { 2962 final ChangeInfo info = sortedTargets.get(i); 2963 final WindowContainer target = info.mContainer; 2964 final TransitionInfo.Change change = new TransitionInfo.Change( 2965 target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken() 2966 : null, getLeashSurface(target, startT)); 2967 // TODO(shell-transitions): Use leash for non-organized windows. 2968 if (info.mEndParent != null) { 2969 change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken()); 2970 } 2971 if (info.mStartParent != null && info.mStartParent.mRemoteToken != null 2972 && target.getParent() != info.mStartParent) { 2973 change.setLastParent(info.mStartParent.mRemoteToken.toWindowContainerToken()); 2974 } 2975 change.setMode(info.getTransitMode(target)); 2976 info.mReadyMode = change.getMode(); 2977 change.setStartAbsBounds(info.mAbsoluteBounds); 2978 change.setFlags(info.getChangeFlags(target)); 2979 info.mReadyFlags = change.getFlags(); 2980 change.setDisplayId(info.mDisplayId, getDisplayId(target)); 2981 2982 // Add FLAGS_IS_OCCLUDED to preventing from visible-translucent change which belows 2983 // the non-translucent change playing unexpected open animation. 2984 if (change.getMode() == TRANSIT_TO_FRONT || change.getMode() == TRANSIT_OPEN) { 2985 for (int occIndex = occludedAtEndContainers.size() - 1; occIndex >= 0; --occIndex) { 2986 if (target.isDescendantOf(occludedAtEndContainers.valueAt(occIndex))) { 2987 change.setFlags(change.getFlags() | FLAG_IS_OCCLUDED); 2988 break; 2989 } 2990 } 2991 } 2992 if (!change.hasFlags(FLAG_TRANSLUCENT) && (change.getMode() == TRANSIT_OPEN 2993 || change.getMode() == TRANSIT_TO_FRONT 2994 || change.getMode() == TRANSIT_CHANGE)) { 2995 occludedAtEndContainers.add(target.getParent()); 2996 } 2997 2998 final Task task = target.asTask(); 2999 final TaskFragment taskFragment = target.asTaskFragment(); 3000 final boolean isEmbeddedTaskFragment = taskFragment != null 3001 && taskFragment.isEmbedded(); 3002 final IBinder taskFragmentToken = 3003 taskFragment != null ? taskFragment.getFragmentToken() : null; 3004 change.setTaskFragmentToken(taskFragmentToken); 3005 final ActivityRecord activityRecord = target.asActivityRecord(); 3006 3007 if (task != null) { 3008 final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo(); 3009 task.fillTaskInfo(tinfo); 3010 change.setTaskInfo(tinfo); 3011 change.setRotationAnimation(getTaskRotationAnimation(task)); 3012 final ActivityRecord topRunningActivity = task.topRunningActivity(); 3013 if (topRunningActivity != null) { 3014 if (topRunningActivity.info.supportsPictureInPicture()) { 3015 change.setAllowEnterPip( 3016 topRunningActivity.checkEnterPictureInPictureAppOpsState()); 3017 } 3018 setEndFixedRotationIfNeeded(change, task, topRunningActivity); 3019 } 3020 } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) { 3021 change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS); 3022 } 3023 3024 final WindowContainer<?> parent = target.getParent(); 3025 final Rect bounds = target.getBounds(); 3026 final Rect parentBounds = parent.getBounds(); 3027 change.setEndRelOffset(bounds.left - parentBounds.left, 3028 bounds.top - parentBounds.top); 3029 change.setEndParentSize(parentBounds.width(), parentBounds.height()); 3030 int endRotation = target.getWindowConfiguration().getRotation(); 3031 if (activityRecord != null) { 3032 // TODO(b/227427984): Shell needs to aware letterbox. 3033 // Always use parent bounds of activity because letterbox area (e.g. fixed aspect 3034 // ratio or size compat mode) should be included in the animation. 3035 change.setEndAbsBounds(parentBounds); 3036 if (activityRecord.getRelativeDisplayRotation() != 0 3037 && !activityRecord.mTransitionController.useShellTransitionsRotation()) { 3038 // Use parent rotation because shell doesn't know the surface is rotated. 3039 endRotation = parent.getWindowConfiguration().getRotation(); 3040 } 3041 } else if (isWallpaper(target) && target.mWmService.mFlags.mEnsureWallpaperInTransitions 3042 && target.getRelativeDisplayRotation() != 0 3043 && !target.mTransitionController.useShellTransitionsRotation()) { 3044 // If the wallpaper is "fixed-rotated", shell is unaware of this, so use the 3045 // "as-if-not-rotating" bounds and rotation 3046 change.setEndAbsBounds(parent.getBounds()); 3047 endRotation = parent.getWindowConfiguration().getRotation(); 3048 } else { 3049 change.setEndAbsBounds(bounds); 3050 } 3051 3052 if (activityRecord != null || isEmbeddedTaskFragment) { 3053 final int backgroundColor; 3054 final TaskFragment organizedTf = activityRecord != null 3055 ? activityRecord.getOrganizedTaskFragment() 3056 : taskFragment.getOrganizedTaskFragment(); 3057 if (organizedTf != null && organizedTf.getAnimationParams() 3058 .getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) { 3059 // This window is embedded and has an animation background color set on the 3060 // TaskFragment. Pass this color with this window, so the handler can use it as 3061 // the animation background color if needed, 3062 backgroundColor = organizedTf.getAnimationParams() 3063 .getAnimationBackgroundColor(); 3064 } else { 3065 // Set background color to Task theme color for activity and embedded 3066 // TaskFragment in case we want to show background during the animation. 3067 final Task parentTask = activityRecord != null 3068 ? activityRecord.getTask() 3069 : taskFragment.getTask(); 3070 backgroundColor = parentTask.getTaskDescription().getBackgroundColor(); 3071 } 3072 // Set to opaque for animation background to prevent it from exposing the blank 3073 // background or content below. 3074 change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255)); 3075 } 3076 3077 AnimationOptions animOptions = null; 3078 if (activityRecord != null && animOptionsForActivityTransition != null) { 3079 animOptions = animOptionsForActivityTransition; 3080 } else if (isEmbeddedTaskFragment) { 3081 final TaskFragmentAnimationParams params = taskFragment.getAnimationParams(); 3082 if (params.hasOverrideAnimation()) { 3083 // Only set AnimationOptions if there's any animation override. 3084 animOptions = AnimationOptions.makeCustomAnimOptions( 3085 taskFragment.getTask().getBasePackageName(), 3086 params.getOpenAnimationResId(), params.getChangeAnimationResId(), 3087 params.getCloseAnimationResId(), false /* overrideTaskTransition */); 3088 animOptions.setUserId(taskFragment.getTask().mUserId); 3089 } 3090 } 3091 if (animOptions != null) { 3092 change.setAnimationOptions(animOptions); 3093 } 3094 3095 if (activityRecord != null) { 3096 change.setActivityComponent(activityRecord.mActivityComponent); 3097 } 3098 3099 change.setRotation(info.mRotation, endRotation); 3100 if (info.mSnapshot != null) { 3101 change.setSnapshot(info.mSnapshot, info.mSnapshotLuma); 3102 } 3103 3104 out.addChange(change); 3105 } 3106 return out; 3107 } 3108 3109 /** 3110 * Calculates {@link AnimationOptions} for activity-to-activity transition. 3111 * It returns a valid {@link AnimationOptions} if: 3112 * <ul> 3113 * <li>the top animation target is an Activity</li> 3114 * <li>there's a {@link android.view.Window#setWindowAnimations(int)} and there's only 3115 * {@link WindowState}, {@link WindowToken} and {@link ActivityRecord} target</li> 3116 * </ul> 3117 * Otherwise, it returns {@code null}. 3118 */ 3119 @Nullable calculateAnimationOptionsForActivityTransition( @ransitionType int type, @NonNull ArrayList<ChangeInfo> sortedTargets)3120 private static AnimationOptions calculateAnimationOptionsForActivityTransition( 3121 @TransitionType int type, @NonNull ArrayList<ChangeInfo> sortedTargets) { 3122 TransitionInfo.AnimationOptions animOptions = null; 3123 3124 // Check if the top-most app is an activity (ie. activity->activity). If so, make sure 3125 // to honor its custom transition options. 3126 WindowContainer<?> topApp = null; 3127 for (int i = 0; i < sortedTargets.size(); i++) { 3128 if (isWallpaper(sortedTargets.get(i).mContainer)) continue; 3129 topApp = sortedTargets.get(i).mContainer; 3130 break; 3131 } 3132 if (topApp.asActivityRecord() != null) { 3133 final ActivityRecord topActivity = topApp.asActivityRecord(); 3134 animOptions = addCustomActivityTransition(topActivity, true/* open */, 3135 null /* animOptions */); 3136 animOptions = addCustomActivityTransition(topActivity, false/* open */, 3137 animOptions); 3138 } 3139 final ActivityRecord animLpActivity = 3140 findAnimLayoutParamsActivityRecord(type, sortedTargets); 3141 final WindowState mainWindow = animLpActivity != null 3142 ? animLpActivity.findMainWindow() : null; 3143 WindowManager.LayoutParams animLp = mainWindow != null ? mainWindow.mAttrs : null; 3144 if (animLp != null && animLp.type != TYPE_APPLICATION_STARTING 3145 && animLp.windowAnimations != 0) { 3146 // Don't send animation options if no windowAnimations have been set or if the we 3147 // are running an app starting animation, in which case we don't want the app to be 3148 // able to change its animation directly. 3149 if (animOptions != null) { 3150 animOptions.addOptionsFromLayoutParameters(animLp); 3151 } else { 3152 animOptions = TransitionInfo.AnimationOptions 3153 .makeAnimOptionsFromLayoutParameters(animLp); 3154 animOptions.setUserId(animLpActivity.mUserId); 3155 } 3156 } 3157 return animOptions; 3158 } 3159 3160 /** 3161 * Returns {@link TransitionInfo.AnimationOptions} with custom Activity transition appended if 3162 * {@code topActivity} specifies {@link ActivityRecord#getCustomAnimation(boolean)}, or 3163 * {@code animOptions}, otherwise. 3164 * <p> 3165 * If the passed {@code animOptions} is {@code null}, this method will creates an 3166 * {@link TransitionInfo.AnimationOptions} with custom animation appended 3167 * 3168 * @param open {@code true} to add a custom open animation, and {@false} to add a close one 3169 */ 3170 @Nullable addCustomActivityTransition( @onNull ActivityRecord activity, boolean open, @Nullable TransitionInfo.AnimationOptions animOptions)3171 private static TransitionInfo.AnimationOptions addCustomActivityTransition( 3172 @NonNull ActivityRecord activity, boolean open, 3173 @Nullable TransitionInfo.AnimationOptions animOptions) { 3174 final ActivityRecord.CustomAppTransition customAnim = 3175 activity.getCustomAnimation(open); 3176 if (customAnim != null) { 3177 if (animOptions == null) { 3178 animOptions = TransitionInfo.AnimationOptions 3179 .makeCommonAnimOptions(activity.packageName); 3180 animOptions.setUserId(activity.mUserId); 3181 } 3182 animOptions.addCustomActivityTransition(open, customAnim.mEnterAnim, 3183 customAnim.mExitAnim, customAnim.mBackgroundColor); 3184 } 3185 return animOptions; 3186 } 3187 setEndFixedRotationIfNeeded(@onNull TransitionInfo.Change change, @NonNull Task task, @NonNull ActivityRecord taskTopRunning)3188 private static void setEndFixedRotationIfNeeded(@NonNull TransitionInfo.Change change, 3189 @NonNull Task task, @NonNull ActivityRecord taskTopRunning) { 3190 if (!taskTopRunning.isVisibleRequested()) { 3191 // Fixed rotation only applies to opening or changing activity. 3192 return; 3193 } 3194 if (!ActivityTaskManagerService.isPip2ExperimentEnabled() 3195 && task.inMultiWindowMode() && taskTopRunning.inMultiWindowMode()) { 3196 // Display won't be rotated for multi window Task, so the fixed rotation won't be 3197 // applied. This can happen when the windowing mode is changed before the previous 3198 // fixed rotation is applied. Check both task and activity because the activity keeps 3199 // fullscreen mode when the task is entering PiP. 3200 return; 3201 } 3202 final int taskRotation = task.getWindowConfiguration().getDisplayRotation(); 3203 final int activityRotation = taskTopRunning.getWindowConfiguration() 3204 .getDisplayRotation(); 3205 // If the Activity uses fixed rotation, its rotation will be applied to display after 3206 // the current transition is done, while the Task is still in the previous rotation. 3207 if (taskRotation != activityRotation) { 3208 change.setEndFixedRotation(activityRotation); 3209 return; 3210 } 3211 3212 // For example, the task is entering PiP so it no longer decides orientation. If the next 3213 // orientation source (it could be an activity which was behind the PiP or launching to top) 3214 // will change display rotation, then set the fixed rotation hint as well so the animation 3215 // can consider the rotated position. 3216 if (!task.inPinnedWindowingMode() || taskTopRunning.mDisplayContent.inTransition()) { 3217 return; 3218 } 3219 final WindowContainer<?> orientationSource = 3220 taskTopRunning.mDisplayContent.getLastOrientationSource(); 3221 if (orientationSource == null) { 3222 return; 3223 } 3224 final int nextRotation = orientationSource.getWindowConfiguration().getDisplayRotation(); 3225 if (taskRotation != nextRotation) { 3226 change.setEndFixedRotation(nextRotation); 3227 } 3228 } 3229 3230 /** 3231 * Finds the top-most common ancestor of app targets. 3232 * 3233 * Makes sure that the previous parent is also a descendant to make sure the animation won't 3234 * be covered by other windows below the previous parent. For example, when reparenting an 3235 * activity from PiP Task to split screen Task. 3236 */ 3237 @NonNull findCommonAncestor( @onNull ArrayList<ChangeInfo> targets, @NonNull WindowContainer<?> topApp)3238 private static WindowContainer<?> findCommonAncestor( 3239 @NonNull ArrayList<ChangeInfo> targets, 3240 @NonNull WindowContainer<?> topApp) { 3241 final int displayId = getDisplayId(topApp); 3242 WindowContainer<?> ancestor = topApp.getParent(); 3243 // Go up ancestor parent chain until all targets are descendants. Ancestor should never be 3244 // null because all targets are attached. 3245 for (int i = targets.size() - 1; i >= 0; i--) { 3246 final ChangeInfo change = targets.get(i); 3247 final WindowContainer wc = change.mContainer; 3248 if (isWallpaper(wc) || getDisplayId(wc) != displayId) { 3249 // Skip the non-app window or windows on a different display 3250 continue; 3251 } 3252 // Re-initiate the last parent as the initial ancestor instead of the top target. 3253 // When move a leaf task from organized task to display area, try to keep the transition 3254 // root be the original organized task for close transition animation. 3255 // Otherwise, shell will use wrong root layer to play animation. 3256 // Note: Since the target is sorted, so only need to do this at the lowest target. 3257 if (change.mStartParent != null && wc.getParent() != null 3258 && change.mStartParent.isAttached() && wc.getParent() != change.mStartParent 3259 && i == targets.size() - 1) { 3260 final int transitionMode = change.getTransitMode(wc); 3261 if (transitionMode == TRANSIT_CLOSE || transitionMode == TRANSIT_TO_BACK) { 3262 ancestor = change.mStartParent; 3263 continue; 3264 } 3265 } 3266 while (!wc.isDescendantOf(ancestor)) { 3267 ancestor = ancestor.getParent(); 3268 } 3269 3270 // Make sure the previous parent is also a descendant to make sure the animation won't 3271 // be covered by other windows below the previous parent. For example, when reparenting 3272 // an activity from PiP Task to split screen Task. 3273 final WindowContainer prevParent = change.mCommonAncestor; 3274 if (prevParent == null || !prevParent.isAttached()) { 3275 continue; 3276 } 3277 while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) { 3278 ancestor = ancestor.getParent(); 3279 } 3280 } 3281 return ancestor; 3282 } 3283 findAnimLayoutParamsActivityRecord(int type, ArrayList<ChangeInfo> sortedTargets)3284 private static ActivityRecord findAnimLayoutParamsActivityRecord(int type, 3285 ArrayList<ChangeInfo> sortedTargets) { 3286 // Find the layout params of the top-most application window that is part of the 3287 // transition, which is what will control the animation theme. 3288 final ArraySet<Integer> activityTypes = new ArraySet<>(); 3289 final int targetCount = sortedTargets.size(); 3290 for (int i = 0; i < targetCount; ++i) { 3291 final WindowContainer target = sortedTargets.get(i).mContainer; 3292 if (target.asActivityRecord() != null) { 3293 activityTypes.add(target.getActivityType()); 3294 } else if (target.asWindowToken() == null && target.asWindowState() == null) { 3295 // We don't want app to customize animations that are not activity to activity. 3296 // Activity-level transitions can only include activities, wallpaper and subwindows. 3297 // Anything else is not a WindowToken nor a WindowState and is "higher" in the 3298 // hierarchy which means we are no longer in an activity transition. 3299 return null; 3300 } 3301 } 3302 if (activityTypes.isEmpty()) { 3303 // We don't want app to be able to customize transitions that are not activity to 3304 // activity through the layout parameter animation style. 3305 return null; 3306 } 3307 return findAnimLayoutParamsActivityRecord(sortedTargets, type, activityTypes); 3308 } 3309 findAnimLayoutParamsActivityRecord( List<ChangeInfo> sortedTargets, @TransitionType int transit, ArraySet<Integer> activityTypes)3310 private static ActivityRecord findAnimLayoutParamsActivityRecord( 3311 List<ChangeInfo> sortedTargets, 3312 @TransitionType int transit, ArraySet<Integer> activityTypes) { 3313 // Remote animations always win, but fullscreen windows override non-fullscreen windows. 3314 ActivityRecord result = lookForTopWindowWithFilter(sortedTargets, 3315 w -> w.getRemoteAnimationDefinition() != null 3316 && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes)); 3317 if (result != null) { 3318 return result; 3319 } 3320 result = lookForTopWindowWithFilter(sortedTargets, 3321 w -> w.fillsParent() && w.findMainWindow() != null); 3322 if (result != null) { 3323 return result; 3324 } 3325 return lookForTopWindowWithFilter(sortedTargets, w -> w.findMainWindow() != null); 3326 } 3327 lookForTopWindowWithFilter(List<ChangeInfo> sortedTargets, Predicate<ActivityRecord> filter)3328 private static ActivityRecord lookForTopWindowWithFilter(List<ChangeInfo> sortedTargets, 3329 Predicate<ActivityRecord> filter) { 3330 final int count = sortedTargets.size(); 3331 for (int i = 0; i < count; ++i) { 3332 final WindowContainer target = sortedTargets.get(i).mContainer; 3333 final ActivityRecord activityRecord = target.asTaskFragment() != null 3334 ? target.asTaskFragment().getTopNonFinishingActivity() 3335 : target.asActivityRecord(); 3336 if (activityRecord != null && filter.test(activityRecord)) { 3337 return activityRecord; 3338 } 3339 } 3340 return null; 3341 } 3342 getTaskRotationAnimation(@onNull Task task)3343 private static int getTaskRotationAnimation(@NonNull Task task) { 3344 final ActivityRecord top = task.getTopVisibleActivity(); 3345 if (top == null) return ROTATION_ANIMATION_UNSPECIFIED; 3346 final WindowState mainWin = top.findMainWindow(false); 3347 if (mainWin == null) return ROTATION_ANIMATION_UNSPECIFIED; 3348 int anim = mainWin.getRotationAnimationHint(); 3349 if (anim >= 0) return anim; 3350 anim = mainWin.mAttrs.rotationAnimation; 3351 if (anim != ROTATION_ANIMATION_SEAMLESS) return anim; 3352 if (mainWin != task.mDisplayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow() 3353 || !top.matchParentBounds()) { 3354 // At the moment, we only support seamless rotation if there is only one window showing. 3355 return ROTATION_ANIMATION_UNSPECIFIED; 3356 } 3357 return mainWin.mAttrs.rotationAnimation; 3358 } 3359 validateKeyguardOcclusion()3360 private void validateKeyguardOcclusion() { 3361 if ((mFlags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) { 3362 mController.mStateValidators.add( 3363 mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange); 3364 } 3365 } 3366 3367 /** Returns {@code true} if the display should use high performance hint for this transition. */ shouldUsePerfHint(@onNull DisplayContent dc)3368 boolean shouldUsePerfHint(@NonNull DisplayContent dc) { 3369 if (mOverrideOptions != null 3370 && mOverrideOptions.getType() == ActivityOptions.ANIM_SCENE_TRANSITION 3371 && mType == TRANSIT_TO_BACK && mParticipants.size() == 1) { 3372 // This should be from convertFromTranslucent that makes the occluded activity invisible 3373 // without animation. So do not use perf hint (especially early-wakeup) that may disturb 3374 // SurfaceFlinger scheduling around the last frame. 3375 return false; 3376 } 3377 return mTargetDisplays.contains(dc); 3378 } 3379 3380 /** 3381 * Returns {@code true} if the transition and the corresponding transaction should be applied 3382 * on display thread. Currently, this only checks for display rotation change because the order 3383 * of dispatching the new display info will be after requesting the windows to sync drawing. 3384 * That avoids potential flickering of screen overlays (e.g. cutout, rounded corner). Also, 3385 * because the display thread has a higher priority, it is faster to perform the configuration 3386 * changes and window hierarchy traversal. 3387 */ shouldApplyOnDisplayThread()3388 boolean shouldApplyOnDisplayThread() { 3389 for (int i = mParticipants.size() - 1; i >= 0; --i) { 3390 final DisplayContent dc = mParticipants.valueAt(i).asDisplayContent(); 3391 if (dc == null) continue; 3392 final ChangeInfo changeInfo = mChanges.get(dc); 3393 if (changeInfo != null && changeInfo.mRotation != dc.getRotation()) { 3394 return Looper.myLooper() != mController.mAtm.mWindowManager.mH.getLooper(); 3395 } 3396 } 3397 return false; 3398 } 3399 3400 /** 3401 * Applies the new configuration for the changed displays. Returns the activities that should 3402 * check whether to deliver the new configuration to clients. 3403 */ applyDisplayChangeIfNeeded(@onNull ArraySet<WindowContainer<?>> activitiesMayChange)3404 void applyDisplayChangeIfNeeded(@NonNull ArraySet<WindowContainer<?>> activitiesMayChange) { 3405 for (int i = mParticipants.size() - 1; i >= 0; --i) { 3406 final WindowContainer<?> wc = mParticipants.valueAt(i); 3407 final DisplayContent dc = wc.asDisplayContent(); 3408 if (dc == null || !mChanges.get(dc).hasChanged()) continue; 3409 final boolean changed = dc.sendNewConfiguration(); 3410 // Set to ready if no other change controls the ready state. But if there is, such as 3411 // if an activity is pausing, it will call setReady(ar, false) and wait for the next 3412 // resumed activity. Then do not set to ready because the transition only contains 3413 // partial participants. Otherwise the transition may only handle HIDE and miss OPEN. 3414 if (!mReadyTrackerOld.mUsed) { 3415 setReady(dc, true); 3416 } 3417 if (!changed) continue; 3418 // If the update is deferred, sendNewConfiguration won't deliver new configuration to 3419 // clients, then it is the caller's responsibility to deliver the changes. 3420 if (mController.mAtm.mTaskSupervisor.isRootVisibilityUpdateDeferred()) { 3421 dc.forAllActivities(r -> { 3422 if (r.isVisibleRequested()) { 3423 activitiesMayChange.add(r); 3424 } 3425 }); 3426 } 3427 } 3428 } 3429 getLegacyIsReady()3430 boolean getLegacyIsReady() { 3431 return isCollecting() && mSyncId >= 0; 3432 } 3433 asyncTraceBegin(@onNull String name, int cookie)3434 static void asyncTraceBegin(@NonNull String name, int cookie) { 3435 Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, name, cookie); 3436 } 3437 asyncTraceEnd(int cookie)3438 static void asyncTraceEnd(int cookie) { 3439 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie); 3440 } 3441 3442 @Override onReadyTraceStart(String name, int id)3443 public void onReadyTraceStart(String name, int id) { 3444 asyncTraceBegin(name, id); 3445 } 3446 3447 @Override onReadyTraceEnd(String name, int id)3448 public void onReadyTraceEnd(String name, int id) { 3449 asyncTraceEnd(id); 3450 } 3451 hasChanged(WindowContainer wc)3452 boolean hasChanged(WindowContainer wc) { 3453 final ChangeInfo chg = mChanges.get(wc); 3454 if (chg == null) return false; 3455 return chg.hasChanged(); 3456 } 3457 hasChanges()3458 boolean hasChanges() { 3459 for (int i = 0; i < mParticipants.size(); ++i) { 3460 if (mChanges.get(mParticipants.valueAt(i)).hasChanged()) { 3461 return true; 3462 } 3463 } 3464 return false; 3465 } 3466 recordChain(@onNull ActionChain chain)3467 void recordChain(@NonNull ActionChain chain) { 3468 chain.mPrevious = mChainHead; 3469 mChainHead = chain; 3470 } 3471 3472 @VisibleForTesting 3473 static class ChangeInfo { 3474 private static final int FLAG_NONE = 0; 3475 3476 /** 3477 * When set, the associated WindowContainer has been explicitly requested to be a 3478 * seamless rotation. This is currently only used by DisplayContent during fixed-rotation. 3479 */ 3480 private static final int FLAG_SEAMLESS_ROTATION = 1; 3481 /** 3482 * Identifies the associated WindowContainer as a transient-launch task or activity. 3483 */ 3484 private static final int FLAG_TRANSIENT_LAUNCH = 2; 3485 /** 3486 * Identifies the associated WindowContainer as a transient-hide task or activity. 3487 */ 3488 private static final int FLAG_TRANSIENT_HIDE = 4; 3489 3490 /** This container explicitly requested no-animation (usually Activity level). */ 3491 private static final int FLAG_CHANGE_NO_ANIMATION = 0x8; 3492 /** 3493 * This container has at-least one child which IS animating (not marked NO_ANIMATION). 3494 * Used during promotion. This trumps `FLAG_NO_ANIMATION` (if both are set). 3495 */ 3496 private static final int FLAG_CHANGE_YES_ANIMATION = 0x10; 3497 3498 /** Whether this change's container moved to the top. */ 3499 private static final int FLAG_CHANGE_MOVED_TO_TOP = 0x20; 3500 3501 /** Whether this change contains config-at-end members. */ 3502 private static final int FLAG_CHANGE_CONFIG_AT_END = 0x40; 3503 3504 /** 3505 * Whether this change is forced participant transition because it is current top target 3506 * of predictive back animation. 3507 */ 3508 private static final int FLAG_BACK_GESTURE_ANIMATION = 0x80; 3509 3510 /** 3511 * Whether this change is forced participant transition because it is previous target of 3512 * predictive back animation 3513 */ 3514 private static final int FLAG_BELOW_BACK_GESTURE_ANIMATION = 0x100; 3515 3516 @IntDef(prefix = { "FLAG_" }, value = { 3517 FLAG_NONE, 3518 FLAG_SEAMLESS_ROTATION, 3519 FLAG_TRANSIENT_LAUNCH, 3520 FLAG_TRANSIENT_HIDE, 3521 FLAG_CHANGE_NO_ANIMATION, 3522 FLAG_CHANGE_YES_ANIMATION, 3523 FLAG_CHANGE_MOVED_TO_TOP, 3524 FLAG_CHANGE_CONFIG_AT_END, 3525 FLAG_BACK_GESTURE_ANIMATION, 3526 FLAG_BELOW_BACK_GESTURE_ANIMATION 3527 }) 3528 @Retention(RetentionPolicy.SOURCE) 3529 @interface ChangeInfoFlag {} 3530 3531 @NonNull final WindowContainer mContainer; 3532 /** 3533 * "Parent" that is also included in the transition. When populating the parent changes, we 3534 * may skip the intermediate parents, so this may not be the actual parent in the hierarchy. 3535 */ 3536 WindowContainer mEndParent; 3537 /** Actual parent window before change state. */ 3538 WindowContainer mStartParent; 3539 /** 3540 * When the window is reparented during the transition, this is the common ancestor window 3541 * of the {@link #mStartParent} and the current parent. This is needed because the 3542 * {@link #mStartParent} may have been detached when the transition starts. 3543 */ 3544 WindowContainer mCommonAncestor; 3545 3546 // State tracking 3547 boolean mExistenceChanged = false; 3548 // This state indicates that we are restoring transient order as a part of an 3549 // end-transition. Because the visibility for transient hide containers has not actually 3550 // changed, we need to ensure that hasChanged() still reports the relevant changes 3551 boolean mRestoringTransientHide = false; 3552 // before change state 3553 boolean mVisible; 3554 int mWindowingMode; 3555 final Rect mAbsoluteBounds = new Rect(); 3556 boolean mShowWallpaper; 3557 int mRotation = ROTATION_UNDEFINED; 3558 int mDisplayId = -1; 3559 @ActivityInfo.Config int mKnownConfigChanges; 3560 3561 /** Extra information about this change. */ 3562 @ChangeInfoFlag int mFlags = FLAG_NONE; 3563 3564 /** Snapshot surface and luma, if relevant. */ 3565 SurfaceControl mSnapshot; 3566 float mSnapshotLuma; 3567 3568 /** The mode which is set when the transition is ready. */ 3569 @TransitionInfo.TransitionMode 3570 int mReadyMode; 3571 3572 /** The flags which is set when the transition is ready. */ 3573 @TransitionInfo.ChangeFlags 3574 int mReadyFlags; 3575 ChangeInfo(@onNull WindowContainer origState)3576 ChangeInfo(@NonNull WindowContainer origState) { 3577 mContainer = origState; 3578 mVisible = origState.isVisibleRequested(); 3579 mWindowingMode = origState.getWindowingMode(); 3580 mAbsoluteBounds.set(origState.getBounds()); 3581 mShowWallpaper = origState.showWallpaper(); 3582 mRotation = origState.getWindowConfiguration().getRotation(); 3583 mStartParent = origState.getParent(); 3584 mDisplayId = getDisplayId(origState); 3585 } 3586 3587 @VisibleForTesting ChangeInfo(@onNull WindowContainer container, boolean visible, boolean existChange)3588 ChangeInfo(@NonNull WindowContainer container, boolean visible, boolean existChange) { 3589 this(container); 3590 mVisible = visible; 3591 mExistenceChanged = existChange; 3592 mShowWallpaper = false; 3593 } 3594 3595 @Override toString()3596 public String toString() { 3597 StringBuilder sb = new StringBuilder(64); 3598 sb.append("ChangeInfo{"); 3599 sb.append(Integer.toHexString(System.identityHashCode(this))); 3600 sb.append(" container=").append(mContainer); 3601 sb.append(" flags=0x").append(Integer.toHexString(mFlags)); 3602 sb.append('}'); 3603 return sb.toString(); 3604 } 3605 hasChanged()3606 boolean hasChanged() { 3607 final boolean currVisible = mContainer.isVisibleRequested(); 3608 // the task including transient launch must promote to root task 3609 if (currVisible && ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0 3610 || (mFlags & ChangeInfo.FLAG_TRANSIENT_HIDE) != 0) 3611 || (mFlags & ChangeInfo.FLAG_BACK_GESTURE_ANIMATION) != 0 3612 || (mFlags & ChangeInfo.FLAG_BELOW_BACK_GESTURE_ANIMATION) != 0) { 3613 return true; 3614 } 3615 // If it's invisible and hasn't changed visibility, always return false since even if 3616 // something changed, it wouldn't be a visible change. 3617 if (currVisible == mVisible && !mVisible) return false; 3618 return currVisible != mVisible 3619 || mKnownConfigChanges != 0 3620 // if mWindowingMode is 0, this container wasn't attached at collect time, so 3621 // assume no change in windowing-mode. 3622 || (mWindowingMode != 0 && mContainer.getWindowingMode() != mWindowingMode) 3623 || !mContainer.getBounds().equals(mAbsoluteBounds) 3624 || mRotation != mContainer.getWindowConfiguration().getRotation() 3625 || mDisplayId != getDisplayId(mContainer) 3626 || (mFlags & ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP) != 0 3627 // If we are restoring transient-hide containers, then we should consider them 3628 // important for the transition as well (their requested visibilities would not 3629 // have changed for the checks below to consider it). 3630 || mRestoringTransientHide; 3631 } 3632 3633 @TransitionInfo.TransitionMode getTransitMode(@onNull WindowContainer wc)3634 int getTransitMode(@NonNull WindowContainer wc) { 3635 if ((mFlags & ChangeInfo.FLAG_TRANSIENT_HIDE) != 0) { 3636 return mExistenceChanged ? TRANSIT_CLOSE : TRANSIT_TO_BACK; 3637 } 3638 if ((mFlags & ChangeInfo.FLAG_BELOW_BACK_GESTURE_ANIMATION) != 0) { 3639 return TRANSIT_TO_FRONT; 3640 } 3641 final boolean nowVisible = wc.isVisibleRequested(); 3642 if (nowVisible == mVisible) { 3643 return TRANSIT_CHANGE; 3644 } 3645 if (mExistenceChanged) { 3646 return nowVisible ? TRANSIT_OPEN : TRANSIT_CLOSE; 3647 } else { 3648 return nowVisible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK; 3649 } 3650 } 3651 3652 @TransitionInfo.ChangeFlags getChangeFlags(@onNull WindowContainer wc)3653 int getChangeFlags(@NonNull WindowContainer wc) { 3654 int flags = 0; 3655 if (mShowWallpaper || wc.showWallpaper()) { 3656 flags |= FLAG_SHOW_WALLPAPER; 3657 } 3658 if (isTranslucent(wc)) { 3659 flags |= FLAG_TRANSLUCENT; 3660 } 3661 if (wc.mWmService.mAtmService.mBackNavigationController.isMonitorTransitionTarget(wc)) { 3662 flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; 3663 } 3664 final TaskDisplayArea tda = wc.asTaskDisplayArea(); 3665 if (tda != null) { 3666 flags |= TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA; 3667 } 3668 final Task task = wc.asTask(); 3669 if (task != null) { 3670 final ActivityRecord topActivity = task.getTopNonFinishingActivity(); 3671 if (topActivity != null) { 3672 if (topActivity.mStartingData != null 3673 && topActivity.mStartingData.hasImeSurface()) { 3674 flags |= FLAG_WILL_IME_SHOWN; 3675 } 3676 if (topActivity.mLaunchTaskBehind 3677 && !topActivity.isAnimating(PARENTS, ANIMATION_TYPE_PREDICT_BACK)) { 3678 Slog.e(TAG, "Unexpected launch-task-behind operation in shell transition"); 3679 flags |= FLAG_TASK_LAUNCHING_BEHIND; 3680 } 3681 if ((topActivity.mTransitionChangeFlags & FLAGS_IS_OCCLUDED_NO_ANIMATION) 3682 == FLAGS_IS_OCCLUDED_NO_ANIMATION) { 3683 flags |= FLAGS_IS_OCCLUDED_NO_ANIMATION; 3684 } 3685 } 3686 if (task.voiceSession != null) { 3687 flags |= FLAG_IS_VOICE_INTERACTION; 3688 } 3689 } 3690 Task parentTask = null; 3691 final ActivityRecord record = wc.asActivityRecord(); 3692 if (record != null) { 3693 parentTask = record.getTask(); 3694 if (record.mVoiceInteraction) { 3695 flags |= FLAG_IS_VOICE_INTERACTION; 3696 } 3697 flags |= record.mTransitionChangeFlags; 3698 if (record.isConfigurationDispatchPaused()) { 3699 flags |= FLAG_CONFIG_AT_END; 3700 } 3701 } 3702 final TaskFragment taskFragment = wc.asTaskFragment(); 3703 if (taskFragment != null && task == null) { 3704 parentTask = taskFragment.getTask(); 3705 } 3706 if (parentTask != null) { 3707 if (parentTask.forAllLeafTaskFragments(TaskFragment::isEmbedded)) { 3708 // Whether this is in a Task with embedded activity. 3709 flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; 3710 } 3711 final ActivityRecord starting = parentTask.topActivityContainsStartingWindow(); 3712 if (starting != null) { 3713 if (starting == record || (starting.mStartingData != null 3714 && starting.mStartingData.mAssociatedTask != null)) { 3715 flags |= FLAG_IS_BEHIND_STARTING_WINDOW; 3716 } else if (record != null && parentTask.mChildren.indexOf(record) 3717 < parentTask.mChildren.indexOf(starting)) { 3718 flags |= FLAG_IS_BEHIND_STARTING_WINDOW; 3719 } 3720 } 3721 if (isWindowFillingTask(wc, parentTask)) { 3722 // Whether the container fills its parent Task bounds. 3723 flags |= FLAG_FILLS_TASK; 3724 } 3725 } else { 3726 final DisplayContent dc = wc.asDisplayContent(); 3727 if (dc != null) { 3728 flags |= FLAG_IS_DISPLAY; 3729 if (dc.hasAlertWindowSurfaces()) { 3730 flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS; 3731 } 3732 } else if (isWallpaper(wc)) { 3733 flags |= FLAG_IS_WALLPAPER; 3734 } else if (isInputMethod(wc)) { 3735 flags |= FLAG_IS_INPUT_METHOD; 3736 } else { 3737 // In this condition, the wc can only be WindowToken or DisplayArea. 3738 final int type = wc.getWindowType(); 3739 if (type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW 3740 && type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) { 3741 flags |= TransitionInfo.FLAG_IS_SYSTEM_WINDOW; 3742 } 3743 } 3744 } 3745 if ((mFlags & FLAG_CHANGE_NO_ANIMATION) != 0 3746 && (mFlags & FLAG_CHANGE_YES_ANIMATION) == 0) { 3747 flags |= FLAG_NO_ANIMATION; 3748 } 3749 if ((mFlags & FLAG_CHANGE_MOVED_TO_TOP) != 0) { 3750 flags |= FLAG_MOVED_TO_TOP; 3751 } 3752 if ((mFlags & FLAG_CHANGE_CONFIG_AT_END) != 0) { 3753 flags |= FLAG_CONFIG_AT_END; 3754 } 3755 return flags; 3756 } 3757 3758 /** Whether the container fills its parent Task bounds before and after the transition. */ isWindowFillingTask(@onNull WindowContainer wc, @NonNull Task parentTask)3759 private boolean isWindowFillingTask(@NonNull WindowContainer wc, @NonNull Task parentTask) { 3760 final Rect taskBounds = parentTask.getBounds(); 3761 final int taskWidth = taskBounds.width(); 3762 final int taskHeight = taskBounds.height(); 3763 final Rect startBounds = mAbsoluteBounds; 3764 final Rect endBounds = wc.getBounds(); 3765 // Treat it as filling the task if it is not visible. 3766 final boolean isInvisibleOrFillingTaskBeforeTransition = !mVisible 3767 || (taskWidth == startBounds.width() && taskHeight == startBounds.height()); 3768 final boolean isInVisibleOrFillingTaskAfterTransition = !wc.isVisibleRequested() 3769 || (taskWidth == endBounds.width() && taskHeight == endBounds.height()); 3770 return isInvisibleOrFillingTaskBeforeTransition 3771 && isInVisibleOrFillingTaskAfterTransition; 3772 } 3773 } 3774 3775 /** 3776 * This transition will be considered not-ready until a corresponding call to 3777 * {@link #continueTransitionReady} 3778 */ deferTransitionReady()3779 void deferTransitionReady() { 3780 ++mReadyTrackerOld.mDeferReadyDepth; 3781 // Make sure it wait until #continueTransitionReady() is called. 3782 mSyncEngine.setReady(mSyncId, false); 3783 } 3784 3785 /** This undoes one call to {@link #deferTransitionReady}. */ continueTransitionReady()3786 void continueTransitionReady() { 3787 --mReadyTrackerOld.mDeferReadyDepth; 3788 // Apply ready in case it is waiting for the previous defer call. 3789 applyReady(); 3790 } 3791 3792 @Override onReadyTimeout()3793 public void onReadyTimeout() { 3794 if (!mController.useFullReadyTracking()) { 3795 Slog.e(TAG, "#" + mSyncId + " readiness timeout, used=" + mReadyTrackerOld.mUsed 3796 + " deferReadyDepth=" + mReadyTrackerOld.mDeferReadyDepth 3797 + " group=" + mReadyTrackerOld.mReadyGroups); 3798 } else { 3799 Slog.e(TAG, "#" + mSyncId + " met conditions: " + mReadyTracker.mMet); 3800 Slog.e(TAG, "#" + mSyncId + " unmet conditions: " + mReadyTracker.mConditions); 3801 } 3802 // Make sure the pending display change can be applied (especially DC#mWaitingForConfig) 3803 // in case shell hasn't called WindowOrganizerController#startTransition yet. 3804 if (mState < STATE_STARTED && this == mController.getCollectingTransition()) { 3805 applyDisplayChangeIfNeeded(new ArraySet<>()); 3806 } 3807 } 3808 3809 /** 3810 * Represents a condition that must be met before an associated transition can be considered 3811 * ready. 3812 * 3813 * Expected usage is that a ReadyCondition is created and then attached to a transition's 3814 * ReadyTracker via {@link ReadyTracker#add}. After that, it is expected to monitor the state 3815 * of the system and when the condition it represents is met, it will call 3816 * {@link ReadyTracker#meet}. 3817 * 3818 * This base class is a simple explicit, named condition. A caller will create/attach the 3819 * condition and then explicitly call {@link #meet} on it (which internally calls 3820 * {@link ReadyTracker#meet}. 3821 * 3822 * Example: 3823 * <pre> 3824 * ReadyCondition myCondition = new ReadyCondition("my condition"); 3825 * transitionController.waitFor(myCondition); 3826 * ... Some operations ... 3827 * myCondition.meet(); 3828 * </pre> 3829 */ 3830 static class ReadyCondition { 3831 final String mName; 3832 3833 /** Just used for debugging */ 3834 final Object mDebugTarget; 3835 ReadyTracker mTracker; 3836 boolean mMet = false; 3837 3838 /** If set (non-null), then this is met by another reason besides state (eg. timeout). */ 3839 String mAlternate = null; 3840 ReadyCondition(@onNull String name)3841 ReadyCondition(@NonNull String name) { 3842 mName = name; 3843 mDebugTarget = null; 3844 } 3845 ReadyCondition(@onNull String name, @Nullable Object debugTarget)3846 ReadyCondition(@NonNull String name, @Nullable Object debugTarget) { 3847 mName = name; 3848 mDebugTarget = debugTarget; 3849 } 3850 getDebugRep()3851 protected String getDebugRep() { 3852 if (mDebugTarget != null) { 3853 return mName + ":" + mDebugTarget; 3854 } 3855 return mName; 3856 } 3857 3858 @Override toString()3859 public String toString() { 3860 return "{" + getDebugRep() + (mAlternate != null ? " (" + mAlternate + ")" : "") + "}"; 3861 } 3862 3863 /** 3864 * Instructs this condition to start tracking system state to detect when this is met. 3865 * Don't call this directly; it is called when this object is attached to a transition's 3866 * ready-tracker. 3867 */ startTracking()3868 void startTracking() { 3869 } 3870 3871 /** 3872 * Immediately consider this condition met by an alternative reason (one which doesn't 3873 * match the normal intent of this condition -- eg. a timeout). 3874 */ meetAlternate(@onNull String reason)3875 void meetAlternate(@NonNull String reason) { 3876 if (mMet) return; 3877 mAlternate = reason; 3878 meet(); 3879 } 3880 3881 /** Immediately consider this condition met. */ meet()3882 void meet() { 3883 if (mMet) return; 3884 if (mTracker == null) { 3885 throw new IllegalStateException("Can't meet a condition before it is waited on"); 3886 } 3887 mTracker.meet(this); 3888 } 3889 } 3890 3891 static class ReadyTracker { 3892 /** 3893 * Used as a place-holder in situations where the transition system isn't active (such as 3894 * early-boot, mid shell crash/recovery, or when using legacy). 3895 */ 3896 static final ReadyTracker NULL_TRACKER = new ReadyTracker(null); 3897 3898 private final Transition mTransition; 3899 3900 /** List of conditions that are still being waited on. */ 3901 final ArrayList<ReadyCondition> mConditions = new ArrayList<>(); 3902 3903 /** List of already-met conditions. Fully-qualified for debugging. */ 3904 final ArrayList<ReadyCondition> mMet = new ArrayList<>(); 3905 ReadyTracker(Transition transition)3906 ReadyTracker(Transition transition) { 3907 mTransition = transition; 3908 } 3909 add(@onNull ReadyCondition condition)3910 void add(@NonNull ReadyCondition condition) { 3911 if (mTransition == null || !mTransition.mController.useFullReadyTracking()) { 3912 condition.mTracker = NULL_TRACKER; 3913 return; 3914 } 3915 mConditions.add(condition); 3916 condition.mTracker = this; 3917 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " Add condition %s for #%d", 3918 condition, mTransition.mSyncId); 3919 condition.startTracking(); 3920 } 3921 meet(@onNull ReadyCondition condition)3922 void meet(@NonNull ReadyCondition condition) { 3923 if (mTransition == null || !mTransition.mController.useFullReadyTracking()) return; 3924 if (mTransition.mState >= STATE_PLAYING) { 3925 Slog.w(TAG, "#%d: Condition met too late, already in state=" + mTransition.mState 3926 + ": " + condition); 3927 return; 3928 } 3929 if (!mConditions.remove(condition)) { 3930 if (mMet.contains(condition)) { 3931 throw new IllegalStateException("Can't meet the same condition more than once: " 3932 + condition + " #" + mTransition.mSyncId); 3933 } else { 3934 throw new IllegalArgumentException("Can't meet a condition that isn't being " 3935 + "waited on: " + condition + " in #" + mTransition.mSyncId); 3936 } 3937 } 3938 condition.mMet = true; 3939 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " Met condition %s for #%d (%d" 3940 + " left)", condition, mTransition.mSyncId, mConditions.size()); 3941 mMet.add(condition); 3942 mTransition.applyReady(); 3943 } 3944 isReady()3945 boolean isReady() { 3946 return mConditions.isEmpty() && !mMet.isEmpty(); 3947 } 3948 } 3949 3950 /** 3951 * The transition sync mechanism has 2 parts: 3952 * 1. Whether all WM operations for a particular transition are "ready" (eg. did the app 3953 * launch or stop or get a new configuration?). 3954 * 2. Whether all the windows involved have finished drawing their final-state content. 3955 * 3956 * A transition animation can play once both parts are complete. This ready-tracker keeps track 3957 * of part (1). Currently, WM code assumes that "readiness" (part 1) is grouped. This means that 3958 * even if the WM operations in one group are ready, the whole transition itself may not be 3959 * ready if there are WM operations still pending in another group. This class helps keep track 3960 * of readiness across the multiple groups. Currently, we assume that each display is a group 3961 * since that is how it has been until now. 3962 */ 3963 private static class ReadyTrackerOld { 3964 private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>(); 3965 3966 /** 3967 * Ensures that this doesn't report as allReady before it has been used. This is needed 3968 * in very niche cases where a transition is a no-op (nothing has been collected) but we 3969 * still want to be marked ready (via. setAllReady). 3970 */ 3971 private boolean mUsed = false; 3972 3973 /** 3974 * If true, this overrides all ready groups and reports ready. Used by shell-initiated 3975 * transitions via {@link #setAllReady()}. 3976 */ 3977 private boolean mReadyOverride = false; 3978 3979 /** 3980 * When non-zero, this transition is forced not-ready (even over setAllReady()). Use this 3981 * (via deferTransitionReady/continueTransitionReady) for situations where we want to do 3982 * bulk operations which could trigger surface-placement but the existing ready-state 3983 * isn't known. 3984 */ 3985 private int mDeferReadyDepth = 0; 3986 3987 /** 3988 * Adds a ready-group. Any setReady calls in this subtree will be tracked together. For 3989 * now these are only DisplayContents. 3990 */ addGroup(WindowContainer wc)3991 void addGroup(WindowContainer wc) { 3992 if (mReadyGroups.containsKey(wc)) { 3993 return; 3994 } 3995 mReadyGroups.put(wc, false); 3996 } 3997 3998 /** 3999 * Sets a group's ready state. 4000 * @param wc Any container within a group's subtree. Used to identify the ready-group. 4001 */ setReadyFrom(WindowContainer wc, boolean ready)4002 void setReadyFrom(WindowContainer wc, boolean ready) { 4003 mUsed = true; 4004 WindowContainer current = wc; 4005 while (current != null) { 4006 if (isReadyGroup(current)) { 4007 mReadyGroups.put(current, ready); 4008 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 4009 " Setting Ready-group to %b. group=%s from %s", ready, current, wc); 4010 break; 4011 } 4012 current = current.getParent(); 4013 } 4014 } 4015 4016 /** Marks this as ready regardless of individual groups. */ setAllReady()4017 void setAllReady() { 4018 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " Setting allReady override"); 4019 mUsed = true; 4020 mReadyOverride = true; 4021 } 4022 4023 /** @return true if all tracked subtrees are ready. */ allReady()4024 boolean allReady() { 4025 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, 4026 " allReady query: used=%b override=%b defer=%d states=[%s]", mUsed, 4027 mReadyOverride, mDeferReadyDepth, groupsToString()); 4028 // If the readiness has never been touched, mUsed will be false. We never want to 4029 // consider a transition ready if nothing has been reported on it. 4030 if (!mUsed) return false; 4031 // If we are deferring readiness, we never report ready. This is usually temporary. 4032 if (mDeferReadyDepth > 0) return false; 4033 // Next check all the ready groups to see if they are ready. We can short-cut this if 4034 // ready-override is set (which is treated as "everything is marked ready"). 4035 if (mReadyOverride) return true; 4036 for (int i = mReadyGroups.size() - 1; i >= 0; --i) { 4037 final WindowContainer wc = mReadyGroups.keyAt(i); 4038 if (!wc.isAttached() || !wc.isVisibleRequested()) continue; 4039 if (!mReadyGroups.valueAt(i)) return false; 4040 } 4041 return true; 4042 } 4043 groupsToString()4044 private String groupsToString() { 4045 StringBuilder b = new StringBuilder(); 4046 for (int i = 0; i < mReadyGroups.size(); ++i) { 4047 if (i != 0) b.append(','); 4048 b.append(mReadyGroups.keyAt(i)).append(':') 4049 .append(mReadyGroups.valueAt(i)); 4050 } 4051 return b.toString(); 4052 } 4053 } 4054 4055 /** 4056 * The container to represent the depth relation for calculating transition targets. The window 4057 * container with larger depth is put at larger index. For the same depth, higher z-order has 4058 * larger index. 4059 */ 4060 private static class Targets { 4061 /** All targets. Its keys (depth) are sorted in ascending order naturally. */ 4062 final SparseArray<ChangeInfo> mArray = new SparseArray<>(); 4063 /** The targets which were represented by their parent. */ 4064 private ArrayList<ChangeInfo> mRemovedTargets; 4065 private int mDepthFactor; 4066 add(ChangeInfo target)4067 void add(ChangeInfo target) { 4068 // The number of slots per depth is larger than the total number of window container, 4069 // so the depth score (key) won't have collision. 4070 if (mDepthFactor == 0) { 4071 mDepthFactor = target.mContainer.mWmService.mRoot.getTreeWeight() + 1; 4072 } 4073 int score = target.mContainer.getPrefixOrderIndex(); 4074 WindowContainer<?> wc = target.mContainer; 4075 while (wc != null) { 4076 final WindowContainer<?> parent = wc.getParent(); 4077 if (parent != null) { 4078 score += mDepthFactor; 4079 } 4080 wc = parent; 4081 } 4082 mArray.put(score, target); 4083 } 4084 remove(int index)4085 void remove(int index) { 4086 final ChangeInfo removingTarget = mArray.valueAt(index); 4087 mArray.removeAt(index); 4088 if (mRemovedTargets == null) { 4089 mRemovedTargets = new ArrayList<>(); 4090 } 4091 mRemovedTargets.add(removingTarget); 4092 } 4093 wasParticipated(ChangeInfo wc)4094 boolean wasParticipated(ChangeInfo wc) { 4095 return mArray.indexOfValue(wc) >= 0 4096 || (mRemovedTargets != null && mRemovedTargets.contains(wc)); 4097 } 4098 4099 /** Returns the target list sorted by z-order in ascending order (index 0 is top). */ getListSortedByZ()4100 ArrayList<ChangeInfo> getListSortedByZ() { 4101 final SparseArray<ChangeInfo> arrayByZ = new SparseArray<>(mArray.size()); 4102 for (int i = mArray.size() - 1; i >= 0; --i) { 4103 final int zOrder = mArray.keyAt(i) % mDepthFactor; 4104 arrayByZ.put(zOrder, mArray.valueAt(i)); 4105 } 4106 final ArrayList<ChangeInfo> sortedTargets = new ArrayList<>(arrayByZ.size()); 4107 for (int i = arrayByZ.size() - 1; i >= 0; --i) { 4108 sortedTargets.add(arrayByZ.valueAt(i)); 4109 } 4110 return sortedTargets; 4111 } 4112 } 4113 4114 /** 4115 * Interface for freezing a container's content during sync preparation. Really just one impl 4116 * but broken into an interface for testing (since you can't take screenshots in unit tests). 4117 */ 4118 interface IContainerFreezer { 4119 /** 4120 * Makes sure a particular window is "frozen" for the remainder of a sync. 4121 * 4122 * @return whether the freeze was successful. It fails if `wc` is already in a frozen window 4123 * or is not visible/ready. 4124 */ freeze(@onNull WindowContainer wc, @NonNull Rect bounds)4125 boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds); 4126 4127 /** Populates `t` with operations that clean-up any state created to set-up the freeze. */ cleanUp(SurfaceControl.Transaction t)4128 void cleanUp(SurfaceControl.Transaction t); 4129 } 4130 4131 /** 4132 * Freezes container content by taking a screenshot. Because screenshots are heavy, usage of 4133 * any container "freeze" is currently explicit. WM code needs to be prudent about which 4134 * containers to freeze. 4135 */ 4136 @VisibleForTesting 4137 private class ScreenshotFreezer implements IContainerFreezer { 4138 /** Keeps track of which windows are frozen. Not all frozen windows have snapshots. */ 4139 private final ArraySet<WindowContainer> mFrozen = new ArraySet<>(); 4140 4141 /** Takes a screenshot and puts it at the top of the container's surface. */ 4142 @Override freeze(@onNull WindowContainer wc, @NonNull Rect bounds)4143 public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) { 4144 if (!wc.isVisibleRequested()) return false; 4145 4146 // Check if any parents have already been "frozen". If so, `wc` is already part of that 4147 // snapshot, so just skip it. 4148 for (WindowContainer p = wc; p != null; p = p.getParent()) { 4149 if (mFrozen.contains(p)) return false; 4150 } 4151 4152 if (mIsSeamlessRotation) { 4153 WindowState top = wc.getDisplayContent() == null ? null 4154 : wc.getDisplayContent().getDisplayPolicy().getTopFullscreenOpaqueWindow(); 4155 if (top != null && (top == wc || top.isDescendantOf(wc))) { 4156 // Don't use screenshots for seamless windows: these will use BLAST even if not 4157 // BLAST mode. 4158 mFrozen.add(wc); 4159 return true; 4160 } 4161 } 4162 4163 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Screenshotting %s [%s]", 4164 wc.toString(), bounds.toString()); 4165 4166 Rect cropBounds = new Rect(bounds); 4167 cropBounds.offsetTo(0, 0); 4168 final boolean isDisplayRotation = wc.asDisplayContent() != null 4169 && wc.asDisplayContent().isRotationChanging(); 4170 ScreenCapture.LayerCaptureArgs captureArgs = 4171 new ScreenCapture.LayerCaptureArgs.Builder(wc.getSurfaceControl()) 4172 .setSourceCrop(cropBounds) 4173 .setCaptureSecureLayers(true) 4174 .setAllowProtected(true) 4175 // We always reroute this screenshot to the display, so this transition 4176 // is ALWAYS seamless 4177 .setHintForSeamlessTransition(true) 4178 .build(); 4179 ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = 4180 ScreenCapture.captureLayers(captureArgs); 4181 final HardwareBuffer buffer = screenshotBuffer == null ? null 4182 : screenshotBuffer.getHardwareBuffer(); 4183 if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { 4184 // This can happen when display is not ready. 4185 Slog.w(TAG, "Failed to capture screenshot for " + wc); 4186 return false; 4187 } 4188 // Some tests may check the name "RotationLayer" to detect display rotation. 4189 final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc; 4190 SurfaceControl snapshotSurface = wc.makeAnimationLeash() 4191 .setName(name) 4192 .setOpaque(wc.fillsParent()) 4193 .setParent(wc.getSurfaceControl()) 4194 .setSecure(screenshotBuffer.containsSecureLayers()) 4195 .setCallsite("Transition.ScreenshotSync") 4196 .setBLASTLayer() 4197 .build(); 4198 mFrozen.add(wc); 4199 final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc)); 4200 changeInfo.mSnapshot = snapshotSurface; 4201 if (changeInfo.mRotation != wc.mDisplayContent.getRotation()) { 4202 // This isn't cheap, so only do it for rotation change. 4203 changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma( 4204 buffer, screenshotBuffer.getColorSpace(), wc.mSurfaceControl); 4205 } 4206 SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get(); 4207 TransitionAnimation.configureScreenshotLayer(t, snapshotSurface, screenshotBuffer); 4208 t.show(snapshotSurface); 4209 4210 // Place it on top of anything else in the container. 4211 t.setLayer(snapshotSurface, Integer.MAX_VALUE); 4212 t.apply(); 4213 t.close(); 4214 buffer.close(); 4215 4216 // Detach the screenshot on the sync transaction (the screenshot is just meant to 4217 // freeze the window until the sync transaction is applied (with all its other 4218 // corresponding changes), so this is how we unfreeze it. 4219 wc.getSyncTransaction().reparent(snapshotSurface, null /* newParent */); 4220 return true; 4221 } 4222 4223 @Override cleanUp(SurfaceControl.Transaction t)4224 public void cleanUp(SurfaceControl.Transaction t) { 4225 for (int i = 0; i < mFrozen.size(); ++i) { 4226 SurfaceControl snap = 4227 Objects.requireNonNull(mChanges.get(mFrozen.valueAt(i))).mSnapshot; 4228 // May be null if it was frozen via BLAST override. 4229 if (snap == null) continue; 4230 t.reparent(snap, null /* newParent */); 4231 } 4232 } 4233 } 4234 4235 private static class Token extends Binder { 4236 final WeakReference<Transition> mTransition; 4237 Token(Transition transition)4238 Token(Transition transition) { 4239 mTransition = new WeakReference<>(transition); 4240 } 4241 4242 @Override toString()4243 public String toString() { 4244 return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " " 4245 + mTransition.get() + "}"; 4246 } 4247 } 4248 } 4249