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