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