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 1378 mState = STATE_FINISHED; 1379 // Rotation change may be deferred while there is a display change transition, so check 1380 // again in case there is a new pending change. 1381 if (hasParticipatedDisplay && !mController.useShellTransitionsRotation()) { 1382 mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */, 1383 false /* forceRelayout */); 1384 } 1385 cleanUpInternal(); 1386 mController.updateAnimatingState(mTmpTransaction); 1387 mTmpTransaction.apply(); 1388 1389 // Handle back animation if it's already started. 1390 mController.mAtm.mBackNavigationController.onTransitionFinish(mTargets, this); 1391 mController.mFinishingTransition = null; 1392 mController.mSnapshotController.onTransitionFinish(mType, mTargets); 1393 } 1394 abort()1395 void abort() { 1396 // This calls back into itself via controller.abort, so just early return here. 1397 if (mState == STATE_ABORT) return; 1398 if (mState == STATE_PENDING) { 1399 // hasn't started collecting, so can jump directly to aborted state. 1400 mState = STATE_ABORT; 1401 return; 1402 } 1403 if (mState != STATE_COLLECTING && mState != STATE_STARTED) { 1404 throw new IllegalStateException("Too late to abort. state=" + mState); 1405 } 1406 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId); 1407 mState = STATE_ABORT; 1408 mLogger.mAbortTimeNs = SystemClock.elapsedRealtimeNanos(); 1409 mController.mTransitionTracer.logAbortedTransition(this); 1410 // Syncengine abort will call through to onTransactionReady() 1411 mSyncEngine.abort(mSyncId); 1412 mController.dispatchLegacyAppTransitionCancelled(); 1413 } 1414 1415 /** Immediately moves this to playing even if it isn't started yet. */ playNow()1416 void playNow() { 1417 if (!(mState == STATE_COLLECTING || mState == STATE_STARTED)) { 1418 return; 1419 } 1420 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Force Playing Transition: %d", 1421 mSyncId); 1422 mForcePlaying = true; 1423 setAllReady(); 1424 if (mState == STATE_COLLECTING) { 1425 start(); 1426 } 1427 // Don't wait for actual surface-placement. We don't want anything else collected in this 1428 // transition. 1429 mSyncEngine.onSurfacePlacement(); 1430 } 1431 isForcePlaying()1432 boolean isForcePlaying() { 1433 return mForcePlaying; 1434 } 1435 setRemoteAnimationApp(IApplicationThread app)1436 void setRemoteAnimationApp(IApplicationThread app) { 1437 mRemoteAnimApp = app; 1438 } 1439 1440 /** Returns the app which will run the transition animation. */ getRemoteAnimationApp()1441 IApplicationThread getRemoteAnimationApp() { 1442 return mRemoteAnimApp; 1443 } 1444 setNoAnimation(WindowContainer wc)1445 void setNoAnimation(WindowContainer wc) { 1446 final ChangeInfo change = mChanges.get(wc); 1447 if (change == null) { 1448 throw new IllegalStateException("Can't set no-animation property of non-participant"); 1449 } 1450 change.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION; 1451 } 1452 1453 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) containsChangeFor(WindowContainer wc, ArrayList<ChangeInfo> list)1454 static boolean containsChangeFor(WindowContainer wc, ArrayList<ChangeInfo> list) { 1455 for (int i = list.size() - 1; i >= 0; --i) { 1456 if (list.get(i).mContainer == wc) return true; 1457 } 1458 return false; 1459 } 1460 1461 @Override onTransactionReady(int syncId, SurfaceControl.Transaction transaction)1462 public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) { 1463 if (syncId != mSyncId) { 1464 Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId); 1465 return; 1466 } 1467 1468 // Commit the visibility of visible activities before calculateTransitionInfo(), so the 1469 // TaskInfo can be visible. Also it needs to be done before moveToPlaying(), otherwise 1470 // ActivityRecord#canShowWindows() may reject to show its window. The visibility also 1471 // needs to be updated for STATE_ABORT. 1472 commitVisibleActivities(transaction); 1473 commitVisibleWallpapers(); 1474 1475 // Fall-back to the default display if there isn't one participating. 1476 final DisplayContent primaryDisplay = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0) 1477 : mController.mAtm.mRootWindowContainer.getDefaultDisplay(); 1478 1479 if (mState == STATE_ABORT) { 1480 mController.onAbort(this); 1481 primaryDisplay.getPendingTransaction().merge(transaction); 1482 mSyncId = -1; 1483 mOverrideOptions = null; 1484 cleanUpInternal(); 1485 return; 1486 } 1487 1488 if (mState != STATE_STARTED) { 1489 Slog.e(TAG, "Playing a Transition which hasn't started! #" + mSyncId + " This will " 1490 + "likely cause an exception in Shell"); 1491 } 1492 1493 mState = STATE_PLAYING; 1494 mStartTransaction = transaction; 1495 mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get(); 1496 1497 // Flags must be assigned before calculateTransitionInfo. Otherwise it won't take effect. 1498 if (primaryDisplay.isKeyguardLocked()) { 1499 mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED; 1500 } 1501 1502 // This is the only (or last) transition that is collecting, so we need to report any 1503 // leftover order changes. 1504 collectOrderChanges(mController.mWaitingTransitions.isEmpty()); 1505 1506 if (mPriorVisibilityMightBeDirty) { 1507 updatePriorVisibility(); 1508 } 1509 // Resolve the animating targets from the participants. 1510 mTargets = calculateTargets(mParticipants, mChanges); 1511 1512 // Check whether the participants were animated from back navigation. 1513 mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets, 1514 transaction); 1515 final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction); 1516 info.setDebugId(mSyncId); 1517 mController.assignTrack(this, info); 1518 1519 mController.moveToPlaying(this); 1520 1521 // Repopulate the displays based on the resolved targets. 1522 mTargetDisplays.clear(); 1523 for (int i = 0; i < info.getRootCount(); ++i) { 1524 final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent( 1525 info.getRoot(i).getDisplayId()); 1526 mTargetDisplays.add(dc); 1527 } 1528 1529 for (int i = 0; i < mTargets.size(); ++i) { 1530 final DisplayArea da = mTargets.get(i).mContainer.asDisplayArea(); 1531 if (da == null) continue; 1532 if (da.isVisibleRequested()) { 1533 mController.mValidateDisplayVis.remove(da); 1534 } else { 1535 // In case something accidentally hides a displayarea and nothing shows it again. 1536 mController.mValidateDisplayVis.add(da); 1537 } 1538 } 1539 1540 if (mOverrideOptions != null) { 1541 info.setAnimationOptions(mOverrideOptions); 1542 if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) { 1543 for (int i = 0; i < mTargets.size(); ++i) { 1544 final TransitionInfo.Change c = info.getChanges().get(i); 1545 final ActivityRecord ar = mTargets.get(i).mContainer.asActivityRecord(); 1546 if (ar == null || c.getMode() != TRANSIT_OPEN) continue; 1547 int flags = c.getFlags(); 1548 flags |= ar.mUserId == ar.mWmService.mCurrentUserId 1549 ? TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL 1550 : TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL; 1551 c.setFlags(flags); 1552 break; 1553 } 1554 } 1555 } 1556 1557 // TODO(b/188669821): Move to animation impl in shell. 1558 for (int i = 0; i < mTargetDisplays.size(); ++i) { 1559 handleLegacyRecentsStartBehavior(mTargetDisplays.get(i), info); 1560 if (mRecentsDisplayId != INVALID_DISPLAY) break; 1561 } 1562 1563 // The callback is only populated for custom activity-level client animations 1564 sendRemoteCallback(mClientAnimationStartCallback); 1565 1566 // Manually show any activities that are visibleRequested. This is needed to properly 1567 // support simultaneous animation queueing/merging. Specifically, if transition A makes 1568 // an activity invisible, it's finishTransaction (which is applied *after* the animation) 1569 // will hide the activity surface. If transition B then makes the activity visible again, 1570 // the normal surfaceplacement logic won't add a show to this start transaction because 1571 // the activity visibility hasn't been committed yet. To deal with this, we have to manually 1572 // show here in the same way that we manually hide in finishTransaction. 1573 for (int i = mParticipants.size() - 1; i >= 0; --i) { 1574 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 1575 if (ar == null || !ar.isVisibleRequested()) continue; 1576 transaction.show(ar.getSurfaceControl()); 1577 1578 // Also manually show any non-reported parents. This is necessary in a few cases 1579 // where a task is NOT organized but had its visibility changed within its direct 1580 // parent. An example of this is if an alternate home leaf-task HB is started atop the 1581 // normal home leaf-task HA: these are both in the Home root-task HR, so there will be a 1582 // transition containing HA and HB where HA surface is hidden. If a standard task SA is 1583 // launched on top, then HB finishes, no transition will happen since neither home is 1584 // visible. When SA finishes, the transition contains HR rather than HA. Since home 1585 // leaf-tasks are NOT organized, HA won't be in the transition and thus its surface 1586 // wouldn't be shown. Just show is safe here since all other properties will have 1587 // already been reset by the original hiding-transition's finishTransaction (we can't 1588 // show in the finishTransaction because by then the activity doesn't hide until 1589 // surface placement). 1590 for (WindowContainer p = ar.getParent(); p != null && !containsChangeFor(p, mTargets); 1591 p = p.getParent()) { 1592 if (p.getSurfaceControl() != null) { 1593 transaction.show(p.getSurfaceControl()); 1594 } 1595 } 1596 } 1597 1598 // Record windowtokens (activity/wallpaper) that are expected to be visible after the 1599 // transition animation. This will be used in finishTransition to prevent prematurely 1600 // committing visibility. Skip transient launches since those are only temporarily visible. 1601 if (mTransientLaunches == null) { 1602 for (int i = mParticipants.size() - 1; i >= 0; --i) { 1603 final WindowContainer wc = mParticipants.valueAt(i); 1604 if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue; 1605 mVisibleAtTransitionEndTokens.add(wc.asWindowToken()); 1606 } 1607 } 1608 1609 // Take task snapshots before the animation so that we can capture IME before it gets 1610 // transferred. If transition is transient, IME won't be moved during the transition and 1611 // the tasks are still live, so we take the snapshot at the end of the transition instead. 1612 if (mTransientLaunches == null) { 1613 mController.mSnapshotController.onTransactionReady(mType, mTargets); 1614 } 1615 1616 // This is non-null only if display has changes. It handles the visible windows that don't 1617 // need to be participated in the transition. 1618 for (int i = 0; i < mTargetDisplays.size(); ++i) { 1619 final DisplayContent dc = mTargetDisplays.get(i); 1620 final AsyncRotationController controller = dc.getAsyncRotationController(); 1621 if (controller != null && containsChangeFor(dc, mTargets)) { 1622 controller.setupStartTransaction(transaction); 1623 } 1624 } 1625 buildFinishTransaction(mFinishTransaction, info); 1626 mCleanupTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get(); 1627 buildCleanupTransaction(mCleanupTransaction, info); 1628 if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) { 1629 mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay); 1630 try { 1631 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1632 "Calling onTransitionReady: %s", info); 1633 mLogger.mSendTimeNs = SystemClock.elapsedRealtimeNanos(); 1634 mLogger.mInfo = info; 1635 mController.getTransitionPlayer().onTransitionReady( 1636 mToken, info, transaction, mFinishTransaction); 1637 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { 1638 asyncTraceBegin(TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this)); 1639 } 1640 } catch (RemoteException e) { 1641 // If there's an exception when trying to send the mergedTransaction to the 1642 // client, we should finish and apply it here so the transactions aren't lost. 1643 postCleanupOnFailure(); 1644 } 1645 for (int i = 0; i < mTargetDisplays.size(); ++i) { 1646 final DisplayContent dc = mTargetDisplays.get(i); 1647 final AccessibilityController accessibilityController = 1648 dc.mWmService.mAccessibilityController; 1649 if (accessibilityController.hasCallbacks()) { 1650 accessibilityController.onWMTransition(dc.getDisplayId(), mType); 1651 } 1652 } 1653 } else { 1654 // No player registered or it's not enabled, so just finish/apply immediately 1655 if (!mIsPlayerEnabled) { 1656 mLogger.mSendTimeNs = SystemClock.uptimeNanos(); 1657 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Apply and finish immediately" 1658 + " because player is disabled for transition #%d .", mSyncId); 1659 } 1660 postCleanupOnFailure(); 1661 } 1662 mOverrideOptions = null; 1663 1664 reportStartReasonsToLogger(); 1665 1666 // Since we created root-leash but no longer reference it from core, release it now 1667 info.releaseAnimSurfaces(); 1668 1669 mController.mLoggerHandler.post(mLogger::logOnSend); 1670 if (mLogger.mInfo != null) { 1671 mController.mTransitionTracer.logSentTransition(this, mTargets); 1672 } 1673 } 1674 1675 /** 1676 * Collect tasks which moved-to-top as part of this transition. This also updates the 1677 * controller's latest-reported when relevant. 1678 * 1679 * This is a non-trivial operation because transition can collect in parallel; however, it can 1680 * be made tenable by acknowledging that the "setup" part of collection (phase 1) is still 1681 * globally serial; so, we can build some reasonable rules around it. 1682 * 1683 * First, we record the "start" on-top state (to compare against). Then, when this becomes 1684 * ready (via allReady, NOT onTransactionReady), we also record the "onReady" on-top state 1685 * -- the idea here is that upon "allReady", all the actual WM changes should be done and we 1686 * are now just waiting for window content to become ready (finish drawing). 1687 * 1688 * Then, in this function (during onTransactionReady), we compare the two orders and include 1689 * any changes to the order in the reported transition-info. Unfortunately, because of parallel 1690 * collection, the order can change in unexpected ways by now. To resolve this, we ALSO keep a 1691 * global "latest reported order" in TransitionController and use that to make decisions. 1692 */ 1693 @VisibleForTesting collectOrderChanges(boolean reportCurrent)1694 void collectOrderChanges(boolean reportCurrent) { 1695 if (mOnTopTasksStart.isEmpty()) return; 1696 boolean includesOrderChange = false; 1697 for (int i = 0; i < mOnTopTasksAtReady.size(); ++i) { 1698 final Task task = mOnTopTasksAtReady.get(i); 1699 if (mOnTopTasksStart.contains(task)) continue; 1700 includesOrderChange = true; 1701 break; 1702 } 1703 if (!includesOrderChange && !reportCurrent) { 1704 // This transition doesn't include an order change, so if it isn't required to report 1705 // the current focus (eg. it's the last of a cluster of transitions), then don't 1706 // report. 1707 return; 1708 } 1709 // The transition included an order change, but it may not be up-to-date, so grab the 1710 // latest state and compare with the last reported state (or our start state if no 1711 // reported state exists). 1712 ArrayList<Task> onTopTasksEnd = new ArrayList<>(); 1713 for (int d = 0; d < mTargetDisplays.size(); ++d) { 1714 addOnTopTasks(mTargetDisplays.get(d), onTopTasksEnd); 1715 final int displayId = mTargetDisplays.get(d).mDisplayId; 1716 ArrayList<Task> reportedOnTop = mController.mLatestOnTopTasksReported.get(displayId); 1717 for (int i = onTopTasksEnd.size() - 1; i >= 0; --i) { 1718 final Task task = onTopTasksEnd.get(i); 1719 if (task.getDisplayId() != displayId) continue; 1720 // If it didn't change since last report, don't report 1721 if (reportedOnTop == null) { 1722 if (mOnTopTasksStart.contains(task)) continue; 1723 } else if (reportedOnTop.contains(task)) { 1724 continue; 1725 } 1726 // Need to report it. 1727 mParticipants.add(task); 1728 int changeIdx = mChanges.indexOfKey(task); 1729 if (changeIdx < 0) { 1730 mChanges.put(task, new ChangeInfo(task)); 1731 changeIdx = mChanges.indexOfKey(task); 1732 } 1733 mChanges.valueAt(changeIdx).mFlags |= ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP; 1734 } 1735 // Swap in the latest on-top tasks. 1736 mController.mLatestOnTopTasksReported.put(displayId, onTopTasksEnd); 1737 onTopTasksEnd = reportedOnTop != null ? reportedOnTop : new ArrayList<>(); 1738 onTopTasksEnd.clear(); 1739 } 1740 } 1741 postCleanupOnFailure()1742 private void postCleanupOnFailure() { 1743 mController.mAtm.mH.post(() -> { 1744 synchronized (mController.mAtm.mGlobalLock) { 1745 cleanUpOnFailure(); 1746 } 1747 }); 1748 } 1749 1750 /** 1751 * If the remote failed for any reason, use this to do any appropriate clean-up. Do not call 1752 * this directly, it's designed to by called by {@link TransitionController} only. 1753 */ cleanUpOnFailure()1754 void cleanUpOnFailure() { 1755 // No need to clean-up if this isn't playing yet. 1756 if (mState < STATE_PLAYING) return; 1757 1758 if (mStartTransaction != null) { 1759 mStartTransaction.apply(); 1760 } 1761 if (mFinishTransaction != null) { 1762 mFinishTransaction.apply(); 1763 } 1764 mController.finishTransition(this); 1765 } 1766 cleanUpInternal()1767 private void cleanUpInternal() { 1768 // Clean-up any native references. 1769 for (int i = 0; i < mChanges.size(); ++i) { 1770 final ChangeInfo ci = mChanges.valueAt(i); 1771 if (ci.mSnapshot != null) { 1772 ci.mSnapshot.release(); 1773 } 1774 } 1775 if (mCleanupTransaction != null) { 1776 mCleanupTransaction.apply(); 1777 mCleanupTransaction = null; 1778 } 1779 } 1780 1781 /** The transition is ready to play. Make the start transaction show the surfaces. */ commitVisibleActivities(SurfaceControl.Transaction transaction)1782 private void commitVisibleActivities(SurfaceControl.Transaction transaction) { 1783 for (int i = mParticipants.size() - 1; i >= 0; --i) { 1784 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 1785 if (ar == null || ar.getTask() == null) { 1786 continue; 1787 } 1788 if (ar.isVisibleRequested()) { 1789 ar.commitVisibility(true /* visible */, false /* performLayout */, 1790 true /* fromTransition */); 1791 ar.commitFinishDrawing(transaction); 1792 } 1793 ar.getTask().setDeferTaskAppear(false); 1794 } 1795 } 1796 1797 /** 1798 * Reset waitingToshow for all wallpapers, and commit the visibility of the visible ones 1799 */ commitVisibleWallpapers()1800 private void commitVisibleWallpapers() { 1801 boolean showWallpaper = shouldWallpaperBeVisible(); 1802 for (int i = mParticipants.size() - 1; i >= 0; --i) { 1803 final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken(); 1804 if (wallpaper != null) { 1805 wallpaper.waitingToShow = false; 1806 if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) { 1807 wallpaper.commitVisibility(showWallpaper); 1808 } 1809 } 1810 } 1811 } 1812 shouldWallpaperBeVisible()1813 private boolean shouldWallpaperBeVisible() { 1814 for (int i = mParticipants.size() - 1; i >= 0; --i) { 1815 WindowContainer participant = mParticipants.valueAt(i); 1816 if (participant.showWallpaper()) return true; 1817 } 1818 return false; 1819 } 1820 1821 // TODO(b/188595497): Remove after migrating to shell. 1822 /** @see RecentsAnimationController#attachNavigationBarToApp */ handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info)1823 private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) { 1824 if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) { 1825 return; 1826 } 1827 1828 // Recents has an input-consumer to grab input from the "live tile" app. Set that up here 1829 final InputConsumerImpl recentsAnimationInputConsumer = 1830 dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); 1831 ActivityRecord recentsActivity = null; 1832 if (recentsAnimationInputConsumer != null) { 1833 // find the top-most going-away activity and the recents activity. The top-most 1834 // is used as layer reference while the recents is used for registering the consumer 1835 // override. 1836 ActivityRecord topActivity = null; 1837 for (int i = 0; i < info.getChanges().size(); ++i) { 1838 final TransitionInfo.Change change = info.getChanges().get(i); 1839 if (change.getTaskInfo() == null) continue; 1840 final Task task = Task.fromWindowContainerToken( 1841 info.getChanges().get(i).getTaskInfo().token); 1842 if (task == null) continue; 1843 final int activityType = change.getTaskInfo().topActivityType; 1844 final boolean isRecents = activityType == ACTIVITY_TYPE_HOME 1845 || activityType == ACTIVITY_TYPE_RECENTS; 1846 if (isRecents && recentsActivity == null) { 1847 recentsActivity = task.getTopVisibleActivity(); 1848 } else if (!isRecents && topActivity == null) { 1849 topActivity = task.getTopNonFinishingActivity(); 1850 } 1851 } 1852 if (recentsActivity != null && topActivity != null) { 1853 recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set( 1854 topActivity.getBounds()); 1855 dc.getInputMonitor().setActiveRecents(recentsActivity, topActivity); 1856 } 1857 } 1858 1859 if (recentsActivity == null) { 1860 // No recents activity on `dc`, its probably on a different display. 1861 return; 1862 } 1863 mRecentsDisplayId = dc.mDisplayId; 1864 1865 // The rest of this function handles nav-bar reparenting 1866 1867 if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition() 1868 // Skip the case where the nav bar is controlled by fade rotation. 1869 || dc.getAsyncRotationController() != null) { 1870 return; 1871 } 1872 1873 WindowContainer topWC = null; 1874 // Find the top-most non-home, closing app. 1875 for (int i = 0; i < info.getChanges().size(); ++i) { 1876 final TransitionInfo.Change c = info.getChanges().get(i); 1877 if (c.getTaskInfo() == null || c.getTaskInfo().displayId != mRecentsDisplayId 1878 || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD 1879 || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) { 1880 continue; 1881 } 1882 topWC = WindowContainer.fromBinder(c.getContainer().asBinder()); 1883 break; 1884 } 1885 if (topWC == null || topWC.inMultiWindowMode()) { 1886 return; 1887 } 1888 1889 final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar(); 1890 if (navWindow == null || navWindow.mToken == null) { 1891 return; 1892 } 1893 mController.mNavigationBarAttachedToApp = true; 1894 navWindow.mToken.cancelAnimation(); 1895 final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction(); 1896 final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl(); 1897 t.reparent(navSurfaceControl, topWC.getSurfaceControl()); 1898 t.show(navSurfaceControl); 1899 1900 final WindowContainer imeContainer = dc.getImeContainer(); 1901 if (imeContainer.isVisible()) { 1902 t.setRelativeLayer(navSurfaceControl, imeContainer.getSurfaceControl(), 1); 1903 } else { 1904 // Place the nav bar on top of anything else in the top activity. 1905 t.setLayer(navSurfaceControl, Integer.MAX_VALUE); 1906 } 1907 final StatusBarManagerInternal bar = dc.getDisplayPolicy().getStatusBarManagerInternal(); 1908 if (bar != null) { 1909 bar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, false); 1910 } 1911 } 1912 1913 /** @see RecentsAnimationController#restoreNavigationBarFromApp */ legacyRestoreNavigationBarFromApp()1914 void legacyRestoreNavigationBarFromApp() { 1915 if (!mController.mNavigationBarAttachedToApp) { 1916 return; 1917 } 1918 mController.mNavigationBarAttachedToApp = false; 1919 1920 if (mRecentsDisplayId == INVALID_DISPLAY) { 1921 Slog.e(TAG, "Reparented navigation bar without a valid display"); 1922 mRecentsDisplayId = DEFAULT_DISPLAY; 1923 } 1924 1925 final DisplayContent dc = 1926 mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId); 1927 final StatusBarManagerInternal bar = dc.getDisplayPolicy().getStatusBarManagerInternal(); 1928 if (bar != null) { 1929 bar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, true); 1930 } 1931 final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar(); 1932 if (navWindow == null) return; 1933 navWindow.setSurfaceTranslationY(0); 1934 1935 final WindowToken navToken = navWindow.mToken; 1936 if (navToken == null) return; 1937 final SurfaceControl.Transaction t = dc.getPendingTransaction(); 1938 final WindowContainer parent = navToken.getParent(); 1939 t.setLayer(navToken.getSurfaceControl(), navToken.getLastLayer()); 1940 1941 boolean animate = false; 1942 // Search for the home task. If it is supposed to be visible, then the navbar is not at 1943 // the bottom of the screen, so we need to animate it. 1944 for (int i = 0; i < mTargets.size(); ++i) { 1945 final Task task = mTargets.get(i).mContainer.asTask(); 1946 if (task == null || !task.isActivityTypeHomeOrRecents()) continue; 1947 animate = task.isVisibleRequested(); 1948 break; 1949 } 1950 1951 if (animate) { 1952 final NavBarFadeAnimationController controller = 1953 new NavBarFadeAnimationController(dc); 1954 controller.fadeWindowToken(true); 1955 } else { 1956 // Reparent the SurfaceControl of nav bar token back. 1957 t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl()); 1958 } 1959 1960 // To apply transactions. 1961 dc.mWmService.scheduleAnimationLocked(); 1962 } 1963 reportStartReasonsToLogger()1964 private void reportStartReasonsToLogger() { 1965 // Record transition start in metrics logger. We just assume everything is "DRAWN" 1966 // at this point since splash-screen is a presentation (shell) detail. 1967 ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>(); 1968 for (int i = mParticipants.size() - 1; i >= 0; --i) { 1969 ActivityRecord r = mParticipants.valueAt(i).asActivityRecord(); 1970 if (r == null || !r.isVisibleRequested()) continue; 1971 int transitionReason = APP_TRANSITION_WINDOWS_DRAWN; 1972 // At this point, r is "ready", but if it's not "ALL ready" then it is probably only 1973 // ready due to starting-window. 1974 if (r.mStartingData instanceof SplashScreenStartingData && !r.mLastAllReadyAtSync) { 1975 transitionReason = APP_TRANSITION_SPLASH_SCREEN; 1976 } else if (r.isActivityTypeHomeOrRecents() && isTransientLaunch(r)) { 1977 transitionReason = APP_TRANSITION_RECENTS_ANIM; 1978 } 1979 reasons.put(r, transitionReason); 1980 } 1981 mController.mAtm.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting( 1982 reasons); 1983 } 1984 1985 @Override toString()1986 public String toString() { 1987 StringBuilder sb = new StringBuilder(64); 1988 sb.append("TransitionRecord{"); 1989 sb.append(Integer.toHexString(System.identityHashCode(this))); 1990 sb.append(" id=" + mSyncId); 1991 sb.append(" type=" + transitTypeToString(mType)); 1992 sb.append(" flags=0x" + Integer.toHexString(mFlags)); 1993 sb.append('}'); 1994 return sb.toString(); 1995 } 1996 1997 /** Returns the parent that the remote animator can animate or control. */ getAnimatableParent(WindowContainer<?> wc)1998 private static WindowContainer<?> getAnimatableParent(WindowContainer<?> wc) { 1999 WindowContainer<?> parent = wc.getParent(); 2000 while (parent != null 2001 && (!parent.canCreateRemoteAnimationTarget() && !parent.isOrganized())) { 2002 parent = parent.getParent(); 2003 } 2004 return parent; 2005 } 2006 reportIfNotTop(WindowContainer wc)2007 private static boolean reportIfNotTop(WindowContainer wc) { 2008 // Organized tasks need to be reported anyways because Core won't show() their surfaces 2009 // and we can't rely on onTaskAppeared because it isn't in sync. 2010 // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN. 2011 return wc.isOrganized(); 2012 } 2013 isWallpaper(WindowContainer wc)2014 private static boolean isWallpaper(WindowContainer wc) { 2015 return wc.asWallpaperToken() != null; 2016 } 2017 isInputMethod(WindowContainer wc)2018 private static boolean isInputMethod(WindowContainer wc) { 2019 return wc.getWindowType() == TYPE_INPUT_METHOD; 2020 } 2021 occludesKeyguard(WindowContainer wc)2022 private static boolean occludesKeyguard(WindowContainer wc) { 2023 final ActivityRecord ar = wc.asActivityRecord(); 2024 if (ar != null) { 2025 return ar.canShowWhenLocked(); 2026 } 2027 final Task t = wc.asTask(); 2028 if (t != null) { 2029 // Get the top activity which was visible (since this is going away, it will remain 2030 // client visible until the transition is finished). 2031 // skip hidden (or about to hide) apps 2032 final ActivityRecord top = t.getActivity(WindowToken::isClientVisible); 2033 return top != null && top.canShowWhenLocked(); 2034 } 2035 return false; 2036 } 2037 isTranslucent(@onNull WindowContainer wc)2038 private static boolean isTranslucent(@NonNull WindowContainer wc) { 2039 final TaskFragment taskFragment = wc.asTaskFragment(); 2040 if (taskFragment == null) { 2041 return !wc.fillsParent(); 2042 } 2043 2044 // Check containers differently as they are affected by child visibility. 2045 2046 if (taskFragment.isTranslucentForTransition()) { 2047 // TaskFragment doesn't contain occluded ActivityRecord. 2048 return true; 2049 } 2050 final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); 2051 if (adjacentTaskFragment != null) { 2052 // When the TaskFragment has an adjacent TaskFragment, sibling behind them should be 2053 // hidden unless any of them are translucent. 2054 return adjacentTaskFragment.isTranslucentForTransition(); 2055 } else { 2056 // Non-filling without adjacent is considered as translucent. 2057 return !wc.fillsParent(); 2058 } 2059 } 2060 updatePriorVisibility()2061 private void updatePriorVisibility() { 2062 for (int i = 0; i < mChanges.size(); ++i) { 2063 final ChangeInfo chg = mChanges.valueAt(i); 2064 // For task/activity, recalculate the current "real" visibility. 2065 if (chg.mContainer.asActivityRecord() == null && chg.mContainer.asTask() == null) { 2066 continue; 2067 } 2068 // This ONLY works in the visible -> invisible case (and is only needed for this case) 2069 // because commitVisible(false) is deferred until finish. 2070 if (!chg.mVisible) continue; 2071 chg.mVisible = chg.mContainer.isVisible(); 2072 } 2073 } 2074 2075 /** 2076 * Under some conditions (eg. all visible targets within a parent container are transitioning 2077 * the same way) the transition can be "promoted" to the parent container. This means an 2078 * animation can play just on the parent rather than all the individual children. 2079 * 2080 * @return {@code true} if transition in target can be promoted to its parent. 2081 */ canPromote(ChangeInfo targetChange, Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)2082 private static boolean canPromote(ChangeInfo targetChange, Targets targets, 2083 ArrayMap<WindowContainer, ChangeInfo> changes) { 2084 final WindowContainer<?> target = targetChange.mContainer; 2085 final WindowContainer<?> parent = target.getParent(); 2086 final ChangeInfo parentChange = changes.get(parent); 2087 if (!parent.canCreateRemoteAnimationTarget() 2088 || parentChange == null || !parentChange.hasChanged()) { 2089 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s", 2090 "parent can't be target " + parent); 2091 return false; 2092 } 2093 if (isWallpaper(target)) { 2094 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: is wallpaper"); 2095 return false; 2096 } 2097 2098 if (targetChange.mStartParent != null && target.getParent() != targetChange.mStartParent) { 2099 // When a window is reparented, the state change won't fit into any of the parents. 2100 // Don't promote such change so that we can animate the reparent if needed. 2101 return false; 2102 } 2103 2104 final @TransitionInfo.TransitionMode int mode = targetChange.getTransitMode(target); 2105 for (int i = parent.getChildCount() - 1; i >= 0; --i) { 2106 final WindowContainer<?> sibling = parent.getChildAt(i); 2107 if (target == sibling) continue; 2108 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s", 2109 sibling); 2110 final ChangeInfo siblingChange = changes.get(sibling); 2111 if (siblingChange == null || !targets.wasParticipated(siblingChange)) { 2112 if (sibling.isVisibleRequested()) { 2113 // Sibling is visible but not animating, so no promote. 2114 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 2115 " SKIP: sibling is visible but not part of transition"); 2116 return false; 2117 } 2118 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 2119 " unrelated invisible sibling %s", sibling); 2120 continue; 2121 } 2122 2123 final int siblingMode = siblingChange.getTransitMode(sibling); 2124 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 2125 " sibling is a participant with mode %s", 2126 TransitionInfo.modeToString(siblingMode)); 2127 if (reduceMode(mode) != reduceMode(siblingMode)) { 2128 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 2129 " SKIP: common mode mismatch. was %s", 2130 TransitionInfo.modeToString(mode)); 2131 return false; 2132 } 2133 } 2134 return true; 2135 } 2136 2137 /** "reduces" a mode into a smaller set of modes that uniquely represents visibility change. */ 2138 @TransitionInfo.TransitionMode reduceMode(@ransitionInfo.TransitionMode int mode)2139 private static int reduceMode(@TransitionInfo.TransitionMode int mode) { 2140 switch (mode) { 2141 case TRANSIT_TO_BACK: return TRANSIT_CLOSE; 2142 case TRANSIT_TO_FRONT: return TRANSIT_OPEN; 2143 default: return mode; 2144 } 2145 } 2146 2147 /** 2148 * Go through topTargets and try to promote (see {@link #canPromote}) one of them. 2149 * 2150 * @param targets all targets that will be sent to the player. 2151 */ tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)2152 private static void tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes) { 2153 WindowContainer<?> lastNonPromotableParent = null; 2154 // Go through from the deepest target. 2155 for (int i = targets.mArray.size() - 1; i >= 0; --i) { 2156 final ChangeInfo targetChange = targets.mArray.valueAt(i); 2157 final WindowContainer<?> target = targetChange.mContainer; 2158 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", target); 2159 final WindowContainer<?> parent = target.getParent(); 2160 if (parent == lastNonPromotableParent) { 2161 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 2162 " SKIP: its sibling was rejected"); 2163 continue; 2164 } 2165 if (!canPromote(targetChange, targets, changes)) { 2166 lastNonPromotableParent = parent; 2167 continue; 2168 } 2169 if (reportIfNotTop(target)) { 2170 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 2171 " keep as target %s", target); 2172 } else { 2173 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 2174 " remove from targets %s", target); 2175 targets.remove(i); 2176 } 2177 final ChangeInfo parentChange = changes.get(parent); 2178 if (targets.mArray.indexOfValue(parentChange) < 0) { 2179 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 2180 " CAN PROMOTE: promoting to parent %s", parent); 2181 // The parent has lower depth, so it will be checked in the later iteration. 2182 i++; 2183 targets.add(parentChange); 2184 } 2185 if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_NO_ANIMATION) != 0) { 2186 parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION; 2187 } else { 2188 parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION; 2189 } 2190 } 2191 } 2192 2193 /** 2194 * Find WindowContainers to be animated from a set of opening and closing apps. We will promote 2195 * animation targets to higher level in the window hierarchy if possible. 2196 */ 2197 @VisibleForTesting 2198 @NonNull calculateTargets(ArraySet<WindowContainer> participants, ArrayMap<WindowContainer, ChangeInfo> changes)2199 static ArrayList<ChangeInfo> calculateTargets(ArraySet<WindowContainer> participants, 2200 ArrayMap<WindowContainer, ChangeInfo> changes) { 2201 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 2202 "Start calculating TransitionInfo based on participants: %s", participants); 2203 2204 // Add all valid participants to the target container. 2205 final Targets targets = new Targets(); 2206 for (int i = participants.size() - 1; i >= 0; --i) { 2207 final WindowContainer<?> wc = participants.valueAt(i); 2208 if (!wc.isAttached()) { 2209 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 2210 " Rejecting as detached: %s", wc); 2211 continue; 2212 } 2213 // The level of transition target should be at least window token. 2214 if (wc.asWindowState() != null) continue; 2215 2216 final ChangeInfo changeInfo = changes.get(wc); 2217 2218 // Reject no-ops 2219 if (!changeInfo.hasChanged()) { 2220 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 2221 " Rejecting as no-op: %s", wc); 2222 continue; 2223 } 2224 targets.add(changeInfo); 2225 } 2226 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Initial targets: %s", 2227 targets.mArray); 2228 // Combine the targets from bottom to top if possible. 2229 tryPromote(targets, changes); 2230 // Establish the relationship between the targets and their top changes. 2231 populateParentChanges(targets, changes); 2232 2233 final ArrayList<ChangeInfo> targetList = targets.getListSortedByZ(); 2234 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Final targets: %s", targetList); 2235 return targetList; 2236 } 2237 2238 /** Populates parent to the change info and collects intermediate targets. */ populateParentChanges(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)2239 private static void populateParentChanges(Targets targets, 2240 ArrayMap<WindowContainer, ChangeInfo> changes) { 2241 final ArrayList<ChangeInfo> intermediates = new ArrayList<>(); 2242 // Make a copy to iterate because the original array may be modified. 2243 final ArrayList<ChangeInfo> targetList = new ArrayList<>(targets.mArray.size()); 2244 for (int i = targets.mArray.size() - 1; i >= 0; --i) { 2245 targetList.add(targets.mArray.valueAt(i)); 2246 } 2247 for (int i = targetList.size() - 1; i >= 0; --i) { 2248 final ChangeInfo targetChange = targetList.get(i); 2249 final WindowContainer wc = targetChange.mContainer; 2250 // Wallpaper must belong to the top (regardless of how nested it is in DisplayAreas). 2251 final boolean skipIntermediateReports = isWallpaper(wc); 2252 intermediates.clear(); 2253 boolean foundParentInTargets = false; 2254 // Collect the intermediate parents between target and top changed parent. 2255 for (WindowContainer<?> p = getAnimatableParent(wc); p != null; 2256 p = getAnimatableParent(p)) { 2257 final ChangeInfo parentChange = changes.get(p); 2258 if (parentChange == null || !parentChange.hasChanged()) break; 2259 if (p.mRemoteToken == null) { 2260 // Intermediate parents must be those that has window to be managed by Shell. 2261 continue; 2262 } 2263 if (parentChange.mEndParent != null && !skipIntermediateReports) { 2264 targetChange.mEndParent = p; 2265 // The chain above the parent was processed. 2266 break; 2267 } 2268 if (targetList.contains(parentChange)) { 2269 if (skipIntermediateReports) { 2270 targetChange.mEndParent = p; 2271 } else { 2272 intermediates.add(parentChange); 2273 } 2274 foundParentInTargets = true; 2275 break; 2276 } else if (reportIfNotTop(p) && !skipIntermediateReports) { 2277 intermediates.add(parentChange); 2278 } 2279 } 2280 if (!foundParentInTargets || intermediates.isEmpty()) continue; 2281 // Add any always-report parents along the way. 2282 targetChange.mEndParent = intermediates.get(0).mContainer; 2283 for (int j = 0; j < intermediates.size() - 1; j++) { 2284 final ChangeInfo intermediate = intermediates.get(j); 2285 intermediate.mEndParent = intermediates.get(j + 1).mContainer; 2286 targets.add(intermediate); 2287 } 2288 } 2289 } 2290 2291 /** 2292 * Gets the leash surface for a window container. 2293 * @param t a transaction to create leashes on when necessary (fixed rotation at token-level). 2294 * If t is null, then this will not create any leashes, just use one if it is there -- 2295 * this is relevant for building the finishTransaction since it needs to match the 2296 * start state and not erroneously create a leash of its own. 2297 */ getLeashSurface(WindowContainer wc, @Nullable SurfaceControl.Transaction t)2298 private static SurfaceControl getLeashSurface(WindowContainer wc, 2299 @Nullable SurfaceControl.Transaction t) { 2300 final DisplayContent asDC = wc.asDisplayContent(); 2301 if (asDC != null) { 2302 // DisplayContent is the "root", so we use the windowing layer instead to avoid 2303 // hardware-screen-level surfaces. 2304 return asDC.getWindowingLayer(); 2305 } 2306 if (!wc.mTransitionController.useShellTransitionsRotation()) { 2307 final WindowToken asToken = wc.asWindowToken(); 2308 if (asToken != null) { 2309 // WindowTokens can have a fixed-rotation applied to them. In the current 2310 // implementation this fact is hidden from the player, so we must create a leash. 2311 final SurfaceControl leash = t != null ? asToken.getOrCreateFixedRotationLeash(t) 2312 : asToken.getFixedRotationLeash(); 2313 if (leash != null) return leash; 2314 } 2315 } 2316 return wc.getSurfaceControl(); 2317 } 2318 getOrigParentSurface(WindowContainer wc)2319 private static SurfaceControl getOrigParentSurface(WindowContainer wc) { 2320 if (wc.asDisplayContent() != null) { 2321 // DisplayContent is the "root", so we reinterpret it's wc as the window layer 2322 // making the parent surface the displaycontent's surface. 2323 return wc.getSurfaceControl(); 2324 } else if (wc.getParent().asDisplayContent() != null) { 2325 // DisplayContent is kinda split into 2 pieces, the "real root" and the 2326 // "windowing layer". So if the parent of the window is DC, then it really belongs on 2327 // the windowing layer (unless it's an overlay display area, but those can't be in 2328 // transitions anyways). 2329 return wc.getParent().asDisplayContent().getWindowingLayer(); 2330 } 2331 return wc.getParent().getSurfaceControl(); 2332 } 2333 2334 /** 2335 * A ready group is defined by a root window-container where all transitioning windows under 2336 * it are expected to animate together as a group. At the moment, this treats each display as 2337 * a ready-group to match the existing legacy transition behavior. 2338 */ isReadyGroup(WindowContainer wc)2339 private static boolean isReadyGroup(WindowContainer wc) { 2340 return wc instanceof DisplayContent; 2341 } 2342 getDisplayId(@onNull WindowContainer wc)2343 private static int getDisplayId(@NonNull WindowContainer wc) { 2344 return wc.getDisplayContent() != null 2345 ? wc.getDisplayContent().getDisplayId() : INVALID_DISPLAY; 2346 } 2347 2348 @VisibleForTesting calculateTransitionRoots(@onNull TransitionInfo outInfo, ArrayList<ChangeInfo> sortedTargets, @NonNull SurfaceControl.Transaction startT)2349 static void calculateTransitionRoots(@NonNull TransitionInfo outInfo, 2350 ArrayList<ChangeInfo> sortedTargets, 2351 @NonNull SurfaceControl.Transaction startT) { 2352 // There needs to be a root on each display. 2353 for (int i = 0; i < sortedTargets.size(); ++i) { 2354 final WindowContainer<?> wc = sortedTargets.get(i).mContainer; 2355 // Don't include wallpapers since they are in a different DA. 2356 if (isWallpaper(wc)) continue; 2357 final DisplayContent dc = wc.getDisplayContent(); 2358 if (dc == null) continue; 2359 final int endDisplayId = dc.getDisplayId(); 2360 2361 // Check if Root was already created for this display with a higher-Z window 2362 if (outInfo.findRootIndex(endDisplayId) >= 0) continue; 2363 2364 WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, wc); 2365 2366 // Make leash based on highest (z-order) direct child of ancestor with a participant. 2367 // Check whether the ancestor is belonged to last parent, shouldn't happen. 2368 final boolean hasReparent = !wc.isDescendantOf(ancestor); 2369 WindowContainer leashReference = wc; 2370 if (hasReparent) { 2371 Slog.e(TAG, "Did not find common ancestor! Ancestor= " + ancestor 2372 + " target= " + wc); 2373 } else { 2374 while (leashReference.getParent() != ancestor) { 2375 leashReference = leashReference.getParent(); 2376 } 2377 } 2378 final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName( 2379 "Transition Root: " + leashReference.getName()).build(); 2380 rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots"); 2381 // Update layers to start transaction because we prevent assignment during collect, so 2382 // the layer of transition root can be correct. 2383 updateDisplayLayers(dc, startT); 2384 startT.setLayer(rootLeash, leashReference.getLastLayer()); 2385 outInfo.addRootLeash(endDisplayId, rootLeash, 2386 ancestor.getBounds().left, ancestor.getBounds().top); 2387 } 2388 } 2389 2390 /** 2391 * Construct a TransitionInfo object from a set of targets and changes. Also populates the 2392 * root surface. 2393 * @param sortedTargets The targets sorted by z-order from top (index 0) to bottom. 2394 * @param startT The start transaction - used to set-up new leashes. 2395 */ 2396 @VisibleForTesting 2397 @NonNull calculateTransitionInfo(@ransitionType int type, int flags, ArrayList<ChangeInfo> sortedTargets, @NonNull SurfaceControl.Transaction startT)2398 static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags, 2399 ArrayList<ChangeInfo> sortedTargets, 2400 @NonNull SurfaceControl.Transaction startT) { 2401 final TransitionInfo out = new TransitionInfo(type, flags); 2402 calculateTransitionRoots(out, sortedTargets, startT); 2403 if (out.getRootCount() == 0) { 2404 return out; 2405 } 2406 2407 // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order. 2408 final int count = sortedTargets.size(); 2409 for (int i = 0; i < count; ++i) { 2410 final ChangeInfo info = sortedTargets.get(i); 2411 final WindowContainer target = info.mContainer; 2412 final TransitionInfo.Change change = new TransitionInfo.Change( 2413 target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken() 2414 : null, getLeashSurface(target, startT)); 2415 // TODO(shell-transitions): Use leash for non-organized windows. 2416 if (info.mEndParent != null) { 2417 change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken()); 2418 } 2419 if (info.mStartParent != null && info.mStartParent.mRemoteToken != null 2420 && target.getParent() != info.mStartParent) { 2421 change.setLastParent(info.mStartParent.mRemoteToken.toWindowContainerToken()); 2422 } 2423 change.setMode(info.getTransitMode(target)); 2424 info.mReadyMode = change.getMode(); 2425 change.setStartAbsBounds(info.mAbsoluteBounds); 2426 change.setFlags(info.getChangeFlags(target)); 2427 info.mReadyFlags = change.getFlags(); 2428 change.setDisplayId(info.mDisplayId, getDisplayId(target)); 2429 2430 final Task task = target.asTask(); 2431 final TaskFragment taskFragment = target.asTaskFragment(); 2432 final ActivityRecord activityRecord = target.asActivityRecord(); 2433 2434 if (task != null) { 2435 final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo(); 2436 task.fillTaskInfo(tinfo); 2437 change.setTaskInfo(tinfo); 2438 change.setRotationAnimation(getTaskRotationAnimation(task)); 2439 final ActivityRecord topRunningActivity = task.topRunningActivity(); 2440 if (topRunningActivity != null) { 2441 if (topRunningActivity.info.supportsPictureInPicture()) { 2442 change.setAllowEnterPip( 2443 topRunningActivity.checkEnterPictureInPictureAppOpsState()); 2444 } 2445 setEndFixedRotationIfNeeded(change, task, topRunningActivity); 2446 } 2447 } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) { 2448 change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS); 2449 } 2450 2451 final WindowContainer<?> parent = target.getParent(); 2452 final Rect bounds = target.getBounds(); 2453 final Rect parentBounds = parent.getBounds(); 2454 change.setEndRelOffset(bounds.left - parentBounds.left, 2455 bounds.top - parentBounds.top); 2456 int endRotation = target.getWindowConfiguration().getRotation(); 2457 if (activityRecord != null) { 2458 // TODO(b/227427984): Shell needs to aware letterbox. 2459 // Always use parent bounds of activity because letterbox area (e.g. fixed aspect 2460 // ratio or size compat mode) should be included in the animation. 2461 change.setEndAbsBounds(parentBounds); 2462 if (activityRecord.getRelativeDisplayRotation() != 0 2463 && !activityRecord.mTransitionController.useShellTransitionsRotation()) { 2464 // Use parent rotation because shell doesn't know the surface is rotated. 2465 endRotation = parent.getWindowConfiguration().getRotation(); 2466 } 2467 } else { 2468 change.setEndAbsBounds(bounds); 2469 } 2470 2471 if (activityRecord != null || (taskFragment != null && taskFragment.isEmbedded())) { 2472 final int backgroundColor; 2473 final TaskFragment organizedTf = activityRecord != null 2474 ? activityRecord.getOrganizedTaskFragment() 2475 : taskFragment.getOrganizedTaskFragment(); 2476 if (organizedTf != null && organizedTf.getAnimationParams() 2477 .getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) { 2478 // This window is embedded and has an animation background color set on the 2479 // TaskFragment. Pass this color with this window, so the handler can use it as 2480 // the animation background color if needed, 2481 backgroundColor = organizedTf.getAnimationParams() 2482 .getAnimationBackgroundColor(); 2483 } else { 2484 // Set background color to Task theme color for activity and embedded 2485 // TaskFragment in case we want to show background during the animation. 2486 final Task parentTask = activityRecord != null 2487 ? activityRecord.getTask() 2488 : taskFragment.getTask(); 2489 backgroundColor = parentTask.getTaskDescription().getBackgroundColor(); 2490 } 2491 // Set to opaque for animation background to prevent it from exposing the blank 2492 // background or content below. 2493 change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255)); 2494 } 2495 2496 change.setRotation(info.mRotation, endRotation); 2497 if (info.mSnapshot != null) { 2498 change.setSnapshot(info.mSnapshot, info.mSnapshotLuma); 2499 } 2500 2501 out.addChange(change); 2502 } 2503 2504 TransitionInfo.AnimationOptions animOptions = null; 2505 2506 // Check if the top-most app is an activity (ie. activity->activity). If so, make sure to 2507 // honor its custom transition options. 2508 WindowContainer<?> topApp = null; 2509 for (int i = 0; i < sortedTargets.size(); i++) { 2510 if (isWallpaper(sortedTargets.get(i).mContainer)) continue; 2511 topApp = sortedTargets.get(i).mContainer; 2512 break; 2513 } 2514 if (topApp.asActivityRecord() != null) { 2515 final ActivityRecord topActivity = topApp.asActivityRecord(); 2516 animOptions = addCustomActivityTransition(topActivity, true/* open */, null); 2517 animOptions = addCustomActivityTransition(topActivity, false/* open */, animOptions); 2518 } 2519 final WindowManager.LayoutParams animLp = 2520 getLayoutParamsForAnimationsStyle(type, sortedTargets); 2521 if (animLp != null && animLp.type != TYPE_APPLICATION_STARTING 2522 && animLp.windowAnimations != 0) { 2523 // Don't send animation options if no windowAnimations have been set or if the we are 2524 // running an app starting animation, in which case we don't want the app to be able to 2525 // change its animation directly. 2526 if (animOptions != null) { 2527 animOptions.addOptionsFromLayoutParameters(animLp); 2528 } else { 2529 animOptions = TransitionInfo.AnimationOptions 2530 .makeAnimOptionsFromLayoutParameters(animLp); 2531 } 2532 } 2533 if (animOptions != null) { 2534 out.setAnimationOptions(animOptions); 2535 } 2536 return out; 2537 } 2538 addCustomActivityTransition(ActivityRecord topActivity, boolean open, TransitionInfo.AnimationOptions animOptions)2539 static TransitionInfo.AnimationOptions addCustomActivityTransition(ActivityRecord topActivity, 2540 boolean open, TransitionInfo.AnimationOptions animOptions) { 2541 final ActivityRecord.CustomAppTransition customAnim = 2542 topActivity.getCustomAnimation(open); 2543 if (customAnim != null) { 2544 if (animOptions == null) { 2545 animOptions = TransitionInfo.AnimationOptions 2546 .makeCommonAnimOptions(topActivity.packageName); 2547 } 2548 animOptions.addCustomActivityTransition(open, customAnim.mEnterAnim, 2549 customAnim.mExitAnim, customAnim.mBackgroundColor); 2550 } 2551 return animOptions; 2552 } 2553 setEndFixedRotationIfNeeded(@onNull TransitionInfo.Change change, @NonNull Task task, @NonNull ActivityRecord taskTopRunning)2554 private static void setEndFixedRotationIfNeeded(@NonNull TransitionInfo.Change change, 2555 @NonNull Task task, @NonNull ActivityRecord taskTopRunning) { 2556 if (!taskTopRunning.isVisibleRequested()) { 2557 // Fixed rotation only applies to opening or changing activity. 2558 return; 2559 } 2560 if (task.inMultiWindowMode() && taskTopRunning.inMultiWindowMode()) { 2561 // Display won't be rotated for multi window Task, so the fixed rotation won't be 2562 // applied. This can happen when the windowing mode is changed before the previous 2563 // fixed rotation is applied. Check both task and activity because the activity keeps 2564 // fullscreen mode when the task is entering PiP. 2565 return; 2566 } 2567 final int taskRotation = task.getWindowConfiguration().getDisplayRotation(); 2568 final int activityRotation = taskTopRunning.getWindowConfiguration() 2569 .getDisplayRotation(); 2570 // If the Activity uses fixed rotation, its rotation will be applied to display after 2571 // the current transition is done, while the Task is still in the previous rotation. 2572 if (taskRotation != activityRotation) { 2573 change.setEndFixedRotation(activityRotation); 2574 return; 2575 } 2576 2577 // For example, the task is entering PiP so it no longer decides orientation. If the next 2578 // orientation source (it could be an activity which was behind the PiP or launching to top) 2579 // will change display rotation, then set the fixed rotation hint as well so the animation 2580 // can consider the rotated position. 2581 if (!task.inPinnedWindowingMode() || taskTopRunning.mDisplayContent.inTransition()) { 2582 return; 2583 } 2584 final WindowContainer<?> orientationSource = 2585 taskTopRunning.mDisplayContent.getLastOrientationSource(); 2586 if (orientationSource == null) { 2587 return; 2588 } 2589 final int nextRotation = orientationSource.getWindowConfiguration().getDisplayRotation(); 2590 if (taskRotation != nextRotation) { 2591 change.setEndFixedRotation(nextRotation); 2592 } 2593 } 2594 2595 /** 2596 * Finds the top-most common ancestor of app targets. 2597 * 2598 * Makes sure that the previous parent is also a descendant to make sure the animation won't 2599 * be covered by other windows below the previous parent. For example, when reparenting an 2600 * activity from PiP Task to split screen Task. 2601 */ 2602 @NonNull findCommonAncestor( @onNull ArrayList<ChangeInfo> targets, @NonNull WindowContainer<?> topApp)2603 private static WindowContainer<?> findCommonAncestor( 2604 @NonNull ArrayList<ChangeInfo> targets, 2605 @NonNull WindowContainer<?> topApp) { 2606 final int displayId = getDisplayId(topApp); 2607 WindowContainer<?> ancestor = topApp.getParent(); 2608 // Go up ancestor parent chain until all targets are descendants. Ancestor should never be 2609 // null because all targets are attached. 2610 for (int i = targets.size() - 1; i >= 0; i--) { 2611 final ChangeInfo change = targets.get(i); 2612 final WindowContainer wc = change.mContainer; 2613 if (isWallpaper(wc) || getDisplayId(wc) != displayId) { 2614 // Skip the non-app window or windows on a different display 2615 continue; 2616 } 2617 // Re-initiate the last parent as the initial ancestor instead of the top target. 2618 // When move a leaf task from organized task to display area, try to keep the transition 2619 // root be the original organized task for close transition animation. 2620 // Otherwise, shell will use wrong root layer to play animation. 2621 // Note: Since the target is sorted, so only need to do this at the lowest target. 2622 if (change.mStartParent != null && wc.getParent() != null 2623 && change.mStartParent.isAttached() && wc.getParent() != change.mStartParent 2624 && i == targets.size() - 1) { 2625 final int transitionMode = change.getTransitMode(wc); 2626 if (transitionMode == TRANSIT_CLOSE || transitionMode == TRANSIT_TO_BACK) { 2627 ancestor = change.mStartParent; 2628 continue; 2629 } 2630 } 2631 while (!wc.isDescendantOf(ancestor)) { 2632 ancestor = ancestor.getParent(); 2633 } 2634 2635 // Make sure the previous parent is also a descendant to make sure the animation won't 2636 // be covered by other windows below the previous parent. For example, when reparenting 2637 // an activity from PiP Task to split screen Task. 2638 final WindowContainer prevParent = change.mCommonAncestor; 2639 if (prevParent == null || !prevParent.isAttached()) { 2640 continue; 2641 } 2642 while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) { 2643 ancestor = ancestor.getParent(); 2644 } 2645 } 2646 return ancestor; 2647 } 2648 getLayoutParamsForAnimationsStyle(int type, ArrayList<ChangeInfo> sortedTargets)2649 private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type, 2650 ArrayList<ChangeInfo> sortedTargets) { 2651 // Find the layout params of the top-most application window that is part of the 2652 // transition, which is what will control the animation theme. 2653 final ArraySet<Integer> activityTypes = new ArraySet<>(); 2654 final int targetCount = sortedTargets.size(); 2655 for (int i = 0; i < targetCount; ++i) { 2656 final WindowContainer target = sortedTargets.get(i).mContainer; 2657 if (target.asActivityRecord() != null) { 2658 activityTypes.add(target.getActivityType()); 2659 } else if (target.asWindowToken() == null && target.asWindowState() == null) { 2660 // We don't want app to customize animations that are not activity to activity. 2661 // Activity-level transitions can only include activities, wallpaper and subwindows. 2662 // Anything else is not a WindowToken nor a WindowState and is "higher" in the 2663 // hierarchy which means we are no longer in an activity transition. 2664 return null; 2665 } 2666 } 2667 if (activityTypes.isEmpty()) { 2668 // We don't want app to be able to customize transitions that are not activity to 2669 // activity through the layout parameter animation style. 2670 return null; 2671 } 2672 final ActivityRecord animLpActivity = 2673 findAnimLayoutParamsActivityRecord(sortedTargets, type, activityTypes); 2674 final WindowState mainWindow = animLpActivity != null 2675 ? animLpActivity.findMainWindow() : null; 2676 return mainWindow != null ? mainWindow.mAttrs : null; 2677 } 2678 findAnimLayoutParamsActivityRecord( List<ChangeInfo> sortedTargets, @TransitionType int transit, ArraySet<Integer> activityTypes)2679 private static ActivityRecord findAnimLayoutParamsActivityRecord( 2680 List<ChangeInfo> sortedTargets, 2681 @TransitionType int transit, ArraySet<Integer> activityTypes) { 2682 // Remote animations always win, but fullscreen windows override non-fullscreen windows. 2683 ActivityRecord result = lookForTopWindowWithFilter(sortedTargets, 2684 w -> w.getRemoteAnimationDefinition() != null 2685 && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes)); 2686 if (result != null) { 2687 return result; 2688 } 2689 result = lookForTopWindowWithFilter(sortedTargets, 2690 w -> w.fillsParent() && w.findMainWindow() != null); 2691 if (result != null) { 2692 return result; 2693 } 2694 return lookForTopWindowWithFilter(sortedTargets, w -> w.findMainWindow() != null); 2695 } 2696 lookForTopWindowWithFilter(List<ChangeInfo> sortedTargets, Predicate<ActivityRecord> filter)2697 private static ActivityRecord lookForTopWindowWithFilter(List<ChangeInfo> sortedTargets, 2698 Predicate<ActivityRecord> filter) { 2699 final int count = sortedTargets.size(); 2700 for (int i = 0; i < count; ++i) { 2701 final WindowContainer target = sortedTargets.get(i).mContainer; 2702 final ActivityRecord activityRecord = target.asTaskFragment() != null 2703 ? target.asTaskFragment().getTopNonFinishingActivity() 2704 : target.asActivityRecord(); 2705 if (activityRecord != null && filter.test(activityRecord)) { 2706 return activityRecord; 2707 } 2708 } 2709 return null; 2710 } 2711 getTaskRotationAnimation(@onNull Task task)2712 private static int getTaskRotationAnimation(@NonNull Task task) { 2713 final ActivityRecord top = task.getTopVisibleActivity(); 2714 if (top == null) return ROTATION_ANIMATION_UNSPECIFIED; 2715 final WindowState mainWin = top.findMainWindow(false); 2716 if (mainWin == null) return ROTATION_ANIMATION_UNSPECIFIED; 2717 int anim = mainWin.getRotationAnimationHint(); 2718 if (anim >= 0) return anim; 2719 anim = mainWin.getAttrs().rotationAnimation; 2720 if (anim != ROTATION_ANIMATION_SEAMLESS) return anim; 2721 if (mainWin != task.mDisplayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow() 2722 || !top.matchParentBounds()) { 2723 // At the moment, we only support seamless rotation if there is only one window showing. 2724 return ROTATION_ANIMATION_UNSPECIFIED; 2725 } 2726 return mainWin.getAttrs().rotationAnimation; 2727 } 2728 validateKeyguardOcclusion()2729 private void validateKeyguardOcclusion() { 2730 if ((mFlags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) { 2731 mController.mStateValidators.add( 2732 mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange); 2733 } 2734 } 2735 2736 /** 2737 * Returns {@code true} if the transition and the corresponding transaction should be applied 2738 * on display thread. Currently, this only checks for display rotation change because the order 2739 * of dispatching the new display info will be after requesting the windows to sync drawing. 2740 * That avoids potential flickering of screen overlays (e.g. cutout, rounded corner). Also, 2741 * because the display thread has a higher priority, it is faster to perform the configuration 2742 * changes and window hierarchy traversal. 2743 */ shouldApplyOnDisplayThread()2744 boolean shouldApplyOnDisplayThread() { 2745 for (int i = mParticipants.size() - 1; i >= 0; --i) { 2746 final DisplayContent dc = mParticipants.valueAt(i).asDisplayContent(); 2747 if (dc == null) continue; 2748 final ChangeInfo changeInfo = mChanges.get(dc); 2749 if (changeInfo != null && changeInfo.mRotation != dc.getRotation()) { 2750 return Looper.myLooper() != mController.mAtm.mWindowManager.mH.getLooper(); 2751 } 2752 } 2753 return false; 2754 } 2755 2756 /** Applies the new configuration for the changed displays. */ applyDisplayChangeIfNeeded()2757 void applyDisplayChangeIfNeeded() { 2758 for (int i = mParticipants.size() - 1; i >= 0; --i) { 2759 final WindowContainer<?> wc = mParticipants.valueAt(i); 2760 final DisplayContent dc = wc.asDisplayContent(); 2761 if (dc == null || !mChanges.get(dc).hasChanged()) continue; 2762 dc.sendNewConfiguration(); 2763 // Set to ready if no other change controls the ready state. But if there is, such as 2764 // if an activity is pausing, it will call setReady(ar, false) and wait for the next 2765 // resumed activity. Then do not set to ready because the transition only contains 2766 // partial participants. Otherwise the transition may only handle HIDE and miss OPEN. 2767 if (!mReadyTracker.mUsed) { 2768 setReady(dc, true); 2769 } 2770 } 2771 } 2772 getLegacyIsReady()2773 boolean getLegacyIsReady() { 2774 return isCollecting() && mSyncId >= 0; 2775 } 2776 asyncTraceBegin(@onNull String name, int cookie)2777 static void asyncTraceBegin(@NonNull String name, int cookie) { 2778 Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, name, cookie); 2779 } 2780 asyncTraceEnd(int cookie)2781 static void asyncTraceEnd(int cookie) { 2782 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie); 2783 } 2784 2785 @VisibleForTesting 2786 static class ChangeInfo { 2787 private static final int FLAG_NONE = 0; 2788 2789 /** 2790 * When set, the associated WindowContainer has been explicitly requested to be a 2791 * seamless rotation. This is currently only used by DisplayContent during fixed-rotation. 2792 */ 2793 private static final int FLAG_SEAMLESS_ROTATION = 1; 2794 private static final int FLAG_TRANSIENT_LAUNCH = 2; 2795 private static final int FLAG_ABOVE_TRANSIENT_LAUNCH = 4; 2796 2797 /** This container explicitly requested no-animation (usually Activity level). */ 2798 private static final int FLAG_CHANGE_NO_ANIMATION = 0x8; 2799 /** 2800 * This container has at-least one child which IS animating (not marked NO_ANIMATION). 2801 * Used during promotion. This trumps `FLAG_NO_ANIMATION` (if both are set). 2802 */ 2803 private static final int FLAG_CHANGE_YES_ANIMATION = 0x10; 2804 2805 /** Whether this change's container moved to the top. */ 2806 private static final int FLAG_CHANGE_MOVED_TO_TOP = 0x20; 2807 2808 @IntDef(prefix = { "FLAG_" }, value = { 2809 FLAG_NONE, 2810 FLAG_SEAMLESS_ROTATION, 2811 FLAG_TRANSIENT_LAUNCH, 2812 FLAG_ABOVE_TRANSIENT_LAUNCH, 2813 FLAG_CHANGE_NO_ANIMATION, 2814 FLAG_CHANGE_YES_ANIMATION, 2815 FLAG_CHANGE_MOVED_TO_TOP 2816 }) 2817 @Retention(RetentionPolicy.SOURCE) 2818 @interface Flag {} 2819 2820 @NonNull final WindowContainer mContainer; 2821 /** 2822 * "Parent" that is also included in the transition. When populating the parent changes, we 2823 * may skip the intermediate parents, so this may not be the actual parent in the hierarchy. 2824 */ 2825 WindowContainer mEndParent; 2826 /** Actual parent window before change state. */ 2827 WindowContainer mStartParent; 2828 /** 2829 * When the window is reparented during the transition, this is the common ancestor window 2830 * of the {@link #mStartParent} and the current parent. This is needed because the 2831 * {@link #mStartParent} may have been detached when the transition starts. 2832 */ 2833 WindowContainer mCommonAncestor; 2834 2835 // State tracking 2836 boolean mExistenceChanged = false; 2837 // before change state 2838 boolean mVisible; 2839 int mWindowingMode; 2840 final Rect mAbsoluteBounds = new Rect(); 2841 boolean mShowWallpaper; 2842 int mRotation = ROTATION_UNDEFINED; 2843 int mDisplayId = -1; 2844 @ActivityInfo.Config int mKnownConfigChanges; 2845 2846 /** Extra information about this change. */ 2847 @Flag int mFlags = FLAG_NONE; 2848 2849 /** Snapshot surface and luma, if relevant. */ 2850 SurfaceControl mSnapshot; 2851 float mSnapshotLuma; 2852 2853 /** The mode which is set when the transition is ready. */ 2854 @TransitionInfo.TransitionMode 2855 int mReadyMode; 2856 2857 /** The flags which is set when the transition is ready. */ 2858 @TransitionInfo.ChangeFlags 2859 int mReadyFlags; 2860 ChangeInfo(@onNull WindowContainer origState)2861 ChangeInfo(@NonNull WindowContainer origState) { 2862 mContainer = origState; 2863 mVisible = origState.isVisibleRequested(); 2864 mWindowingMode = origState.getWindowingMode(); 2865 mAbsoluteBounds.set(origState.getBounds()); 2866 mShowWallpaper = origState.showWallpaper(); 2867 mRotation = origState.getWindowConfiguration().getRotation(); 2868 mStartParent = origState.getParent(); 2869 mDisplayId = getDisplayId(origState); 2870 } 2871 2872 @VisibleForTesting ChangeInfo(@onNull WindowContainer container, boolean visible, boolean existChange)2873 ChangeInfo(@NonNull WindowContainer container, boolean visible, boolean existChange) { 2874 this(container); 2875 mVisible = visible; 2876 mExistenceChanged = existChange; 2877 mShowWallpaper = false; 2878 } 2879 2880 @Override toString()2881 public String toString() { 2882 return mContainer.toString(); 2883 } 2884 hasChanged()2885 boolean hasChanged() { 2886 // the task including transient launch must promote to root task 2887 if ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0 2888 || (mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) { 2889 return true; 2890 } 2891 // If it's invisible and hasn't changed visibility, always return false since even if 2892 // something changed, it wouldn't be a visible change. 2893 final boolean currVisible = mContainer.isVisibleRequested(); 2894 if (currVisible == mVisible && !mVisible) return false; 2895 return currVisible != mVisible 2896 || mKnownConfigChanges != 0 2897 // if mWindowingMode is 0, this container wasn't attached at collect time, so 2898 // assume no change in windowing-mode. 2899 || (mWindowingMode != 0 && mContainer.getWindowingMode() != mWindowingMode) 2900 || !mContainer.getBounds().equals(mAbsoluteBounds) 2901 || mRotation != mContainer.getWindowConfiguration().getRotation() 2902 || mDisplayId != getDisplayId(mContainer) 2903 || (mFlags & ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP) != 0; 2904 } 2905 2906 @TransitionInfo.TransitionMode getTransitMode(@onNull WindowContainer wc)2907 int getTransitMode(@NonNull WindowContainer wc) { 2908 if ((mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) { 2909 return mExistenceChanged ? TRANSIT_CLOSE : TRANSIT_TO_BACK; 2910 } 2911 final boolean nowVisible = wc.isVisibleRequested(); 2912 if (nowVisible == mVisible) { 2913 return TRANSIT_CHANGE; 2914 } 2915 if (mExistenceChanged) { 2916 return nowVisible ? TRANSIT_OPEN : TRANSIT_CLOSE; 2917 } else { 2918 return nowVisible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK; 2919 } 2920 } 2921 2922 @TransitionInfo.ChangeFlags getChangeFlags(@onNull WindowContainer wc)2923 int getChangeFlags(@NonNull WindowContainer wc) { 2924 int flags = 0; 2925 if (mShowWallpaper || wc.showWallpaper()) { 2926 flags |= FLAG_SHOW_WALLPAPER; 2927 } 2928 if (isTranslucent(wc)) { 2929 flags |= FLAG_TRANSLUCENT; 2930 } 2931 if (wc.mWmService.mAtmService.mBackNavigationController.isMonitorTransitionTarget(wc)) { 2932 flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; 2933 } 2934 final Task task = wc.asTask(); 2935 if (task != null) { 2936 final ActivityRecord topActivity = task.getTopNonFinishingActivity(); 2937 if (topActivity != null) { 2938 if (topActivity.mStartingData != null 2939 && topActivity.mStartingData.hasImeSurface()) { 2940 flags |= FLAG_WILL_IME_SHOWN; 2941 } 2942 if (topActivity.mLaunchTaskBehind) { 2943 Slog.e(TAG, "Unexpected launch-task-behind operation in shell transition"); 2944 flags |= FLAG_TASK_LAUNCHING_BEHIND; 2945 } 2946 } 2947 if (task.voiceSession != null) { 2948 flags |= FLAG_IS_VOICE_INTERACTION; 2949 } 2950 } 2951 Task parentTask = null; 2952 final ActivityRecord record = wc.asActivityRecord(); 2953 if (record != null) { 2954 parentTask = record.getTask(); 2955 if (record.mVoiceInteraction) { 2956 flags |= FLAG_IS_VOICE_INTERACTION; 2957 } 2958 flags |= record.mTransitionChangeFlags; 2959 } 2960 final TaskFragment taskFragment = wc.asTaskFragment(); 2961 if (taskFragment != null && task == null) { 2962 parentTask = taskFragment.getTask(); 2963 } 2964 if (parentTask != null) { 2965 if (parentTask.forAllLeafTaskFragments(TaskFragment::isEmbedded)) { 2966 // Whether this is in a Task with embedded activity. 2967 flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; 2968 } 2969 if (parentTask.forAllActivities(ActivityRecord::hasStartingWindow)) { 2970 // The starting window should cover all windows inside the leaf Task. 2971 flags |= FLAG_IS_BEHIND_STARTING_WINDOW; 2972 } 2973 if (isWindowFillingTask(wc, parentTask)) { 2974 // Whether the container fills its parent Task bounds. 2975 flags |= FLAG_FILLS_TASK; 2976 } 2977 } else { 2978 final DisplayContent dc = wc.asDisplayContent(); 2979 if (dc != null) { 2980 flags |= FLAG_IS_DISPLAY; 2981 if (dc.hasAlertWindowSurfaces()) { 2982 flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS; 2983 } 2984 } else if (isWallpaper(wc)) { 2985 flags |= FLAG_IS_WALLPAPER; 2986 } else if (isInputMethod(wc)) { 2987 flags |= FLAG_IS_INPUT_METHOD; 2988 } else { 2989 // In this condition, the wc can only be WindowToken or DisplayArea. 2990 final int type = wc.getWindowType(); 2991 if (type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW 2992 && type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) { 2993 flags |= TransitionInfo.FLAG_IS_SYSTEM_WINDOW; 2994 } 2995 } 2996 } 2997 if ((mFlags & FLAG_CHANGE_NO_ANIMATION) != 0 2998 && (mFlags & FLAG_CHANGE_YES_ANIMATION) == 0) { 2999 flags |= FLAG_NO_ANIMATION; 3000 } 3001 if ((mFlags & FLAG_CHANGE_MOVED_TO_TOP) != 0) { 3002 flags |= FLAG_MOVED_TO_TOP; 3003 } 3004 return flags; 3005 } 3006 3007 /** Whether the container fills its parent Task bounds before and after the transition. */ isWindowFillingTask(@onNull WindowContainer wc, @NonNull Task parentTask)3008 private boolean isWindowFillingTask(@NonNull WindowContainer wc, @NonNull Task parentTask) { 3009 final Rect taskBounds = parentTask.getBounds(); 3010 final int taskWidth = taskBounds.width(); 3011 final int taskHeight = taskBounds.height(); 3012 final Rect startBounds = mAbsoluteBounds; 3013 final Rect endBounds = wc.getBounds(); 3014 // Treat it as filling the task if it is not visible. 3015 final boolean isInvisibleOrFillingTaskBeforeTransition = !mVisible 3016 || (taskWidth == startBounds.width() && taskHeight == startBounds.height()); 3017 final boolean isInVisibleOrFillingTaskAfterTransition = !wc.isVisibleRequested() 3018 || (taskWidth == endBounds.width() && taskHeight == endBounds.height()); 3019 return isInvisibleOrFillingTaskBeforeTransition 3020 && isInVisibleOrFillingTaskAfterTransition; 3021 } 3022 } 3023 3024 /** 3025 * This transition will be considered not-ready until a corresponding call to 3026 * {@link #continueTransitionReady} 3027 */ deferTransitionReady()3028 void deferTransitionReady() { 3029 ++mReadyTracker.mDeferReadyDepth; 3030 // Make sure it wait until #continueTransitionReady() is called. 3031 mSyncEngine.setReady(mSyncId, false); 3032 } 3033 3034 /** This undoes one call to {@link #deferTransitionReady}. */ continueTransitionReady()3035 void continueTransitionReady() { 3036 --mReadyTracker.mDeferReadyDepth; 3037 // Apply ready in case it is waiting for the previous defer call. 3038 applyReady(); 3039 } 3040 3041 /** 3042 * The transition sync mechanism has 2 parts: 3043 * 1. Whether all WM operations for a particular transition are "ready" (eg. did the app 3044 * launch or stop or get a new configuration?). 3045 * 2. Whether all the windows involved have finished drawing their final-state content. 3046 * 3047 * A transition animation can play once both parts are complete. This ready-tracker keeps track 3048 * of part (1). Currently, WM code assumes that "readiness" (part 1) is grouped. This means that 3049 * even if the WM operations in one group are ready, the whole transition itself may not be 3050 * ready if there are WM operations still pending in another group. This class helps keep track 3051 * of readiness across the multiple groups. Currently, we assume that each display is a group 3052 * since that is how it has been until now. 3053 */ 3054 private static class ReadyTracker { 3055 private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>(); 3056 3057 /** 3058 * Ensures that this doesn't report as allReady before it has been used. This is needed 3059 * in very niche cases where a transition is a no-op (nothing has been collected) but we 3060 * still want to be marked ready (via. setAllReady). 3061 */ 3062 private boolean mUsed = false; 3063 3064 /** 3065 * If true, this overrides all ready groups and reports ready. Used by shell-initiated 3066 * transitions via {@link #setAllReady()}. 3067 */ 3068 private boolean mReadyOverride = false; 3069 3070 /** 3071 * When non-zero, this transition is forced not-ready (even over setAllReady()). Use this 3072 * (via deferTransitionReady/continueTransitionReady) for situations where we want to do 3073 * bulk operations which could trigger surface-placement but the existing ready-state 3074 * isn't known. 3075 */ 3076 private int mDeferReadyDepth = 0; 3077 3078 /** 3079 * Adds a ready-group. Any setReady calls in this subtree will be tracked together. For 3080 * now these are only DisplayContents. 3081 */ addGroup(WindowContainer wc)3082 void addGroup(WindowContainer wc) { 3083 if (mReadyGroups.containsKey(wc)) { 3084 Slog.e(TAG, "Trying to add a ready-group twice: " + wc); 3085 return; 3086 } 3087 mReadyGroups.put(wc, false); 3088 } 3089 3090 /** 3091 * Sets a group's ready state. 3092 * @param wc Any container within a group's subtree. Used to identify the ready-group. 3093 */ setReadyFrom(WindowContainer wc, boolean ready)3094 void setReadyFrom(WindowContainer wc, boolean ready) { 3095 mUsed = true; 3096 WindowContainer current = wc; 3097 while (current != null) { 3098 if (isReadyGroup(current)) { 3099 mReadyGroups.put(current, ready); 3100 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting Ready-group to" 3101 + " %b. group=%s from %s", ready, current, wc); 3102 break; 3103 } 3104 current = current.getParent(); 3105 } 3106 } 3107 3108 /** Marks this as ready regardless of individual groups. */ setAllReady()3109 void setAllReady() { 3110 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting allReady override"); 3111 mUsed = true; 3112 mReadyOverride = true; 3113 } 3114 3115 /** @return true if all tracked subtrees are ready. */ allReady()3116 boolean allReady() { 3117 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b " 3118 + "override=%b defer=%d states=[%s]", mUsed, mReadyOverride, mDeferReadyDepth, 3119 groupsToString()); 3120 // If the readiness has never been touched, mUsed will be false. We never want to 3121 // consider a transition ready if nothing has been reported on it. 3122 if (!mUsed) return false; 3123 // If we are deferring readiness, we never report ready. This is usually temporary. 3124 if (mDeferReadyDepth > 0) return false; 3125 // Next check all the ready groups to see if they are ready. We can short-cut this if 3126 // ready-override is set (which is treated as "everything is marked ready"). 3127 if (mReadyOverride) return true; 3128 for (int i = mReadyGroups.size() - 1; i >= 0; --i) { 3129 final WindowContainer wc = mReadyGroups.keyAt(i); 3130 if (!wc.isAttached() || !wc.isVisibleRequested()) continue; 3131 if (!mReadyGroups.valueAt(i)) return false; 3132 } 3133 return true; 3134 } 3135 groupsToString()3136 private String groupsToString() { 3137 StringBuilder b = new StringBuilder(); 3138 for (int i = 0; i < mReadyGroups.size(); ++i) { 3139 if (i != 0) b.append(','); 3140 b.append(mReadyGroups.keyAt(i)).append(':') 3141 .append(mReadyGroups.valueAt(i)); 3142 } 3143 return b.toString(); 3144 } 3145 } 3146 3147 /** 3148 * The container to represent the depth relation for calculating transition targets. The window 3149 * container with larger depth is put at larger index. For the same depth, higher z-order has 3150 * larger index. 3151 */ 3152 private static class Targets { 3153 /** All targets. Its keys (depth) are sorted in ascending order naturally. */ 3154 final SparseArray<ChangeInfo> mArray = new SparseArray<>(); 3155 /** The targets which were represented by their parent. */ 3156 private ArrayList<ChangeInfo> mRemovedTargets; 3157 private int mDepthFactor; 3158 add(ChangeInfo target)3159 void add(ChangeInfo target) { 3160 // The number of slots per depth is larger than the total number of window container, 3161 // so the depth score (key) won't have collision. 3162 if (mDepthFactor == 0) { 3163 mDepthFactor = target.mContainer.mWmService.mRoot.getTreeWeight() + 1; 3164 } 3165 int score = target.mContainer.getPrefixOrderIndex(); 3166 WindowContainer<?> wc = target.mContainer; 3167 while (wc != null) { 3168 final WindowContainer<?> parent = wc.getParent(); 3169 if (parent != null) { 3170 score += mDepthFactor; 3171 } 3172 wc = parent; 3173 } 3174 mArray.put(score, target); 3175 } 3176 remove(int index)3177 void remove(int index) { 3178 final ChangeInfo removingTarget = mArray.valueAt(index); 3179 mArray.removeAt(index); 3180 if (mRemovedTargets == null) { 3181 mRemovedTargets = new ArrayList<>(); 3182 } 3183 mRemovedTargets.add(removingTarget); 3184 } 3185 wasParticipated(ChangeInfo wc)3186 boolean wasParticipated(ChangeInfo wc) { 3187 return mArray.indexOfValue(wc) >= 0 3188 || (mRemovedTargets != null && mRemovedTargets.contains(wc)); 3189 } 3190 3191 /** Returns the target list sorted by z-order in ascending order (index 0 is top). */ getListSortedByZ()3192 ArrayList<ChangeInfo> getListSortedByZ() { 3193 final SparseArray<ChangeInfo> arrayByZ = new SparseArray<>(mArray.size()); 3194 for (int i = mArray.size() - 1; i >= 0; --i) { 3195 final int zOrder = mArray.keyAt(i) % mDepthFactor; 3196 arrayByZ.put(zOrder, mArray.valueAt(i)); 3197 } 3198 final ArrayList<ChangeInfo> sortedTargets = new ArrayList<>(arrayByZ.size()); 3199 for (int i = arrayByZ.size() - 1; i >= 0; --i) { 3200 sortedTargets.add(arrayByZ.valueAt(i)); 3201 } 3202 return sortedTargets; 3203 } 3204 } 3205 3206 /** 3207 * Interface for freezing a container's content during sync preparation. Really just one impl 3208 * but broken into an interface for testing (since you can't take screenshots in unit tests). 3209 */ 3210 interface IContainerFreezer { 3211 /** 3212 * Makes sure a particular window is "frozen" for the remainder of a sync. 3213 * 3214 * @return whether the freeze was successful. It fails if `wc` is already in a frozen window 3215 * or is not visible/ready. 3216 */ freeze(@onNull WindowContainer wc, @NonNull Rect bounds)3217 boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds); 3218 3219 /** Populates `t` with operations that clean-up any state created to set-up the freeze. */ cleanUp(SurfaceControl.Transaction t)3220 void cleanUp(SurfaceControl.Transaction t); 3221 } 3222 3223 /** 3224 * Freezes container content by taking a screenshot. Because screenshots are heavy, usage of 3225 * any container "freeze" is currently explicit. WM code needs to be prudent about which 3226 * containers to freeze. 3227 */ 3228 @VisibleForTesting 3229 private class ScreenshotFreezer implements IContainerFreezer { 3230 /** Keeps track of which windows are frozen. Not all frozen windows have snapshots. */ 3231 private final ArraySet<WindowContainer> mFrozen = new ArraySet<>(); 3232 3233 /** Takes a screenshot and puts it at the top of the container's surface. */ 3234 @Override freeze(@onNull WindowContainer wc, @NonNull Rect bounds)3235 public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) { 3236 if (!wc.isVisibleRequested()) return false; 3237 3238 // Check if any parents have already been "frozen". If so, `wc` is already part of that 3239 // snapshot, so just skip it. 3240 for (WindowContainer p = wc; p != null; p = p.getParent()) { 3241 if (mFrozen.contains(p)) return false; 3242 } 3243 3244 if (mIsSeamlessRotation) { 3245 WindowState top = wc.getDisplayContent() == null ? null 3246 : wc.getDisplayContent().getDisplayPolicy().getTopFullscreenOpaqueWindow(); 3247 if (top != null && (top == wc || top.isDescendantOf(wc))) { 3248 // Don't use screenshots for seamless windows: these will use BLAST even if not 3249 // BLAST mode. 3250 mFrozen.add(wc); 3251 return true; 3252 } 3253 } 3254 3255 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Screenshotting %s [%s]", 3256 wc.toString(), bounds.toString()); 3257 3258 Rect cropBounds = new Rect(bounds); 3259 cropBounds.offsetTo(0, 0); 3260 final boolean isDisplayRotation = wc.asDisplayContent() != null 3261 && wc.asDisplayContent().isRotationChanging(); 3262 ScreenCapture.LayerCaptureArgs captureArgs = 3263 new ScreenCapture.LayerCaptureArgs.Builder(wc.getSurfaceControl()) 3264 .setSourceCrop(cropBounds) 3265 .setCaptureSecureLayers(true) 3266 .setAllowProtected(true) 3267 .setHintForSeamlessTransition(isDisplayRotation) 3268 .build(); 3269 ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = 3270 ScreenCapture.captureLayers(captureArgs); 3271 final HardwareBuffer buffer = screenshotBuffer == null ? null 3272 : screenshotBuffer.getHardwareBuffer(); 3273 if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { 3274 // This can happen when display is not ready. 3275 Slog.w(TAG, "Failed to capture screenshot for " + wc); 3276 return false; 3277 } 3278 // Some tests may check the name "RotationLayer" to detect display rotation. 3279 final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc; 3280 SurfaceControl snapshotSurface = wc.makeAnimationLeash() 3281 .setName(name) 3282 .setOpaque(wc.fillsParent()) 3283 .setParent(wc.getSurfaceControl()) 3284 .setSecure(screenshotBuffer.containsSecureLayers()) 3285 .setCallsite("Transition.ScreenshotSync") 3286 .setBLASTLayer() 3287 .build(); 3288 mFrozen.add(wc); 3289 final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc)); 3290 changeInfo.mSnapshot = snapshotSurface; 3291 if (isDisplayRotation) { 3292 // This isn't cheap, so only do it for display rotations. 3293 changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma( 3294 buffer, screenshotBuffer.getColorSpace()); 3295 } 3296 SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get(); 3297 TransitionAnimation.configureScreenshotLayer(t, snapshotSurface, screenshotBuffer); 3298 t.show(snapshotSurface); 3299 3300 // Place it on top of anything else in the container. 3301 t.setLayer(snapshotSurface, Integer.MAX_VALUE); 3302 t.apply(); 3303 t.close(); 3304 buffer.close(); 3305 3306 // Detach the screenshot on the sync transaction (the screenshot is just meant to 3307 // freeze the window until the sync transaction is applied (with all its other 3308 // corresponding changes), so this is how we unfreeze it. 3309 wc.getSyncTransaction().reparent(snapshotSurface, null /* newParent */); 3310 return true; 3311 } 3312 3313 @Override cleanUp(SurfaceControl.Transaction t)3314 public void cleanUp(SurfaceControl.Transaction t) { 3315 for (int i = 0; i < mFrozen.size(); ++i) { 3316 SurfaceControl snap = 3317 Objects.requireNonNull(mChanges.get(mFrozen.valueAt(i))).mSnapshot; 3318 // May be null if it was frozen via BLAST override. 3319 if (snap == null) continue; 3320 t.reparent(snap, null /* newParent */); 3321 } 3322 } 3323 } 3324 3325 private static class Token extends Binder { 3326 final WeakReference<Transition> mTransition; 3327 Token(Transition transition)3328 Token(Transition transition) { 3329 mTransition = new WeakReference<>(transition); 3330 } 3331 3332 @Override toString()3333 public String toString() { 3334 return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " " 3335 + mTransition.get() + "}"; 3336 } 3337 } 3338 } 3339