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.os.Trace.TRACE_TAG_WINDOW_MANAGER; 25 import static android.view.Display.DEFAULT_DISPLAY; 26 import static android.view.Display.INVALID_DISPLAY; 27 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; 28 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; 29 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; 30 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; 31 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; 32 import static android.view.WindowManager.TRANSIT_CHANGE; 33 import static android.view.WindowManager.TRANSIT_CLOSE; 34 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; 35 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; 36 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; 37 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; 38 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; 39 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; 40 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; 41 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; 42 import static android.view.WindowManager.TRANSIT_OPEN; 43 import static android.view.WindowManager.TRANSIT_TO_BACK; 44 import static android.view.WindowManager.TRANSIT_TO_FRONT; 45 import static android.view.WindowManager.TransitionFlags; 46 import static android.view.WindowManager.TransitionType; 47 import static android.view.WindowManager.transitTypeToString; 48 import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR; 49 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; 50 import static android.window.TransitionInfo.FLAG_FILLS_TASK; 51 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; 52 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; 53 import static android.window.TransitionInfo.FLAG_IS_DISPLAY; 54 import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD; 55 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; 56 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; 57 import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD; 58 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; 59 import static android.window.TransitionInfo.FLAG_TRANSLUCENT; 60 import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN; 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.content.pm.ActivityInfo; 72 import android.graphics.Point; 73 import android.graphics.Rect; 74 import android.hardware.HardwareBuffer; 75 import android.os.Binder; 76 import android.os.IBinder; 77 import android.os.IRemoteCallback; 78 import android.os.RemoteException; 79 import android.os.SystemClock; 80 import android.os.Trace; 81 import android.util.ArrayMap; 82 import android.util.ArraySet; 83 import android.util.Slog; 84 import android.util.SparseArray; 85 import android.view.Display; 86 import android.view.SurfaceControl; 87 import android.view.WindowManager; 88 import android.view.animation.Animation; 89 import android.window.RemoteTransition; 90 import android.window.TransitionInfo; 91 92 import com.android.internal.annotations.VisibleForTesting; 93 import com.android.internal.graphics.ColorUtils; 94 import com.android.internal.protolog.ProtoLogGroup; 95 import com.android.internal.protolog.common.ProtoLog; 96 import com.android.internal.util.function.pooled.PooledLambda; 97 import com.android.server.inputmethod.InputMethodManagerInternal; 98 import com.android.server.wm.utils.RotationAnimationUtils; 99 100 import java.lang.annotation.Retention; 101 import java.lang.annotation.RetentionPolicy; 102 import java.lang.ref.WeakReference; 103 import java.util.ArrayList; 104 import java.util.List; 105 import java.util.Objects; 106 import java.util.function.Predicate; 107 108 /** 109 * Represents a logical transition. 110 * @see TransitionController 111 */ 112 class Transition implements BLASTSyncEngine.TransactionReadyListener { 113 private static final String TAG = "Transition"; 114 private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition"; 115 116 /** The default package for resources */ 117 private static final String DEFAULT_PACKAGE = "android"; 118 119 /** The transition has been created but isn't collecting yet. */ 120 private static final int STATE_PENDING = -1; 121 122 /** The transition has been created and is collecting, but hasn't formally started. */ 123 private static final int STATE_COLLECTING = 0; 124 125 /** 126 * The transition has formally started. It is still collecting but will stop once all 127 * participants are ready to animate (finished drawing). 128 */ 129 private static final int STATE_STARTED = 1; 130 131 /** 132 * This transition is currently playing its animation and can no longer collect or be changed. 133 */ 134 private static final int STATE_PLAYING = 2; 135 136 /** 137 * This transition is aborting or has aborted. No animation will play nor will anything get 138 * sent to the player. 139 */ 140 private static final int STATE_ABORT = 3; 141 142 /** 143 * This transition has finished playing successfully. 144 */ 145 private static final int STATE_FINISHED = 4; 146 147 @IntDef(prefix = { "STATE_" }, value = { 148 STATE_PENDING, 149 STATE_COLLECTING, 150 STATE_STARTED, 151 STATE_PLAYING, 152 STATE_ABORT, 153 STATE_FINISHED 154 }) 155 @Retention(RetentionPolicy.SOURCE) 156 @interface TransitionState {} 157 158 final @TransitionType int mType; 159 private int mSyncId = -1; 160 private @TransitionFlags int mFlags; 161 private final TransitionController mController; 162 private final BLASTSyncEngine mSyncEngine; 163 private final Token mToken; 164 private RemoteTransition mRemoteTransition = null; 165 166 /** Only use for clean-up after binder death! */ 167 private SurfaceControl.Transaction mStartTransaction = null; 168 private SurfaceControl.Transaction mFinishTransaction = null; 169 170 /** 171 * Contains change infos for both participants and all remote-animatable ancestors. The 172 * ancestors can be the promotion candidates so their start-states need to be captured. 173 * @see #getAnimatableParent 174 */ 175 final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>(); 176 177 /** The collected participants in the transition. */ 178 final ArraySet<WindowContainer> mParticipants = new ArraySet<>(); 179 180 /** The final animation targets derived from participants after promotion. */ 181 private ArrayList<WindowContainer> mTargets; 182 183 /** The displays that this transition is running on. */ 184 private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>(); 185 186 /** 187 * Set of participating windowtokens (activity/wallpaper) which are visible at the end of 188 * the transition animation. 189 */ 190 private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>(); 191 192 /** 193 * Set of transient activities (lifecycle initially tied to this transition) and their 194 * restore-below tasks. 195 */ 196 private ArrayMap<ActivityRecord, Task> mTransientLaunches = null; 197 198 /** Custom activity-level animation options and callbacks. */ 199 private TransitionInfo.AnimationOptions mOverrideOptions; 200 private IRemoteCallback mClientAnimationStartCallback = null; 201 private IRemoteCallback mClientAnimationFinishCallback = null; 202 203 private @TransitionState int mState = STATE_PENDING; 204 private final ReadyTracker mReadyTracker = new ReadyTracker(); 205 206 // TODO(b/188595497): remove when not needed. 207 /** @see RecentsAnimationController#mNavigationBarAttachedToApp */ 208 private boolean mNavBarAttachedToApp = false; 209 private int mRecentsDisplayId = INVALID_DISPLAY; 210 211 /** The delay for light bar appearance animation. */ 212 long mStatusBarTransitionDelay; 213 214 /** @see #setCanPipOnFinish */ 215 private boolean mCanPipOnFinish = true; 216 217 private boolean mIsSeamlessRotation = false; 218 private IContainerFreezer mContainerFreezer = null; 219 Transition(@ransitionType int type, @TransitionFlags int flags, TransitionController controller, BLASTSyncEngine syncEngine)220 Transition(@TransitionType int type, @TransitionFlags int flags, 221 TransitionController controller, BLASTSyncEngine syncEngine) { 222 mType = type; 223 mFlags = flags; 224 mController = controller; 225 mSyncEngine = syncEngine; 226 mToken = new Token(this); 227 228 controller.mTransitionTracer.logState(this); 229 } 230 231 @Nullable fromBinder(@ullable IBinder token)232 static Transition fromBinder(@Nullable IBinder token) { 233 if (token == null) return null; 234 try { 235 return ((Token) token).mTransition.get(); 236 } catch (ClassCastException e) { 237 Slog.w(TAG, "Invalid transition token: " + token, e); 238 return null; 239 } 240 } 241 242 @NonNull getToken()243 IBinder getToken() { 244 return mToken; 245 } 246 addFlag(int flag)247 void addFlag(int flag) { 248 mFlags |= flag; 249 } 250 251 /** Records an activity as transient-launch. This activity must be already collected. */ setTransientLaunch(@onNull ActivityRecord activity, @Nullable Task restoreBelow)252 void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) { 253 if (mTransientLaunches == null) { 254 mTransientLaunches = new ArrayMap<>(); 255 } 256 mTransientLaunches.put(activity, restoreBelow); 257 setTransientLaunchToChanges(activity); 258 259 if (restoreBelow != null) { 260 final ChangeInfo info = mChanges.get(restoreBelow); 261 if (info != null) { 262 info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH; 263 } 264 } 265 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as " 266 + "transient-launch", mSyncId, activity); 267 } 268 isTransientHide(@onNull Task task)269 boolean isTransientHide(@NonNull Task task) { 270 if (mTransientLaunches == null) return false; 271 for (int i = 0; i < mTransientLaunches.size(); ++i) { 272 if (mTransientLaunches.valueAt(i) == task) { 273 return true; 274 } 275 } 276 return false; 277 } 278 isTransientLaunch(@onNull ActivityRecord activity)279 boolean isTransientLaunch(@NonNull ActivityRecord activity) { 280 return mTransientLaunches != null && mTransientLaunches.containsKey(activity); 281 } 282 getTransientLaunchRestoreTarget(@onNull WindowContainer container)283 Task getTransientLaunchRestoreTarget(@NonNull WindowContainer container) { 284 if (mTransientLaunches == null) return null; 285 for (int i = 0; i < mTransientLaunches.size(); ++i) { 286 if (mTransientLaunches.keyAt(i).isDescendantOf(container)) { 287 return mTransientLaunches.valueAt(i); 288 } 289 } 290 return null; 291 } 292 isOnDisplay(@onNull DisplayContent dc)293 boolean isOnDisplay(@NonNull DisplayContent dc) { 294 return mTargetDisplays.contains(dc); 295 } 296 297 /** Set a transition to be a seamless-rotation. */ setSeamlessRotation(@onNull WindowContainer wc)298 void setSeamlessRotation(@NonNull WindowContainer wc) { 299 final ChangeInfo info = mChanges.get(wc); 300 if (info == null) return; 301 info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION; 302 onSeamlessRotating(wc.getDisplayContent()); 303 } 304 305 /** 306 * Called when it's been determined that this is transition is a seamless rotation. This should 307 * be called before any WM changes have happened. 308 */ onSeamlessRotating(@onNull DisplayContent dc)309 void onSeamlessRotating(@NonNull DisplayContent dc) { 310 // Don't need to do anything special if everything is using BLAST sync already. 311 if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) return; 312 if (mContainerFreezer == null) { 313 mContainerFreezer = new ScreenshotFreezer(); 314 } 315 final WindowState top = dc.getDisplayPolicy().getTopFullscreenOpaqueWindow(); 316 if (top != null) { 317 mIsSeamlessRotation = true; 318 top.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST; 319 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Override sync-method for %s " 320 + "because seamless rotating", top.getName()); 321 } 322 } 323 324 /** 325 * Only set flag to the parent tasks and activity itself. 326 */ setTransientLaunchToChanges(@onNull WindowContainer wc)327 private void setTransientLaunchToChanges(@NonNull WindowContainer wc) { 328 for (WindowContainer curr = wc; curr != null && mChanges.containsKey(curr); 329 curr = curr.getParent()) { 330 if (curr.asTask() == null && curr.asActivityRecord() == null) { 331 return; 332 } 333 final ChangeInfo info = mChanges.get(curr); 334 info.mFlags = info.mFlags | ChangeInfo.FLAG_TRANSIENT_LAUNCH; 335 } 336 } 337 338 /** Only for testing. */ setContainerFreezer(IContainerFreezer freezer)339 void setContainerFreezer(IContainerFreezer freezer) { 340 mContainerFreezer = freezer; 341 } 342 343 @TransitionState getState()344 int getState() { 345 return mState; 346 } 347 348 @VisibleForTesting getSyncId()349 int getSyncId() { 350 return mSyncId; 351 } 352 353 @TransitionFlags getFlags()354 int getFlags() { 355 return mFlags; 356 } 357 358 @VisibleForTesting getStartTransaction()359 SurfaceControl.Transaction getStartTransaction() { 360 return mStartTransaction; 361 } 362 363 @VisibleForTesting getFinishTransaction()364 SurfaceControl.Transaction getFinishTransaction() { 365 return mFinishTransaction; 366 } 367 isCollecting()368 boolean isCollecting() { 369 return mState == STATE_COLLECTING || mState == STATE_STARTED; 370 } 371 372 @VisibleForTesting startCollecting(long timeoutMs)373 void startCollecting(long timeoutMs) { 374 startCollecting(timeoutMs, TransitionController.SYNC_METHOD); 375 } 376 377 /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */ startCollecting(long timeoutMs, int method)378 void startCollecting(long timeoutMs, int method) { 379 if (mState != STATE_PENDING) { 380 throw new IllegalStateException("Attempting to re-use a transition"); 381 } 382 mState = STATE_COLLECTING; 383 mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG, method); 384 385 mController.mTransitionTracer.logState(this); 386 } 387 388 /** 389 * Formally starts the transition. Participants can be collected before this is started, 390 * but this won't consider itself ready until started -- even if all the participants have 391 * drawn. 392 */ start()393 void start() { 394 if (mState < STATE_COLLECTING) { 395 throw new IllegalStateException("Can't start Transition which isn't collecting."); 396 } else if (mState >= STATE_STARTED) { 397 Slog.w(TAG, "Transition already started: " + mSyncId); 398 } 399 mState = STATE_STARTED; 400 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d", 401 mSyncId); 402 applyReady(); 403 404 mController.mTransitionTracer.logState(this); 405 } 406 407 /** 408 * Adds wc to set of WindowContainers participating in this transition. 409 */ collect(@onNull WindowContainer wc)410 void collect(@NonNull WindowContainer wc) { 411 if (mState < STATE_COLLECTING) { 412 throw new IllegalStateException("Transition hasn't started collecting."); 413 } 414 if (!isCollecting()) { 415 // Too late, transition already started playing, so don't collect. 416 return; 417 } 418 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s", 419 mSyncId, wc); 420 // "snapshot" all parents (as potential promotion targets). Do this before checking 421 // if this is already a participant in case it has since been re-parented. 422 for (WindowContainer<?> curr = getAnimatableParent(wc); 423 curr != null && !mChanges.containsKey(curr); 424 curr = getAnimatableParent(curr)) { 425 mChanges.put(curr, new ChangeInfo(curr)); 426 if (isReadyGroup(curr)) { 427 mReadyTracker.addGroup(curr); 428 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for" 429 + " Transition %d with root=%s", mSyncId, curr); 430 } 431 } 432 if (mParticipants.contains(wc)) return; 433 // Wallpaper is like in a static drawn state unless display may have changes, so exclude 434 // the case to reduce transition latency waiting for the unchanged wallpaper to redraw. 435 final boolean needSyncDraw = !isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent); 436 if (needSyncDraw) { 437 mSyncEngine.addToSyncSet(mSyncId, wc); 438 } 439 ChangeInfo info = mChanges.get(wc); 440 if (info == null) { 441 info = new ChangeInfo(wc); 442 mChanges.put(wc, info); 443 } 444 mParticipants.add(wc); 445 if (wc.getDisplayContent() != null && !mTargetDisplays.contains(wc.getDisplayContent())) { 446 mTargetDisplays.add(wc.getDisplayContent()); 447 } 448 if (info.mShowWallpaper) { 449 // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set. 450 final WindowState wallpaper = 451 wc.getDisplayContent().mWallpaperController.getTopVisibleWallpaper(); 452 if (wallpaper != null) { 453 collect(wallpaper.mToken); 454 } 455 } 456 } 457 458 /** 459 * Records wc as changing its state of existence during this transition. For example, a new 460 * task is considered an existence change while moving a task to front is not. wc is added 461 * to the collection set. Note: Existence is NOT a promotable characteristic. 462 * 463 * This must be explicitly recorded because there are o number of situations where the actual 464 * hierarchy operations don't align with the intent (eg. re-using a task with a new activity 465 * or waiting until after the animation to close). 466 */ collectExistenceChange(@onNull WindowContainer wc)467 void collectExistenceChange(@NonNull WindowContainer wc) { 468 if (mState >= STATE_PLAYING) { 469 // Too late to collect. Don't check too-early here since `collect` will check that. 470 return; 471 } 472 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:" 473 + " %s", mSyncId, wc); 474 collect(wc); 475 mChanges.get(wc).mExistenceChanged = true; 476 } 477 478 /** 479 * Records that a particular container is changing visibly (ie. something about it is changing 480 * while it remains visible). This only effects windows that are already in the collecting 481 * transition. 482 */ collectVisibleChange(WindowContainer wc)483 void collectVisibleChange(WindowContainer wc) { 484 if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) { 485 // All windows are synced already. 486 return; 487 } 488 if (!isInTransition(wc)) return; 489 490 if (mContainerFreezer == null) { 491 mContainerFreezer = new ScreenshotFreezer(); 492 } 493 Transition.ChangeInfo change = mChanges.get(wc); 494 if (change == null || !change.mVisible || !wc.isVisibleRequested()) return; 495 // Note: many more tests have already been done by caller. 496 mContainerFreezer.freeze(wc, change.mAbsoluteBounds); 497 } 498 499 /** 500 * Records that a particular container has been reparented. This only effects windows that have 501 * already been collected in the transition. This should be called before reparenting because 502 * the old parent may be removed during reparenting, for example: 503 * {@link Task#shouldRemoveSelfOnLastChildRemoval} 504 */ collectReparentChange(@onNull WindowContainer wc, @NonNull WindowContainer newParent)505 void collectReparentChange(@NonNull WindowContainer wc, @NonNull WindowContainer newParent) { 506 if (!mChanges.containsKey(wc)) { 507 // #collectReparentChange() will be called when the window is reparented. Skip if it is 508 // a window that has not been collected, which means we don't care about this window for 509 // the current transition. 510 return; 511 } 512 final ChangeInfo change = mChanges.get(wc); 513 // Use the current common ancestor if there are multiple reparent, and the original parent 514 // has been detached. Otherwise, use the original parent before the transition. 515 final WindowContainer prevParent = 516 change.mStartParent == null || change.mStartParent.isAttached() 517 ? change.mStartParent 518 : change.mCommonAncestor; 519 if (prevParent == null || !prevParent.isAttached()) { 520 Slog.w(TAG, "Trying to collect reparenting of a window after the previous parent has" 521 + " been detached: " + wc); 522 return; 523 } 524 if (prevParent == newParent) { 525 Slog.w(TAG, "Trying to collect reparenting of a window that has not been reparented: " 526 + wc); 527 return; 528 } 529 if (!newParent.isAttached()) { 530 Slog.w(TAG, "Trying to collect reparenting of a window that is not attached after" 531 + " reparenting: " + wc); 532 return; 533 } 534 WindowContainer ancestor = newParent; 535 while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) { 536 ancestor = ancestor.getParent(); 537 } 538 change.mCommonAncestor = ancestor; 539 } 540 541 /** 542 * @return {@code true} if `wc` is a participant or is a descendant of one. 543 */ isInTransition(WindowContainer wc)544 boolean isInTransition(WindowContainer wc) { 545 for (WindowContainer p = wc; p != null; p = p.getParent()) { 546 if (mParticipants.contains(p)) return true; 547 } 548 return false; 549 } 550 551 /** 552 * Specifies configuration change explicitly for the window container, so it can be chosen as 553 * transition target. This is usually used with transition mode 554 * {@link android.view.WindowManager#TRANSIT_CHANGE}. 555 */ setKnownConfigChanges(WindowContainer<?> wc, @ActivityInfo.Config int changes)556 void setKnownConfigChanges(WindowContainer<?> wc, @ActivityInfo.Config int changes) { 557 final ChangeInfo changeInfo = mChanges.get(wc); 558 if (changeInfo != null) { 559 changeInfo.mKnownConfigChanges = changes; 560 } 561 } 562 sendRemoteCallback(@ullable IRemoteCallback callback)563 private void sendRemoteCallback(@Nullable IRemoteCallback callback) { 564 if (callback == null) return; 565 mController.mAtm.mH.sendMessage(PooledLambda.obtainMessage(cb -> { 566 try { 567 cb.sendResult(null); 568 } catch (RemoteException e) { } 569 }, callback)); 570 } 571 572 /** 573 * Set animation options for collecting transition by ActivityRecord. 574 * @param options AnimationOptions captured from ActivityOptions 575 */ setOverrideAnimation(TransitionInfo.AnimationOptions options, @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback)576 void setOverrideAnimation(TransitionInfo.AnimationOptions options, 577 @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) { 578 if (!isCollecting()) return; 579 mOverrideOptions = options; 580 sendRemoteCallback(mClientAnimationStartCallback); 581 mClientAnimationStartCallback = startCallback; 582 mClientAnimationFinishCallback = finishCallback; 583 } 584 585 /** 586 * Call this when all known changes related to this transition have been applied. Until 587 * all participants have finished drawing, the transition can still collect participants. 588 * 589 * If this is called before the transition is started, it will be deferred until start. 590 * 591 * @param wc A reference point to determine which ready-group to update. For now, each display 592 * has its own ready-group, so this is used to look-up which display to mark ready. 593 * The transition will wait for all groups to be ready. 594 */ setReady(WindowContainer wc, boolean ready)595 void setReady(WindowContainer wc, boolean ready) { 596 if (!isCollecting() || mSyncId < 0) return; 597 mReadyTracker.setReadyFrom(wc, ready); 598 applyReady(); 599 } 600 applyReady()601 private void applyReady() { 602 if (mState < STATE_STARTED) return; 603 final boolean ready = mReadyTracker.allReady(); 604 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 605 "Set transition ready=%b %d", ready, mSyncId); 606 mSyncEngine.setReady(mSyncId, ready); 607 } 608 609 /** 610 * Sets all possible ready groups to ready. 611 * @see ReadyTracker#setAllReady. 612 */ setAllReady()613 void setAllReady() { 614 if (!isCollecting() || mSyncId < 0) return; 615 mReadyTracker.setAllReady(); 616 applyReady(); 617 } 618 619 @VisibleForTesting allReady()620 boolean allReady() { 621 return mReadyTracker.allReady(); 622 } 623 624 /** 625 * Build a transaction that "resets" all the re-parenting and layer changes. This is 626 * intended to be applied at the end of the transition but before the finish callback. This 627 * needs to be passed/applied in shell because until finish is called, shell owns the surfaces. 628 * Additionally, this gives shell the ability to better deal with merged transitions. 629 */ buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash)630 private void buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash) { 631 final Point tmpPos = new Point(); 632 // usually only size 1 633 final ArraySet<DisplayContent> displays = new ArraySet<>(); 634 for (int i = mTargets.size() - 1; i >= 0; --i) { 635 final WindowContainer target = mTargets.get(i); 636 if (target.getParent() != null) { 637 final SurfaceControl targetLeash = getLeashSurface(target, null /* t */); 638 final SurfaceControl origParent = getOrigParentSurface(target); 639 // Ensure surfaceControls are re-parented back into the hierarchy. 640 t.reparent(targetLeash, origParent); 641 t.setLayer(targetLeash, target.getLastLayer()); 642 target.getRelativePosition(tmpPos); 643 t.setPosition(targetLeash, tmpPos.x, tmpPos.y); 644 // No need to clip the display in case seeing the clipped content when during the 645 // display rotation. No need to clip activities because they rely on clipping on 646 // task layers. 647 if (target.asTaskFragment() == null) { 648 t.setCrop(targetLeash, null /* crop */); 649 } else { 650 // Crop to the resolved override bounds. 651 final Rect clipRect = target.getResolvedOverrideBounds(); 652 t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height()); 653 } 654 t.setCornerRadius(targetLeash, 0); 655 t.setShadowRadius(targetLeash, 0); 656 t.setMatrix(targetLeash, 1, 0, 0, 1); 657 t.setAlpha(targetLeash, 1); 658 // The bounds sent to the transition is always a real bounds. This means we lose 659 // information about "null" bounds (inheriting from parent). Core will fix-up 660 // non-organized window surface bounds; however, since Core can't touch organized 661 // surfaces, add the "inherit from parent" restoration here. 662 if (target.isOrganized() && target.matchParentBounds()) { 663 t.setWindowCrop(targetLeash, -1, -1); 664 } 665 displays.add(target.getDisplayContent()); 666 } 667 } 668 // Remove screenshot layers if necessary 669 if (mContainerFreezer != null) { 670 mContainerFreezer.cleanUp(t); 671 } 672 // Need to update layers on involved displays since they were all paused while 673 // the animation played. This puts the layers back into the correct order. 674 mController.mBuildingFinishLayers = true; 675 try { 676 for (int i = displays.size() - 1; i >= 0; --i) { 677 if (displays.valueAt(i) == null) continue; 678 displays.valueAt(i).assignChildLayers(t); 679 } 680 } finally { 681 mController.mBuildingFinishLayers = false; 682 } 683 if (rootLeash.isValid()) { 684 t.reparent(rootLeash, null); 685 } 686 } 687 688 /** 689 * Set whether this transition can start a pip-enter transition when finished. This is usually 690 * true, but gets set to false when recents decides that it wants to finish its animation but 691 * not actually finish its animation (yeah...). 692 */ setCanPipOnFinish(boolean canPipOnFinish)693 void setCanPipOnFinish(boolean canPipOnFinish) { 694 mCanPipOnFinish = canPipOnFinish; 695 } 696 didCommitTransientLaunch()697 private boolean didCommitTransientLaunch() { 698 if (mTransientLaunches == null) return false; 699 for (int j = 0; j < mTransientLaunches.size(); ++j) { 700 if (mTransientLaunches.keyAt(j).isVisibleRequested()) { 701 return true; 702 } 703 } 704 return false; 705 } 706 707 /** 708 * Check if pip-entry is possible after finishing and enter-pip if it is. 709 * 710 * @return true if we are *guaranteed* to enter-pip. This means we return false if there's 711 * a chance we won't thus legacy-entry (via pause+userLeaving) will return false. 712 */ checkEnterPipOnFinish(@onNull ActivityRecord ar)713 private boolean checkEnterPipOnFinish(@NonNull ActivityRecord ar) { 714 if (!mCanPipOnFinish || !ar.isVisible() || ar.getTask() == null) return false; 715 716 if (ar.pictureInPictureArgs != null && ar.pictureInPictureArgs.isAutoEnterEnabled()) { 717 if (didCommitTransientLaunch()) { 718 // force enable pip-on-task-switch now that we've committed to actually launching 719 // to the transient activity. 720 ar.supportsEnterPipOnTaskSwitch = true; 721 } 722 return mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs, 723 false /* fromClient */); 724 } 725 726 // Legacy pip-entry (not via isAutoEnterEnabled). 727 boolean canPip = ar.getDeferHidingClient(); 728 if (!canPip && didCommitTransientLaunch()) { 729 // force enable pip-on-task-switch now that we've committed to actually launching to the 730 // transient activity, and then recalculate whether we can attempt pip. 731 ar.supportsEnterPipOnTaskSwitch = true; 732 canPip = ar.checkEnterPictureInPictureState( 733 "finishTransition", true /* beforeStopping */) 734 && ar.isState(RESUMED); 735 } 736 if (!canPip) return false; 737 try { 738 // Legacy PIP-enter requires pause event with user-leaving. 739 mController.mAtm.mTaskSupervisor.mUserLeaving = true; 740 ar.getTaskFragment().startPausing(false /* uiSleeping */, 741 null /* resuming */, "finishTransition"); 742 } finally { 743 mController.mAtm.mTaskSupervisor.mUserLeaving = false; 744 } 745 // Return false anyway because there's no guarantee that the app will enter pip. 746 return false; 747 } 748 749 /** 750 * The transition has finished animating and is ready to finalize WM state. This should not 751 * be called directly; use {@link TransitionController#finishTransition} instead. 752 */ finishTransition()753 void finishTransition() { 754 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { 755 Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION, 756 System.identityHashCode(this)); 757 } 758 // Close the transactions now. They were originally copied to Shell in case we needed to 759 // apply them due to a remote failure. Since we don't need to apply them anymore, free them 760 // immediately. 761 if (mStartTransaction != null) mStartTransaction.close(); 762 if (mFinishTransaction != null) mFinishTransaction.close(); 763 mStartTransaction = mFinishTransaction = null; 764 if (mState < STATE_PLAYING) { 765 throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId); 766 } 767 768 boolean hasParticipatedDisplay = false; 769 // Commit all going-invisible containers 770 for (int i = 0; i < mParticipants.size(); ++i) { 771 final WindowContainer<?> participant = mParticipants.valueAt(i); 772 final ActivityRecord ar = participant.asActivityRecord(); 773 if (ar != null) { 774 boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar); 775 // We need both the expected visibility AND current requested-visibility to be 776 // false. If it is expected-visible but not currently visible, it means that 777 // another animation is queued-up to animate this to invisibility, so we can't 778 // remove the surfaces yet. If it is currently visible, but not expected-visible, 779 // then doing commitVisibility here would actually be out-of-order and leave the 780 // activity in a bad state. 781 // TODO (b/243755838) Create a screen off transition to correct the visible status 782 // of activities. 783 final boolean isScreenOff = ar.mDisplayContent == null 784 || ar.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF; 785 if ((!visibleAtTransitionEnd || isScreenOff) && !ar.isVisibleRequested()) { 786 final boolean commitVisibility = !checkEnterPipOnFinish(ar); 787 // Avoid commit visibility if entering pip or else we will get a sudden 788 // "flash" / surface going invisible for a split second. 789 if (commitVisibility) { 790 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 791 " Commit activity becoming invisible: %s", ar); 792 final Task task = ar.getTask(); 793 if (task != null && !task.isVisibleRequested() 794 && mTransientLaunches != null) { 795 // If transition is transient, then snapshots are taken at end of 796 // transition. 797 mController.mTaskSnapshotController.recordTaskSnapshot( 798 task, false /* allowSnapshotHome */); 799 } 800 ar.commitVisibility(false /* visible */, false /* performLayout */, 801 true /* fromTransition */); 802 } 803 } 804 if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) { 805 // Legacy dispatch relies on this (for now). 806 ar.mEnteringAnimation = visibleAtTransitionEnd; 807 } else if (mTransientLaunches != null && mTransientLaunches.containsKey(ar) 808 && ar.isVisible()) { 809 // Transient launch was committed, so report enteringAnimation 810 ar.mEnteringAnimation = true; 811 } 812 continue; 813 } 814 if (participant.asDisplayContent() != null) { 815 hasParticipatedDisplay = true; 816 continue; 817 } 818 final WallpaperWindowToken wt = participant.asWallpaperToken(); 819 if (wt != null) { 820 final boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(wt); 821 if (!visibleAtTransitionEnd && !wt.isVisibleRequested()) { 822 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 823 " Commit wallpaper becoming invisible: %s", wt); 824 wt.commitVisibility(false /* visible */); 825 } 826 } 827 } 828 // dispatch legacy callback in a different loop. This is because multiple legacy handlers 829 // (fixed-rotation/displaycontent) make global changes, so we want to ensure that we've 830 // processed all the participants first (in particular, we want to trigger pip-enter first) 831 for (int i = 0; i < mParticipants.size(); ++i) { 832 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 833 if (ar != null) { 834 mController.dispatchLegacyAppTransitionFinished(ar); 835 } 836 } 837 838 // Update the input-sink (touch-blocking) state now that the animation is finished. 839 SurfaceControl.Transaction inputSinkTransaction = null; 840 for (int i = 0; i < mParticipants.size(); ++i) { 841 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 842 if (ar == null || !ar.isVisible() || ar.getParent() == null) continue; 843 if (inputSinkTransaction == null) { 844 inputSinkTransaction = new SurfaceControl.Transaction(); 845 } 846 ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(inputSinkTransaction); 847 } 848 if (inputSinkTransaction != null) inputSinkTransaction.apply(); 849 850 // Always schedule stop processing when transition finishes because activities don't 851 // stop while they are in a transition thus their stop could still be pending. 852 mController.mAtm.mTaskSupervisor 853 .scheduleProcessStoppingAndFinishingActivitiesIfNeeded(); 854 855 sendRemoteCallback(mClientAnimationFinishCallback); 856 857 legacyRestoreNavigationBarFromApp(); 858 859 if (mRecentsDisplayId != INVALID_DISPLAY) { 860 // Clean up input monitors (for recents) 861 final DisplayContent dc = 862 mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId); 863 dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */); 864 } 865 866 for (int i = 0; i < mTargetDisplays.size(); ++i) { 867 final DisplayContent dc = mTargetDisplays.get(i); 868 final AsyncRotationController asyncRotationController = dc.getAsyncRotationController(); 869 if (asyncRotationController != null && mTargets.contains(dc)) { 870 asyncRotationController.onTransitionFinished(); 871 } 872 if (mTransientLaunches != null) { 873 InsetsControlTarget prevImeTarget = dc.getImeTarget( 874 DisplayContent.IME_TARGET_CONTROL); 875 InsetsControlTarget newImeTarget = null; 876 // Transient-launch activities cannot be IME target (WindowState#canBeImeTarget), 877 // so re-compute in case the IME target is changed after transition. 878 for (int t = 0; t < mTransientLaunches.size(); ++t) { 879 if (mTransientLaunches.keyAt(t).getDisplayContent() == dc) { 880 newImeTarget = dc.computeImeTarget(true /* updateImeTarget */); 881 break; 882 } 883 } 884 if (mRecentsDisplayId != INVALID_DISPLAY && prevImeTarget == newImeTarget) { 885 // Restore IME icon only when moving the original app task to front from 886 // recents, in case IME icon may missing if the moving task has already been 887 // the current focused task. 888 InputMethodManagerInternal.get().updateImeWindowStatus( 889 false /* disableImeIcon */); 890 } 891 } 892 dc.removeImeSurfaceImmediately(); 893 dc.handleCompleteDeferredRemoval(); 894 } 895 896 mState = STATE_FINISHED; 897 mController.mTransitionTracer.logState(this); 898 // Rotation change may be deferred while there is a display change transition, so check 899 // again in case there is a new pending change. 900 if (hasParticipatedDisplay && !mController.useShellTransitionsRotation()) { 901 mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */, 902 false /* forceRelayout */); 903 } 904 cleanUpInternal(); 905 } 906 abort()907 void abort() { 908 // This calls back into itself via controller.abort, so just early return here. 909 if (mState == STATE_ABORT) return; 910 if (mState != STATE_COLLECTING && mState != STATE_STARTED) { 911 throw new IllegalStateException("Too late to abort. state=" + mState); 912 } 913 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId); 914 mState = STATE_ABORT; 915 // Syncengine abort will call through to onTransactionReady() 916 mSyncEngine.abort(mSyncId); 917 mController.dispatchLegacyAppTransitionCancelled(); 918 } 919 setRemoteTransition(RemoteTransition remoteTransition)920 void setRemoteTransition(RemoteTransition remoteTransition) { 921 mRemoteTransition = remoteTransition; 922 } 923 getRemoteTransition()924 RemoteTransition getRemoteTransition() { 925 return mRemoteTransition; 926 } 927 928 @Override onTransactionReady(int syncId, SurfaceControl.Transaction transaction)929 public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) { 930 if (syncId != mSyncId) { 931 Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId); 932 return; 933 } 934 if (mTargetDisplays.isEmpty()) { 935 mTargetDisplays.add(mController.mAtm.mRootWindowContainer.getDefaultDisplay()); 936 } 937 // While there can be multiple DC's involved. For now, we just use the first one as 938 // the "primary" one for most things. Eventually, this will need to change, but, for the 939 // time being, we don't have full cross-display transitions so it isn't a problem. 940 final DisplayContent dc = mTargetDisplays.get(0); 941 942 if (mState == STATE_ABORT) { 943 mController.abort(this); 944 dc.getPendingTransaction().merge(transaction); 945 mSyncId = -1; 946 mOverrideOptions = null; 947 cleanUpInternal(); 948 return; 949 } 950 951 mState = STATE_PLAYING; 952 mStartTransaction = transaction; 953 mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get(); 954 mController.moveToPlaying(this); 955 956 if (dc.isKeyguardLocked()) { 957 mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED; 958 } 959 960 // Resolve the animating targets from the participants 961 mTargets = calculateTargets(mParticipants, mChanges); 962 final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges, 963 transaction); 964 if (mOverrideOptions != null) { 965 info.setAnimationOptions(mOverrideOptions); 966 if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) { 967 for (int i = 0; i < mTargets.size(); ++i) { 968 final TransitionInfo.Change c = info.getChanges().get(i); 969 final ActivityRecord ar = mTargets.get(i).asActivityRecord(); 970 if (ar == null || c.getMode() != TRANSIT_OPEN) continue; 971 int flags = c.getFlags(); 972 flags |= ar.mUserId == ar.mWmService.mCurrentUserId 973 ? TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL 974 : TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL; 975 c.setFlags(flags); 976 break; 977 } 978 } 979 } 980 981 // TODO(b/188669821): Move to animation impl in shell. 982 handleLegacyRecentsStartBehavior(dc, info); 983 984 handleNonAppWindowsInTransition(dc, mType, mFlags); 985 986 // The callback is only populated for custom activity-level client animations 987 sendRemoteCallback(mClientAnimationStartCallback); 988 989 // Manually show any activities that are visibleRequested. This is needed to properly 990 // support simultaneous animation queueing/merging. Specifically, if transition A makes 991 // an activity invisible, it's finishTransaction (which is applied *after* the animation) 992 // will hide the activity surface. If transition B then makes the activity visible again, 993 // the normal surfaceplacement logic won't add a show to this start transaction because 994 // the activity visibility hasn't been committed yet. To deal with this, we have to manually 995 // show here in the same way that we manually hide in finishTransaction. 996 for (int i = mParticipants.size() - 1; i >= 0; --i) { 997 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 998 if (ar == null || !ar.isVisibleRequested()) continue; 999 transaction.show(ar.getSurfaceControl()); 1000 1001 // Also manually show any non-reported parents. This is necessary in a few cases 1002 // where a task is NOT organized but had its visibility changed within its direct 1003 // parent. An example of this is if an alternate home leaf-task HB is started atop the 1004 // normal home leaf-task HA: these are both in the Home root-task HR, so there will be a 1005 // transition containing HA and HB where HA surface is hidden. If a standard task SA is 1006 // launched on top, then HB finishes, no transition will happen since neither home is 1007 // visible. When SA finishes, the transition contains HR rather than HA. Since home 1008 // leaf-tasks are NOT organized, HA won't be in the transition and thus its surface 1009 // wouldn't be shown. Just show is safe here since all other properties will have 1010 // already been reset by the original hiding-transition's finishTransaction (we can't 1011 // show in the finishTransaction because by then the activity doesn't hide until 1012 // surface placement). 1013 for (WindowContainer p = ar.getParent(); p != null && !mTargets.contains(p); 1014 p = p.getParent()) { 1015 if (p.getSurfaceControl() != null) { 1016 transaction.show(p.getSurfaceControl()); 1017 } 1018 } 1019 } 1020 1021 // Record windowtokens (activity/wallpaper) that are expected to be visible after the 1022 // transition animation. This will be used in finishTransition to prevent prematurely 1023 // committing visibility. 1024 for (int i = mParticipants.size() - 1; i >= 0; --i) { 1025 final WindowContainer wc = mParticipants.valueAt(i); 1026 if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue; 1027 // don't include transient launches, though, since those are only temporarily visible. 1028 if (mTransientLaunches != null && wc.asActivityRecord() != null 1029 && mTransientLaunches.containsKey(wc.asActivityRecord())) continue; 1030 mVisibleAtTransitionEndTokens.add(wc.asWindowToken()); 1031 } 1032 1033 // Take task snapshots before the animation so that we can capture IME before it gets 1034 // transferred. If transition is transient, IME won't be moved during the transition and 1035 // the tasks are still live, so we take the snapshot at the end of the transition instead. 1036 if (mTransientLaunches == null) { 1037 for (int i = mParticipants.size() - 1; i >= 0; --i) { 1038 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 1039 if (ar == null || ar.isVisibleRequested() || ar.getTask() == null 1040 || ar.getTask().isVisibleRequested()) continue; 1041 mController.mTaskSnapshotController.recordTaskSnapshot( 1042 ar.getTask(), false /* allowSnapshotHome */); 1043 } 1044 } 1045 1046 // This is non-null only if display has changes. It handles the visible windows that don't 1047 // need to be participated in the transition. 1048 final AsyncRotationController controller = dc.getAsyncRotationController(); 1049 if (controller != null && mTargets.contains(dc)) { 1050 controller.setupStartTransaction(transaction); 1051 } 1052 buildFinishTransaction(mFinishTransaction, info.getRootLeash()); 1053 if (mController.getTransitionPlayer() != null) { 1054 mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay); 1055 try { 1056 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1057 "Calling onTransitionReady: %s", info); 1058 mController.getTransitionPlayer().onTransitionReady( 1059 mToken, info, transaction, mFinishTransaction); 1060 // Since we created root-leash but no longer reference it from core, release it now 1061 info.releaseAnimSurfaces(); 1062 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { 1063 Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION, 1064 System.identityHashCode(this)); 1065 } 1066 } catch (RemoteException e) { 1067 // If there's an exception when trying to send the mergedTransaction to the 1068 // client, we should finish and apply it here so the transactions aren't lost. 1069 cleanUpOnFailure(); 1070 } 1071 } else { 1072 // No player registered, so just finish/apply immediately 1073 cleanUpOnFailure(); 1074 } 1075 mOverrideOptions = null; 1076 1077 reportStartReasonsToLogger(); 1078 } 1079 1080 /** 1081 * If the remote failed for any reason, use this to do any appropriate clean-up. Do not call 1082 * this directly, it's designed to by called by {@link TransitionController} only. 1083 */ cleanUpOnFailure()1084 void cleanUpOnFailure() { 1085 // No need to clean-up if this isn't playing yet. 1086 if (mState < STATE_PLAYING) return; 1087 1088 if (mStartTransaction != null) { 1089 mStartTransaction.apply(); 1090 } 1091 if (mFinishTransaction != null) { 1092 mFinishTransaction.apply(); 1093 } 1094 mController.finishTransition(mToken); 1095 } 1096 cleanUpInternal()1097 private void cleanUpInternal() { 1098 // Clean-up any native references. 1099 for (int i = 0; i < mChanges.size(); ++i) { 1100 final ChangeInfo ci = mChanges.valueAt(i); 1101 if (ci.mSnapshot != null) { 1102 ci.mSnapshot.release(); 1103 } 1104 } 1105 } 1106 1107 /** @see RecentsAnimationController#attachNavigationBarToApp */ handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info)1108 private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) { 1109 if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) { 1110 return; 1111 } 1112 mRecentsDisplayId = dc.mDisplayId; 1113 1114 // Recents has an input-consumer to grab input from the "live tile" app. Set that up here 1115 final InputConsumerImpl recentsAnimationInputConsumer = 1116 dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); 1117 if (recentsAnimationInputConsumer != null) { 1118 // find the top-most going-away activity and the recents activity. The top-most 1119 // is used as layer reference while the recents is used for registering the consumer 1120 // override. 1121 ActivityRecord recentsActivity = null; 1122 ActivityRecord topActivity = null; 1123 for (int i = 0; i < info.getChanges().size(); ++i) { 1124 final TransitionInfo.Change change = info.getChanges().get(i); 1125 if (change.getTaskInfo() == null) continue; 1126 final Task task = Task.fromWindowContainerToken( 1127 info.getChanges().get(i).getTaskInfo().token); 1128 if (task == null) continue; 1129 final int activityType = change.getTaskInfo().topActivityType; 1130 final boolean isRecents = activityType == ACTIVITY_TYPE_HOME 1131 || activityType == ACTIVITY_TYPE_RECENTS; 1132 if (isRecents && recentsActivity == null) { 1133 recentsActivity = task.getTopVisibleActivity(); 1134 } else if (!isRecents && topActivity == null) { 1135 topActivity = task.getTopNonFinishingActivity(); 1136 } 1137 } 1138 if (recentsActivity != null && topActivity != null) { 1139 recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set( 1140 topActivity.getBounds()); 1141 dc.getInputMonitor().setActiveRecents(recentsActivity, topActivity); 1142 } 1143 } 1144 1145 // The rest of this function handles nav-bar reparenting 1146 1147 if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition() 1148 // Skip the case where the nav bar is controlled by fade rotation. 1149 || dc.getAsyncRotationController() != null) { 1150 return; 1151 } 1152 1153 WindowContainer topWC = null; 1154 // Find the top-most non-home, closing app. 1155 for (int i = 0; i < info.getChanges().size(); ++i) { 1156 final TransitionInfo.Change c = info.getChanges().get(i); 1157 if (c.getTaskInfo() == null || c.getTaskInfo().displayId != mRecentsDisplayId 1158 || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD 1159 || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) { 1160 continue; 1161 } 1162 topWC = WindowContainer.fromBinder(c.getContainer().asBinder()); 1163 break; 1164 } 1165 if (topWC == null || topWC.inMultiWindowMode()) { 1166 return; 1167 } 1168 1169 final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar(); 1170 if (navWindow == null || navWindow.mToken == null) { 1171 return; 1172 } 1173 mNavBarAttachedToApp = true; 1174 navWindow.mToken.cancelAnimation(); 1175 final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction(); 1176 final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl(); 1177 t.reparent(navSurfaceControl, topWC.getSurfaceControl()); 1178 t.show(navSurfaceControl); 1179 1180 final WindowContainer imeContainer = dc.getImeContainer(); 1181 if (imeContainer.isVisible()) { 1182 t.setRelativeLayer(navSurfaceControl, imeContainer.getSurfaceControl(), 1); 1183 } else { 1184 // Place the nav bar on top of anything else in the top activity. 1185 t.setLayer(navSurfaceControl, Integer.MAX_VALUE); 1186 } 1187 if (mController.mStatusBar != null) { 1188 mController.mStatusBar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, false); 1189 } 1190 } 1191 1192 /** @see RecentsAnimationController#restoreNavigationBarFromApp */ legacyRestoreNavigationBarFromApp()1193 void legacyRestoreNavigationBarFromApp() { 1194 if (!mNavBarAttachedToApp) return; 1195 mNavBarAttachedToApp = false; 1196 1197 if (mRecentsDisplayId == INVALID_DISPLAY) { 1198 Slog.e(TAG, "Reparented navigation bar without a valid display"); 1199 mRecentsDisplayId = DEFAULT_DISPLAY; 1200 } 1201 1202 if (mController.mStatusBar != null) { 1203 mController.mStatusBar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, true); 1204 } 1205 1206 final DisplayContent dc = 1207 mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId); 1208 final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar(); 1209 if (navWindow == null) return; 1210 navWindow.setSurfaceTranslationY(0); 1211 1212 final WindowToken navToken = navWindow.mToken; 1213 if (navToken == null) return; 1214 final SurfaceControl.Transaction t = dc.getPendingTransaction(); 1215 final WindowContainer parent = navToken.getParent(); 1216 t.setLayer(navToken.getSurfaceControl(), navToken.getLastLayer()); 1217 1218 boolean animate = false; 1219 // Search for the home task. If it is supposed to be visible, then the navbar is not at 1220 // the bottom of the screen, so we need to animate it. 1221 for (int i = 0; i < mTargets.size(); ++i) { 1222 final Task task = mTargets.get(i).asTask(); 1223 if (task == null || !task.isActivityTypeHomeOrRecents()) continue; 1224 animate = task.isVisibleRequested(); 1225 break; 1226 } 1227 1228 if (animate) { 1229 final NavBarFadeAnimationController controller = 1230 new NavBarFadeAnimationController(dc); 1231 controller.fadeWindowToken(true); 1232 } else { 1233 // Reparent the SurfaceControl of nav bar token back. 1234 t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl()); 1235 } 1236 } 1237 handleNonAppWindowsInTransition(@onNull DisplayContent dc, @TransitionType int transit, @TransitionFlags int flags)1238 private void handleNonAppWindowsInTransition(@NonNull DisplayContent dc, 1239 @TransitionType int transit, @TransitionFlags int flags) { 1240 if ((transit == TRANSIT_KEYGUARD_GOING_AWAY 1241 || (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) 1242 && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) { 1243 if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0 1244 && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0 1245 && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) { 1246 Animation anim = mController.mAtm.mWindowManager.mPolicy 1247 .createKeyguardWallpaperExit( 1248 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0); 1249 if (anim != null) { 1250 anim.scaleCurrentDuration( 1251 mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked()); 1252 dc.mWallpaperController.startWallpaperAnimation(anim); 1253 } 1254 } 1255 dc.startKeyguardExitOnNonAppWindows( 1256 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0, 1257 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0, 1258 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0); 1259 if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) { 1260 // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI 1261 // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't 1262 // need to call IKeyguardService#keyguardGoingAway here. 1263 mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation( 1264 SystemClock.uptimeMillis(), 0 /* duration */); 1265 } 1266 } 1267 if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { 1268 mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange( 1269 true /* keyguardOccludingStarted */); 1270 } 1271 } 1272 reportStartReasonsToLogger()1273 private void reportStartReasonsToLogger() { 1274 // Record transition start in metrics logger. We just assume everything is "DRAWN" 1275 // at this point since splash-screen is a presentation (shell) detail. 1276 ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>(); 1277 for (int i = mParticipants.size() - 1; i >= 0; --i) { 1278 ActivityRecord r = mParticipants.valueAt(i).asActivityRecord(); 1279 if (r == null || !r.isVisibleRequested()) continue; 1280 int transitionReason = APP_TRANSITION_WINDOWS_DRAWN; 1281 // At this point, r is "ready", but if it's not "ALL ready" then it is probably only 1282 // ready due to starting-window. 1283 if (r.mStartingData instanceof SplashScreenStartingData && !r.mLastAllReadyAtSync) { 1284 transitionReason = APP_TRANSITION_SPLASH_SCREEN; 1285 } else if (r.isActivityTypeHomeOrRecents() && isTransientLaunch(r)) { 1286 transitionReason = APP_TRANSITION_RECENTS_ANIM; 1287 } 1288 reasons.put(r, transitionReason); 1289 } 1290 mController.mAtm.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting( 1291 reasons); 1292 } 1293 1294 @Override toString()1295 public String toString() { 1296 StringBuilder sb = new StringBuilder(64); 1297 sb.append("TransitionRecord{"); 1298 sb.append(Integer.toHexString(System.identityHashCode(this))); 1299 sb.append(" id=" + mSyncId); 1300 sb.append(" type=" + transitTypeToString(mType)); 1301 sb.append(" flags=" + mFlags); 1302 sb.append('}'); 1303 return sb.toString(); 1304 } 1305 1306 /** Returns the parent that the remote animator can animate or control. */ getAnimatableParent(WindowContainer<?> wc)1307 private static WindowContainer<?> getAnimatableParent(WindowContainer<?> wc) { 1308 WindowContainer<?> parent = wc.getParent(); 1309 while (parent != null 1310 && (!parent.canCreateRemoteAnimationTarget() && !parent.isOrganized())) { 1311 parent = parent.getParent(); 1312 } 1313 return parent; 1314 } 1315 reportIfNotTop(WindowContainer wc)1316 private static boolean reportIfNotTop(WindowContainer wc) { 1317 // Organized tasks need to be reported anyways because Core won't show() their surfaces 1318 // and we can't rely on onTaskAppeared because it isn't in sync. 1319 // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN. 1320 return wc.isOrganized(); 1321 } 1322 isWallpaper(WindowContainer wc)1323 private static boolean isWallpaper(WindowContainer wc) { 1324 return wc.asWallpaperToken() != null; 1325 } 1326 isInputMethod(WindowContainer wc)1327 private static boolean isInputMethod(WindowContainer wc) { 1328 return wc.getWindowType() == TYPE_INPUT_METHOD; 1329 } 1330 occludesKeyguard(WindowContainer wc)1331 private static boolean occludesKeyguard(WindowContainer wc) { 1332 final ActivityRecord ar = wc.asActivityRecord(); 1333 if (ar != null) { 1334 return ar.canShowWhenLocked(); 1335 } 1336 final Task t = wc.asTask(); 1337 if (t != null) { 1338 // Get the top activity which was visible (since this is going away, it will remain 1339 // client visible until the transition is finished). 1340 // skip hidden (or about to hide) apps 1341 final ActivityRecord top = t.getActivity(WindowToken::isClientVisible); 1342 return top != null && top.canShowWhenLocked(); 1343 } 1344 return false; 1345 } 1346 isTranslucent(@onNull WindowContainer wc)1347 private static boolean isTranslucent(@NonNull WindowContainer wc) { 1348 final TaskFragment taskFragment = wc.asTaskFragment(); 1349 if (taskFragment != null) { 1350 if (taskFragment.isTranslucent(null /* starting */)) { 1351 return true; 1352 } 1353 final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); 1354 if (adjacentTaskFragment != null) { 1355 // Treat the TaskFragment as translucent if its adjacent TF is, otherwise everything 1356 // behind two adjacent TaskFragments are occluded. 1357 return adjacentTaskFragment.isTranslucent(null /* starting */); 1358 } 1359 } 1360 // TODO(b/172695805): hierarchical check. This is non-trivial because for containers 1361 // it is effected by child visibility but needs to work even 1362 // before visibility is committed. This means refactoring some 1363 // checks to use requested visibility. 1364 return !wc.fillsParent(); 1365 } 1366 1367 /** 1368 * Under some conditions (eg. all visible targets within a parent container are transitioning 1369 * the same way) the transition can be "promoted" to the parent container. This means an 1370 * animation can play just on the parent rather than all the individual children. 1371 * 1372 * @return {@code true} if transition in target can be promoted to its parent. 1373 */ canPromote(WindowContainer<?> target, Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)1374 private static boolean canPromote(WindowContainer<?> target, Targets targets, 1375 ArrayMap<WindowContainer, ChangeInfo> changes) { 1376 final WindowContainer<?> parent = target.getParent(); 1377 final ChangeInfo parentChange = changes.get(parent); 1378 if (!parent.canCreateRemoteAnimationTarget() 1379 || parentChange == null || !parentChange.hasChanged(parent)) { 1380 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s", 1381 "parent can't be target " + parent); 1382 return false; 1383 } 1384 if (isWallpaper(target)) { 1385 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: is wallpaper"); 1386 return false; 1387 } 1388 1389 final ChangeInfo change = changes.get(target); 1390 if (change.mStartParent != null && target.getParent() != change.mStartParent) { 1391 // When a window is reparented, the state change won't fit into any of the parents. 1392 // Don't promote such change so that we can animate the reparent if needed. 1393 return false; 1394 } 1395 1396 final @TransitionInfo.TransitionMode int mode = change.getTransitMode(target); 1397 for (int i = parent.getChildCount() - 1; i >= 0; --i) { 1398 final WindowContainer<?> sibling = parent.getChildAt(i); 1399 if (target == sibling) continue; 1400 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s", 1401 sibling); 1402 final ChangeInfo siblingChange = changes.get(sibling); 1403 if (siblingChange == null || !targets.wasParticipated(sibling)) { 1404 if (sibling.isVisibleRequested()) { 1405 // Sibling is visible but not animating, so no promote. 1406 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1407 " SKIP: sibling is visible but not part of transition"); 1408 return false; 1409 } 1410 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1411 " unrelated invisible sibling %s", sibling); 1412 continue; 1413 } 1414 1415 final int siblingMode = siblingChange.getTransitMode(sibling); 1416 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1417 " sibling is a participant with mode %s", 1418 TransitionInfo.modeToString(siblingMode)); 1419 if (reduceMode(mode) != reduceMode(siblingMode)) { 1420 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1421 " SKIP: common mode mismatch. was %s", 1422 TransitionInfo.modeToString(mode)); 1423 return false; 1424 } 1425 } 1426 return true; 1427 } 1428 1429 /** "reduces" a mode into a smaller set of modes that uniquely represents visibility change. */ 1430 @TransitionInfo.TransitionMode reduceMode(@ransitionInfo.TransitionMode int mode)1431 private static int reduceMode(@TransitionInfo.TransitionMode int mode) { 1432 switch (mode) { 1433 case TRANSIT_TO_BACK: return TRANSIT_CLOSE; 1434 case TRANSIT_TO_FRONT: return TRANSIT_OPEN; 1435 default: return mode; 1436 } 1437 } 1438 1439 /** 1440 * Go through topTargets and try to promote (see {@link #canPromote}) one of them. 1441 * 1442 * @param targets all targets that will be sent to the player. 1443 */ tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)1444 private static void tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes) { 1445 WindowContainer<?> lastNonPromotableParent = null; 1446 // Go through from the deepest target. 1447 for (int i = targets.mArray.size() - 1; i >= 0; --i) { 1448 final WindowContainer<?> target = targets.mArray.valueAt(i); 1449 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", target); 1450 final WindowContainer<?> parent = target.getParent(); 1451 if (parent == lastNonPromotableParent) { 1452 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1453 " SKIP: its sibling was rejected"); 1454 continue; 1455 } 1456 if (!canPromote(target, targets, changes)) { 1457 lastNonPromotableParent = parent; 1458 continue; 1459 } 1460 if (reportIfNotTop(target)) { 1461 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1462 " keep as target %s", target); 1463 } else { 1464 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1465 " remove from targets %s", target); 1466 targets.remove(i, target); 1467 } 1468 if (targets.mArray.indexOfValue(parent) < 0) { 1469 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1470 " CAN PROMOTE: promoting to parent %s", parent); 1471 // The parent has lower depth, so it will be checked in the later iteration. 1472 i++; 1473 targets.add(parent); 1474 } 1475 } 1476 } 1477 1478 /** 1479 * Find WindowContainers to be animated from a set of opening and closing apps. We will promote 1480 * animation targets to higher level in the window hierarchy if possible. 1481 */ 1482 @VisibleForTesting 1483 @NonNull calculateTargets(ArraySet<WindowContainer> participants, ArrayMap<WindowContainer, ChangeInfo> changes)1484 static ArrayList<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants, 1485 ArrayMap<WindowContainer, ChangeInfo> changes) { 1486 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1487 "Start calculating TransitionInfo based on participants: %s", participants); 1488 1489 // Add all valid participants to the target container. 1490 final Targets targets = new Targets(); 1491 for (int i = participants.size() - 1; i >= 0; --i) { 1492 final WindowContainer<?> wc = participants.valueAt(i); 1493 if (!wc.isAttached()) { 1494 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1495 " Rejecting as detached: %s", wc); 1496 continue; 1497 } 1498 // The level of transition target should be at least window token. 1499 if (wc.asWindowState() != null) continue; 1500 1501 final ChangeInfo changeInfo = changes.get(wc); 1502 1503 // Reject no-ops 1504 if (!changeInfo.hasChanged(wc)) { 1505 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1506 " Rejecting as no-op: %s", wc); 1507 continue; 1508 } 1509 targets.add(wc); 1510 } 1511 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Initial targets: %s", 1512 targets.mArray); 1513 // Combine the targets from bottom to top if possible. 1514 tryPromote(targets, changes); 1515 // Establish the relationship between the targets and their top changes. 1516 populateParentChanges(targets, changes); 1517 1518 final ArrayList<WindowContainer> targetList = targets.getListSortedByZ(); 1519 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Final targets: %s", targetList); 1520 return targetList; 1521 } 1522 1523 /** Populates parent to the change info and collects intermediate targets. */ populateParentChanges(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)1524 private static void populateParentChanges(Targets targets, 1525 ArrayMap<WindowContainer, ChangeInfo> changes) { 1526 final ArrayList<WindowContainer<?>> intermediates = new ArrayList<>(); 1527 // Make a copy to iterate because the original array may be modified. 1528 final ArrayList<WindowContainer<?>> targetList = new ArrayList<>(targets.mArray.size()); 1529 for (int i = targets.mArray.size() - 1; i >= 0; --i) { 1530 targetList.add(targets.mArray.valueAt(i)); 1531 } 1532 for (int i = targetList.size() - 1; i >= 0; --i) { 1533 final WindowContainer<?> wc = targetList.get(i); 1534 // Wallpaper must belong to the top (regardless of how nested it is in DisplayAreas). 1535 final boolean skipIntermediateReports = isWallpaper(wc); 1536 intermediates.clear(); 1537 boolean foundParentInTargets = false; 1538 // Collect the intermediate parents between target and top changed parent. 1539 for (WindowContainer<?> p = getAnimatableParent(wc); p != null; 1540 p = getAnimatableParent(p)) { 1541 final ChangeInfo parentChange = changes.get(p); 1542 if (parentChange == null || !parentChange.hasChanged(p)) break; 1543 if (p.mRemoteToken == null) { 1544 // Intermediate parents must be those that has window to be managed by Shell. 1545 continue; 1546 } 1547 if (parentChange.mEndParent != null && !skipIntermediateReports) { 1548 changes.get(wc).mEndParent = p; 1549 // The chain above the parent was processed. 1550 break; 1551 } 1552 if (targetList.contains(p)) { 1553 if (skipIntermediateReports) { 1554 changes.get(wc).mEndParent = p; 1555 } else { 1556 intermediates.add(p); 1557 } 1558 foundParentInTargets = true; 1559 break; 1560 } else if (reportIfNotTop(p) && !skipIntermediateReports) { 1561 intermediates.add(p); 1562 } 1563 } 1564 if (!foundParentInTargets || intermediates.isEmpty()) continue; 1565 // Add any always-report parents along the way. 1566 changes.get(wc).mEndParent = intermediates.get(0); 1567 for (int j = 0; j < intermediates.size() - 1; j++) { 1568 final WindowContainer<?> intermediate = intermediates.get(j); 1569 changes.get(intermediate).mEndParent = intermediates.get(j + 1); 1570 targets.add(intermediate); 1571 } 1572 } 1573 } 1574 1575 /** 1576 * Gets the leash surface for a window container. 1577 * @param t a transaction to create leashes on when necessary (fixed rotation at token-level). 1578 * If t is null, then this will not create any leashes, just use one if it is there -- 1579 * this is relevant for building the finishTransaction since it needs to match the 1580 * start state and not erroneously create a leash of its own. 1581 */ getLeashSurface(WindowContainer wc, @Nullable SurfaceControl.Transaction t)1582 private static SurfaceControl getLeashSurface(WindowContainer wc, 1583 @Nullable SurfaceControl.Transaction t) { 1584 final DisplayContent asDC = wc.asDisplayContent(); 1585 if (asDC != null) { 1586 // DisplayContent is the "root", so we use the windowing layer instead to avoid 1587 // hardware-screen-level surfaces. 1588 return asDC.getWindowingLayer(); 1589 } 1590 if (!wc.mTransitionController.useShellTransitionsRotation()) { 1591 final WindowToken asToken = wc.asWindowToken(); 1592 if (asToken != null) { 1593 // WindowTokens can have a fixed-rotation applied to them. In the current 1594 // implementation this fact is hidden from the player, so we must create a leash. 1595 final SurfaceControl leash = t != null ? asToken.getOrCreateFixedRotationLeash(t) 1596 : asToken.getFixedRotationLeash(); 1597 if (leash != null) return leash; 1598 } 1599 } 1600 return wc.getSurfaceControl(); 1601 } 1602 getOrigParentSurface(WindowContainer wc)1603 private static SurfaceControl getOrigParentSurface(WindowContainer wc) { 1604 if (wc.asDisplayContent() != null) { 1605 // DisplayContent is the "root", so we reinterpret it's wc as the window layer 1606 // making the parent surface the displaycontent's surface. 1607 return wc.getSurfaceControl(); 1608 } 1609 return wc.getParent().getSurfaceControl(); 1610 } 1611 1612 /** 1613 * A ready group is defined by a root window-container where all transitioning windows under 1614 * it are expected to animate together as a group. At the moment, this treats each display as 1615 * a ready-group to match the existing legacy transition behavior. 1616 */ isReadyGroup(WindowContainer wc)1617 private static boolean isReadyGroup(WindowContainer wc) { 1618 return wc instanceof DisplayContent; 1619 } 1620 1621 /** 1622 * Construct a TransitionInfo object from a set of targets and changes. Also populates the 1623 * root surface. 1624 * @param sortedTargets The targets sorted by z-order from top (index 0) to bottom. 1625 * @param startT The start transaction - used to set-up new leashes. 1626 */ 1627 @VisibleForTesting 1628 @NonNull calculateTransitionInfo(@ransitionType int type, int flags, ArrayList<WindowContainer> sortedTargets, ArrayMap<WindowContainer, ChangeInfo> changes, @Nullable SurfaceControl.Transaction startT)1629 static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags, 1630 ArrayList<WindowContainer> sortedTargets, 1631 ArrayMap<WindowContainer, ChangeInfo> changes, 1632 @Nullable SurfaceControl.Transaction startT) { 1633 final TransitionInfo out = new TransitionInfo(type, flags); 1634 1635 WindowContainer<?> topApp = null; 1636 for (int i = 0; i < sortedTargets.size(); i++) { 1637 final WindowContainer<?> wc = sortedTargets.get(i); 1638 if (!isWallpaper(wc)) { 1639 topApp = wc; 1640 break; 1641 } 1642 } 1643 if (topApp == null) { 1644 out.setRootLeash(new SurfaceControl(), 0, 0); 1645 return out; 1646 } 1647 1648 WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, changes, topApp); 1649 1650 // Make leash based on highest (z-order) direct child of ancestor with a participant. 1651 // TODO(b/261418859): Handle the case when the target contains window containers which 1652 // belong to a different display. As a workaround we use topApp, from which wallpaper 1653 // window container is removed, instead of sortedTargets here. 1654 WindowContainer leashReference = topApp; 1655 while (leashReference.getParent() != ancestor) { 1656 leashReference = leashReference.getParent(); 1657 } 1658 final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName( 1659 "Transition Root: " + leashReference.getName()).build(); 1660 startT.setLayer(rootLeash, leashReference.getLastLayer()); 1661 out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top); 1662 1663 // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order. 1664 final int count = sortedTargets.size(); 1665 for (int i = 0; i < count; ++i) { 1666 final WindowContainer target = sortedTargets.get(i); 1667 final ChangeInfo info = changes.get(target); 1668 final TransitionInfo.Change change = new TransitionInfo.Change( 1669 target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken() 1670 : null, getLeashSurface(target, startT)); 1671 // TODO(shell-transitions): Use leash for non-organized windows. 1672 if (info.mEndParent != null) { 1673 change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken()); 1674 } 1675 if (info.mStartParent != null && info.mStartParent.mRemoteToken != null 1676 && target.getParent() != info.mStartParent) { 1677 change.setLastParent(info.mStartParent.mRemoteToken.toWindowContainerToken()); 1678 } 1679 change.setMode(info.getTransitMode(target)); 1680 change.setStartAbsBounds(info.mAbsoluteBounds); 1681 change.setFlags(info.getChangeFlags(target)); 1682 1683 final Task task = target.asTask(); 1684 final TaskFragment taskFragment = target.asTaskFragment(); 1685 final ActivityRecord activityRecord = target.asActivityRecord(); 1686 1687 if (task != null) { 1688 final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo(); 1689 task.fillTaskInfo(tinfo); 1690 change.setTaskInfo(tinfo); 1691 change.setRotationAnimation(getTaskRotationAnimation(task)); 1692 final ActivityRecord topMostActivity = task.getTopMostActivity(); 1693 change.setAllowEnterPip(topMostActivity != null 1694 && topMostActivity.checkEnterPictureInPictureAppOpsState()); 1695 final ActivityRecord topRunningActivity = task.topRunningActivity(); 1696 if (topRunningActivity != null && task.mDisplayContent != null 1697 // Display won't be rotated for multi window Task, so the fixed rotation 1698 // won't be applied. This can happen when the windowing mode is changed 1699 // before the previous fixed rotation is applied. 1700 && !task.inMultiWindowMode()) { 1701 // If Activity is in fixed rotation, its will be applied with the next rotation, 1702 // when the Task is still in the previous rotation. 1703 final int taskRotation = task.getWindowConfiguration().getDisplayRotation(); 1704 final int activityRotation = topRunningActivity.getWindowConfiguration() 1705 .getDisplayRotation(); 1706 if (taskRotation != activityRotation) { 1707 change.setEndFixedRotation(activityRotation); 1708 } 1709 } 1710 } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) { 1711 change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS); 1712 } 1713 1714 final WindowContainer<?> parent = target.getParent(); 1715 final Rect bounds = target.getBounds(); 1716 final Rect parentBounds = parent.getBounds(); 1717 change.setEndRelOffset(bounds.left - parentBounds.left, 1718 bounds.top - parentBounds.top); 1719 int endRotation = target.getWindowConfiguration().getRotation(); 1720 if (activityRecord != null) { 1721 // TODO(b/227427984): Shell needs to aware letterbox. 1722 // Always use parent bounds of activity because letterbox area (e.g. fixed aspect 1723 // ratio or size compat mode) should be included in the animation. 1724 change.setEndAbsBounds(parentBounds); 1725 if (activityRecord.getRelativeDisplayRotation() != 0 1726 && !activityRecord.mTransitionController.useShellTransitionsRotation()) { 1727 // Use parent rotation because shell doesn't know the surface is rotated. 1728 endRotation = parent.getWindowConfiguration().getRotation(); 1729 } 1730 } else { 1731 change.setEndAbsBounds(bounds); 1732 } 1733 1734 if (activityRecord != null || (taskFragment != null && taskFragment.isEmbedded())) { 1735 final int backgroundColor; 1736 final TaskFragment organizedTf = activityRecord != null 1737 ? activityRecord.getOrganizedTaskFragment() 1738 : taskFragment.getOrganizedTaskFragment(); 1739 if (organizedTf != null && organizedTf.getAnimationParams() 1740 .getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) { 1741 // This window is embedded and has an animation background color set on the 1742 // TaskFragment. Pass this color with this window, so the handler can use it as 1743 // the animation background color if needed, 1744 backgroundColor = organizedTf.getAnimationParams() 1745 .getAnimationBackgroundColor(); 1746 } else { 1747 // Set background color to Task theme color for activity and embedded 1748 // TaskFragment in case we want to show background during the animation. 1749 final Task parentTask = activityRecord != null 1750 ? activityRecord.getTask() 1751 : taskFragment.getTask(); 1752 backgroundColor = parentTask.getTaskDescription().getBackgroundColor(); 1753 } 1754 // Set to opaque for animation background to prevent it from exposing the blank 1755 // background or content below. 1756 change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255)); 1757 } 1758 1759 change.setRotation(info.mRotation, endRotation); 1760 if (info.mSnapshot != null) { 1761 change.setSnapshot(info.mSnapshot, info.mSnapshotLuma); 1762 } 1763 1764 out.addChange(change); 1765 } 1766 1767 final WindowManager.LayoutParams animLp = 1768 getLayoutParamsForAnimationsStyle(type, sortedTargets); 1769 if (animLp != null && animLp.type != TYPE_APPLICATION_STARTING 1770 && animLp.windowAnimations != 0) { 1771 // Don't send animation options if no windowAnimations have been set or if the we are 1772 // running an app starting animation, in which case we don't want the app to be able to 1773 // change its animation directly. 1774 TransitionInfo.AnimationOptions animOptions = 1775 TransitionInfo.AnimationOptions.makeAnimOptionsFromLayoutParameters(animLp); 1776 out.setAnimationOptions(animOptions); 1777 } 1778 1779 return out; 1780 } 1781 1782 /** 1783 * Finds the top-most common ancestor of app targets. 1784 * 1785 * Makes sure that the previous parent is also a descendant to make sure the animation won't 1786 * be covered by other windows below the previous parent. For example, when reparenting an 1787 * activity from PiP Task to split screen Task. 1788 */ 1789 @NonNull findCommonAncestor( @onNull ArrayList<WindowContainer> targets, @NonNull ArrayMap<WindowContainer, ChangeInfo> changes, @NonNull WindowContainer<?> topApp)1790 private static WindowContainer<?> findCommonAncestor( 1791 @NonNull ArrayList<WindowContainer> targets, 1792 @NonNull ArrayMap<WindowContainer, ChangeInfo> changes, 1793 @NonNull WindowContainer<?> topApp) { 1794 WindowContainer<?> ancestor = topApp.getParent(); 1795 // Go up ancestor parent chain until all targets are descendants. Ancestor should never be 1796 // null because all targets are attached. 1797 for (int i = targets.size() - 1; i >= 0; i--) { 1798 final WindowContainer wc = targets.get(i); 1799 if (isWallpaper(wc)) { 1800 // Skip the non-app window. 1801 continue; 1802 } 1803 while (!wc.isDescendantOf(ancestor)) { 1804 ancestor = ancestor.getParent(); 1805 } 1806 1807 // Make sure the previous parent is also a descendant to make sure the animation won't 1808 // be covered by other windows below the previous parent. For example, when reparenting 1809 // an activity from PiP Task to split screen Task. 1810 final ChangeInfo change = changes.get(wc); 1811 final WindowContainer prevParent = change.mCommonAncestor; 1812 if (prevParent == null || !prevParent.isAttached()) { 1813 continue; 1814 } 1815 while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) { 1816 ancestor = ancestor.getParent(); 1817 } 1818 } 1819 return ancestor; 1820 } 1821 getLayoutParamsForAnimationsStyle(int type, ArrayList<WindowContainer> sortedTargets)1822 private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type, 1823 ArrayList<WindowContainer> sortedTargets) { 1824 // Find the layout params of the top-most application window that is part of the 1825 // transition, which is what will control the animation theme. 1826 final ArraySet<Integer> activityTypes = new ArraySet<>(); 1827 for (WindowContainer target : sortedTargets) { 1828 if (target.asActivityRecord() != null) { 1829 activityTypes.add(target.getActivityType()); 1830 } else if (target.asWindowToken() == null && target.asWindowState() == null) { 1831 // We don't want app to customize animations that are not activity to activity. 1832 // Activity-level transitions can only include activities, wallpaper and subwindows. 1833 // Anything else is not a WindowToken nor a WindowState and is "higher" in the 1834 // hierarchy which means we are no longer in an activity transition. 1835 return null; 1836 } 1837 } 1838 if (activityTypes.isEmpty()) { 1839 // We don't want app to be able to customize transitions that are not activity to 1840 // activity through the layout parameter animation style. 1841 return null; 1842 } 1843 final ActivityRecord animLpActivity = 1844 findAnimLayoutParamsActivityRecord(sortedTargets, type, activityTypes); 1845 final WindowState mainWindow = animLpActivity != null 1846 ? animLpActivity.findMainWindow() : null; 1847 return mainWindow != null ? mainWindow.mAttrs : null; 1848 } 1849 findAnimLayoutParamsActivityRecord( List<WindowContainer> sortedTargets, @TransitionType int transit, ArraySet<Integer> activityTypes)1850 private static ActivityRecord findAnimLayoutParamsActivityRecord( 1851 List<WindowContainer> sortedTargets, 1852 @TransitionType int transit, ArraySet<Integer> activityTypes) { 1853 // Remote animations always win, but fullscreen windows override non-fullscreen windows. 1854 ActivityRecord result = lookForTopWindowWithFilter(sortedTargets, 1855 w -> w.getRemoteAnimationDefinition() != null 1856 && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes)); 1857 if (result != null) { 1858 return result; 1859 } 1860 result = lookForTopWindowWithFilter(sortedTargets, 1861 w -> w.fillsParent() && w.findMainWindow() != null); 1862 if (result != null) { 1863 return result; 1864 } 1865 return lookForTopWindowWithFilter(sortedTargets, w -> w.findMainWindow() != null); 1866 } 1867 lookForTopWindowWithFilter(List<WindowContainer> sortedTargets, Predicate<ActivityRecord> filter)1868 private static ActivityRecord lookForTopWindowWithFilter(List<WindowContainer> sortedTargets, 1869 Predicate<ActivityRecord> filter) { 1870 for (WindowContainer target : sortedTargets) { 1871 final ActivityRecord activityRecord = target.asTaskFragment() != null 1872 ? target.asTaskFragment().getTopNonFinishingActivity() 1873 : target.asActivityRecord(); 1874 if (activityRecord != null && filter.test(activityRecord)) { 1875 return activityRecord; 1876 } 1877 } 1878 return null; 1879 } 1880 getTaskRotationAnimation(@onNull Task task)1881 private static int getTaskRotationAnimation(@NonNull Task task) { 1882 final ActivityRecord top = task.getTopVisibleActivity(); 1883 if (top == null) return ROTATION_ANIMATION_UNSPECIFIED; 1884 final WindowState mainWin = top.findMainWindow(false); 1885 if (mainWin == null) return ROTATION_ANIMATION_UNSPECIFIED; 1886 int anim = mainWin.getRotationAnimationHint(); 1887 if (anim >= 0) return anim; 1888 anim = mainWin.getAttrs().rotationAnimation; 1889 if (anim != ROTATION_ANIMATION_SEAMLESS) return anim; 1890 if (mainWin != task.mDisplayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow() 1891 || !top.matchParentBounds()) { 1892 // At the moment, we only support seamless rotation if there is only one window showing. 1893 return ROTATION_ANIMATION_UNSPECIFIED; 1894 } 1895 return mainWin.getAttrs().rotationAnimation; 1896 } 1897 1898 /** Applies the new configuration and returns {@code true} if there is a display change. */ applyDisplayChangeIfNeeded()1899 boolean applyDisplayChangeIfNeeded() { 1900 boolean changed = false; 1901 for (int i = mParticipants.size() - 1; i >= 0; --i) { 1902 final WindowContainer<?> wc = mParticipants.valueAt(i); 1903 final DisplayContent dc = wc.asDisplayContent(); 1904 if (dc == null || !mChanges.get(dc).hasChanged(dc)) continue; 1905 dc.sendNewConfiguration(); 1906 changed = true; 1907 } 1908 return changed; 1909 } 1910 getLegacyIsReady()1911 boolean getLegacyIsReady() { 1912 return isCollecting() && mSyncId >= 0; 1913 } 1914 1915 @VisibleForTesting 1916 static class ChangeInfo { 1917 private static final int FLAG_NONE = 0; 1918 1919 /** 1920 * When set, the associated WindowContainer has been explicitly requested to be a 1921 * seamless rotation. This is currently only used by DisplayContent during fixed-rotation. 1922 */ 1923 private static final int FLAG_SEAMLESS_ROTATION = 1; 1924 private static final int FLAG_TRANSIENT_LAUNCH = 2; 1925 private static final int FLAG_ABOVE_TRANSIENT_LAUNCH = 4; 1926 1927 @IntDef(prefix = { "FLAG_" }, value = { 1928 FLAG_NONE, 1929 FLAG_SEAMLESS_ROTATION, 1930 FLAG_TRANSIENT_LAUNCH, 1931 FLAG_ABOVE_TRANSIENT_LAUNCH 1932 }) 1933 @Retention(RetentionPolicy.SOURCE) 1934 @interface Flag {} 1935 1936 /** 1937 * "Parent" that is also included in the transition. When populating the parent changes, we 1938 * may skip the intermediate parents, so this may not be the actual parent in the hierarchy. 1939 */ 1940 WindowContainer mEndParent; 1941 /** Actual parent window before change state. */ 1942 WindowContainer mStartParent; 1943 /** 1944 * When the window is reparented during the transition, this is the common ancestor window 1945 * of the {@link #mStartParent} and the current parent. This is needed because the 1946 * {@link #mStartParent} may have been detached when the transition starts. 1947 */ 1948 WindowContainer mCommonAncestor; 1949 1950 // State tracking 1951 boolean mExistenceChanged = false; 1952 // before change state 1953 boolean mVisible; 1954 int mWindowingMode; 1955 final Rect mAbsoluteBounds = new Rect(); 1956 boolean mShowWallpaper; 1957 int mRotation = ROTATION_UNDEFINED; 1958 @ActivityInfo.Config int mKnownConfigChanges; 1959 1960 /** These are just extra info. They aren't used for change-detection. */ 1961 @Flag int mFlags = FLAG_NONE; 1962 1963 /** Snapshot surface and luma, if relevant. */ 1964 SurfaceControl mSnapshot; 1965 float mSnapshotLuma; 1966 ChangeInfo(@onNull WindowContainer origState)1967 ChangeInfo(@NonNull WindowContainer origState) { 1968 mVisible = origState.isVisibleRequested(); 1969 mWindowingMode = origState.getWindowingMode(); 1970 mAbsoluteBounds.set(origState.getBounds()); 1971 mShowWallpaper = origState.showWallpaper(); 1972 mRotation = origState.getWindowConfiguration().getRotation(); 1973 mStartParent = origState.getParent(); 1974 } 1975 1976 @VisibleForTesting ChangeInfo(boolean visible, boolean existChange)1977 ChangeInfo(boolean visible, boolean existChange) { 1978 mVisible = visible; 1979 mExistenceChanged = existChange; 1980 mShowWallpaper = false; 1981 } 1982 hasChanged(@onNull WindowContainer newState)1983 boolean hasChanged(@NonNull WindowContainer newState) { 1984 // the task including transient launch must promote to root task 1985 if ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0 1986 || (mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) { 1987 return true; 1988 } 1989 // If it's invisible and hasn't changed visibility, always return false since even if 1990 // something changed, it wouldn't be a visible change. 1991 final boolean currVisible = newState.isVisibleRequested(); 1992 if (currVisible == mVisible && !mVisible) return false; 1993 return currVisible != mVisible 1994 || mKnownConfigChanges != 0 1995 // if mWindowingMode is 0, this container wasn't attached at collect time, so 1996 // assume no change in windowing-mode. 1997 || (mWindowingMode != 0 && newState.getWindowingMode() != mWindowingMode) 1998 || !newState.getBounds().equals(mAbsoluteBounds) 1999 || mRotation != newState.getWindowConfiguration().getRotation(); 2000 } 2001 2002 @TransitionInfo.TransitionMode getTransitMode(@onNull WindowContainer wc)2003 int getTransitMode(@NonNull WindowContainer wc) { 2004 if ((mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) { 2005 return mExistenceChanged ? TRANSIT_CLOSE : TRANSIT_TO_BACK; 2006 } 2007 final boolean nowVisible = wc.isVisibleRequested(); 2008 if (nowVisible == mVisible) { 2009 return TRANSIT_CHANGE; 2010 } 2011 if (mExistenceChanged) { 2012 return nowVisible ? TRANSIT_OPEN : TRANSIT_CLOSE; 2013 } else { 2014 return nowVisible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK; 2015 } 2016 } 2017 2018 @TransitionInfo.ChangeFlags getChangeFlags(@onNull WindowContainer wc)2019 int getChangeFlags(@NonNull WindowContainer wc) { 2020 int flags = 0; 2021 if (mShowWallpaper || wc.showWallpaper()) { 2022 flags |= FLAG_SHOW_WALLPAPER; 2023 } 2024 if (isTranslucent(wc)) { 2025 flags |= FLAG_TRANSLUCENT; 2026 } 2027 final Task task = wc.asTask(); 2028 if (task != null) { 2029 final ActivityRecord topActivity = task.getTopNonFinishingActivity(); 2030 if (topActivity != null && topActivity.mStartingData != null 2031 && topActivity.mStartingData.hasImeSurface()) { 2032 flags |= FLAG_WILL_IME_SHOWN; 2033 } 2034 if (task.voiceSession != null) { 2035 flags |= FLAG_IS_VOICE_INTERACTION; 2036 } 2037 } 2038 Task parentTask = null; 2039 final ActivityRecord record = wc.asActivityRecord(); 2040 if (record != null) { 2041 parentTask = record.getTask(); 2042 if (record.mVoiceInteraction) { 2043 flags |= FLAG_IS_VOICE_INTERACTION; 2044 } 2045 flags |= record.mTransitionChangeFlags; 2046 } 2047 final TaskFragment taskFragment = wc.asTaskFragment(); 2048 if (taskFragment != null && task == null) { 2049 parentTask = taskFragment.getTask(); 2050 } 2051 if (parentTask != null) { 2052 if (parentTask.forAllLeafTaskFragments(TaskFragment::isEmbedded)) { 2053 // Whether this is in a Task with embedded activity. 2054 flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; 2055 } 2056 if (parentTask.forAllActivities(ActivityRecord::hasStartingWindow)) { 2057 // The starting window should cover all windows inside the leaf Task. 2058 flags |= FLAG_IS_BEHIND_STARTING_WINDOW; 2059 } 2060 if (isWindowFillingTask(wc, parentTask)) { 2061 // Whether the container fills its parent Task bounds. 2062 flags |= FLAG_FILLS_TASK; 2063 } 2064 } else { 2065 final DisplayContent dc = wc.asDisplayContent(); 2066 if (dc != null) { 2067 flags |= FLAG_IS_DISPLAY; 2068 if (dc.hasAlertWindowSurfaces()) { 2069 flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS; 2070 } 2071 } else if (isWallpaper(wc)) { 2072 flags |= FLAG_IS_WALLPAPER; 2073 } else if (isInputMethod(wc)) { 2074 flags |= FLAG_IS_INPUT_METHOD; 2075 } else { 2076 // In this condition, the wc can only be WindowToken or DisplayArea. 2077 final int type = wc.getWindowType(); 2078 if (type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW 2079 && type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) { 2080 flags |= TransitionInfo.FLAG_IS_SYSTEM_WINDOW; 2081 } 2082 } 2083 } 2084 if (occludesKeyguard(wc)) { 2085 flags |= FLAG_OCCLUDES_KEYGUARD; 2086 } 2087 return flags; 2088 } 2089 2090 /** Whether the container fills its parent Task bounds before and after the transition. */ isWindowFillingTask(@onNull WindowContainer wc, @NonNull Task parentTask)2091 private boolean isWindowFillingTask(@NonNull WindowContainer wc, @NonNull Task parentTask) { 2092 final Rect taskBounds = parentTask.getBounds(); 2093 final int taskWidth = taskBounds.width(); 2094 final int taskHeight = taskBounds.height(); 2095 final Rect startBounds = mAbsoluteBounds; 2096 final Rect endBounds = wc.getBounds(); 2097 // Treat it as filling the task if it is not visible. 2098 final boolean isInvisibleOrFillingTaskBeforeTransition = !mVisible 2099 || (taskWidth == startBounds.width() && taskHeight == startBounds.height()); 2100 final boolean isInVisibleOrFillingTaskAfterTransition = !wc.isVisibleRequested() 2101 || (taskWidth == endBounds.width() && taskHeight == endBounds.height()); 2102 return isInvisibleOrFillingTaskBeforeTransition 2103 && isInVisibleOrFillingTaskAfterTransition; 2104 } 2105 } 2106 2107 /** 2108 * This transition will be considered not-ready until a corresponding call to 2109 * {@link #continueTransitionReady} 2110 */ deferTransitionReady()2111 void deferTransitionReady() { 2112 ++mReadyTracker.mDeferReadyDepth; 2113 // Make sure it wait until #continueTransitionReady() is called. 2114 mSyncEngine.setReady(mSyncId, false); 2115 } 2116 2117 /** This undoes one call to {@link #deferTransitionReady}. */ continueTransitionReady()2118 void continueTransitionReady() { 2119 --mReadyTracker.mDeferReadyDepth; 2120 // Apply ready in case it is waiting for the previous defer call. 2121 applyReady(); 2122 } 2123 2124 /** 2125 * The transition sync mechanism has 2 parts: 2126 * 1. Whether all WM operations for a particular transition are "ready" (eg. did the app 2127 * launch or stop or get a new configuration?). 2128 * 2. Whether all the windows involved have finished drawing their final-state content. 2129 * 2130 * A transition animation can play once both parts are complete. This ready-tracker keeps track 2131 * of part (1). Currently, WM code assumes that "readiness" (part 1) is grouped. This means that 2132 * even if the WM operations in one group are ready, the whole transition itself may not be 2133 * ready if there are WM operations still pending in another group. This class helps keep track 2134 * of readiness across the multiple groups. Currently, we assume that each display is a group 2135 * since that is how it has been until now. 2136 */ 2137 private static class ReadyTracker { 2138 private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>(); 2139 2140 /** 2141 * Ensures that this doesn't report as allReady before it has been used. This is needed 2142 * in very niche cases where a transition is a no-op (nothing has been collected) but we 2143 * still want to be marked ready (via. setAllReady). 2144 */ 2145 private boolean mUsed = false; 2146 2147 /** 2148 * If true, this overrides all ready groups and reports ready. Used by shell-initiated 2149 * transitions via {@link #setAllReady()}. 2150 */ 2151 private boolean mReadyOverride = false; 2152 2153 /** 2154 * When non-zero, this transition is forced not-ready (even over setAllReady()). Use this 2155 * (via deferTransitionReady/continueTransitionReady) for situations where we want to do 2156 * bulk operations which could trigger surface-placement but the existing ready-state 2157 * isn't known. 2158 */ 2159 private int mDeferReadyDepth = 0; 2160 2161 /** 2162 * Adds a ready-group. Any setReady calls in this subtree will be tracked together. For 2163 * now these are only DisplayContents. 2164 */ addGroup(WindowContainer wc)2165 void addGroup(WindowContainer wc) { 2166 if (mReadyGroups.containsKey(wc)) { 2167 Slog.e(TAG, "Trying to add a ready-group twice: " + wc); 2168 return; 2169 } 2170 mReadyGroups.put(wc, false); 2171 } 2172 2173 /** 2174 * Sets a group's ready state. 2175 * @param wc Any container within a group's subtree. Used to identify the ready-group. 2176 */ setReadyFrom(WindowContainer wc, boolean ready)2177 void setReadyFrom(WindowContainer wc, boolean ready) { 2178 mUsed = true; 2179 WindowContainer current = wc; 2180 while (current != null) { 2181 if (isReadyGroup(current)) { 2182 mReadyGroups.put(current, ready); 2183 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting Ready-group to" 2184 + " %b. group=%s from %s", ready, current, wc); 2185 break; 2186 } 2187 current = current.getParent(); 2188 } 2189 } 2190 2191 /** Marks this as ready regardless of individual groups. */ setAllReady()2192 void setAllReady() { 2193 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting allReady override"); 2194 mUsed = true; 2195 mReadyOverride = true; 2196 } 2197 2198 /** @return true if all tracked subtrees are ready. */ allReady()2199 boolean allReady() { 2200 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b " 2201 + "override=%b defer=%d states=[%s]", mUsed, mReadyOverride, mDeferReadyDepth, 2202 groupsToString()); 2203 // If the readiness has never been touched, mUsed will be false. We never want to 2204 // consider a transition ready if nothing has been reported on it. 2205 if (!mUsed) return false; 2206 // If we are deferring readiness, we never report ready. This is usually temporary. 2207 if (mDeferReadyDepth > 0) return false; 2208 // Next check all the ready groups to see if they are ready. We can short-cut this if 2209 // ready-override is set (which is treated as "everything is marked ready"). 2210 if (mReadyOverride) return true; 2211 for (int i = mReadyGroups.size() - 1; i >= 0; --i) { 2212 final WindowContainer wc = mReadyGroups.keyAt(i); 2213 if (!wc.isAttached() || !wc.isVisibleRequested()) continue; 2214 if (!mReadyGroups.valueAt(i)) return false; 2215 } 2216 return true; 2217 } 2218 groupsToString()2219 private String groupsToString() { 2220 StringBuilder b = new StringBuilder(); 2221 for (int i = 0; i < mReadyGroups.size(); ++i) { 2222 if (i != 0) b.append(','); 2223 b.append(mReadyGroups.keyAt(i)).append(':') 2224 .append(mReadyGroups.valueAt(i)); 2225 } 2226 return b.toString(); 2227 } 2228 } 2229 2230 /** 2231 * The container to represent the depth relation for calculating transition targets. The window 2232 * container with larger depth is put at larger index. For the same depth, higher z-order has 2233 * larger index. 2234 */ 2235 private static class Targets { 2236 /** All targets. Its keys (depth) are sorted in ascending order naturally. */ 2237 final SparseArray<WindowContainer<?>> mArray = new SparseArray<>(); 2238 /** The targets which were represented by their parent. */ 2239 private ArrayList<WindowContainer<?>> mRemovedTargets; 2240 private int mDepthFactor; 2241 add(WindowContainer<?> target)2242 void add(WindowContainer<?> target) { 2243 // The number of slots per depth is larger than the total number of window container, 2244 // so the depth score (key) won't have collision. 2245 if (mDepthFactor == 0) { 2246 mDepthFactor = target.mWmService.mRoot.getTreeWeight() + 1; 2247 } 2248 int score = target.getPrefixOrderIndex(); 2249 WindowContainer<?> wc = target; 2250 while (wc != null) { 2251 final WindowContainer<?> parent = wc.getParent(); 2252 if (parent != null) { 2253 score += mDepthFactor; 2254 } 2255 wc = parent; 2256 } 2257 mArray.put(score, target); 2258 } 2259 remove(int index, WindowContainer<?> removingTarget)2260 void remove(int index, WindowContainer<?> removingTarget) { 2261 mArray.removeAt(index); 2262 if (mRemovedTargets == null) { 2263 mRemovedTargets = new ArrayList<>(); 2264 } 2265 mRemovedTargets.add(removingTarget); 2266 } 2267 wasParticipated(WindowContainer<?> wc)2268 boolean wasParticipated(WindowContainer<?> wc) { 2269 return mArray.indexOfValue(wc) >= 0 2270 || (mRemovedTargets != null && mRemovedTargets.contains(wc)); 2271 } 2272 2273 /** Returns the target list sorted by z-order in ascending order (index 0 is top). */ getListSortedByZ()2274 ArrayList<WindowContainer> getListSortedByZ() { 2275 final SparseArray<WindowContainer<?>> arrayByZ = new SparseArray<>(mArray.size()); 2276 for (int i = mArray.size() - 1; i >= 0; --i) { 2277 final int zOrder = mArray.keyAt(i) % mDepthFactor; 2278 arrayByZ.put(zOrder, mArray.valueAt(i)); 2279 } 2280 final ArrayList<WindowContainer> sortedTargets = new ArrayList<>(arrayByZ.size()); 2281 for (int i = arrayByZ.size() - 1; i >= 0; --i) { 2282 sortedTargets.add(arrayByZ.valueAt(i)); 2283 } 2284 return sortedTargets; 2285 } 2286 } 2287 2288 /** 2289 * Interface for freezing a container's content during sync preparation. Really just one impl 2290 * but broken into an interface for testing (since you can't take screenshots in unit tests). 2291 */ 2292 interface IContainerFreezer { 2293 /** 2294 * Makes sure a particular window is "frozen" for the remainder of a sync. 2295 * 2296 * @return whether the freeze was successful. It fails if `wc` is already in a frozen window 2297 * or is not visible/ready. 2298 */ freeze(@onNull WindowContainer wc, @NonNull Rect bounds)2299 boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds); 2300 2301 /** Populates `t` with operations that clean-up any state created to set-up the freeze. */ cleanUp(SurfaceControl.Transaction t)2302 void cleanUp(SurfaceControl.Transaction t); 2303 } 2304 2305 /** 2306 * Freezes container content by taking a screenshot. Because screenshots are heavy, usage of 2307 * any container "freeze" is currently explicit. WM code needs to be prudent about which 2308 * containers to freeze. 2309 */ 2310 @VisibleForTesting 2311 private class ScreenshotFreezer implements IContainerFreezer { 2312 /** Keeps track of which windows are frozen. Not all frozen windows have snapshots. */ 2313 private final ArraySet<WindowContainer> mFrozen = new ArraySet<>(); 2314 2315 /** Takes a screenshot and puts it at the top of the container's surface. */ 2316 @Override freeze(@onNull WindowContainer wc, @NonNull Rect bounds)2317 public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) { 2318 if (!wc.isVisibleRequested()) return false; 2319 2320 // Check if any parents have already been "frozen". If so, `wc` is already part of that 2321 // snapshot, so just skip it. 2322 for (WindowContainer p = wc; p != null; p = p.getParent()) { 2323 if (mFrozen.contains(p)) return false; 2324 } 2325 2326 if (mIsSeamlessRotation) { 2327 WindowState top = wc.getDisplayContent() == null ? null 2328 : wc.getDisplayContent().getDisplayPolicy().getTopFullscreenOpaqueWindow(); 2329 if (top != null && (top == wc || top.isDescendantOf(wc))) { 2330 // Don't use screenshots for seamless windows: these will use BLAST even if not 2331 // BLAST mode. 2332 mFrozen.add(wc); 2333 return true; 2334 } 2335 } 2336 2337 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Screenshotting %s [%s]", 2338 wc.toString(), bounds.toString()); 2339 2340 Rect cropBounds = new Rect(bounds); 2341 cropBounds.offsetTo(0, 0); 2342 SurfaceControl.LayerCaptureArgs captureArgs = 2343 new SurfaceControl.LayerCaptureArgs.Builder(wc.getSurfaceControl()) 2344 .setSourceCrop(cropBounds) 2345 .setCaptureSecureLayers(true) 2346 .setAllowProtected(true) 2347 .build(); 2348 SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = 2349 SurfaceControl.captureLayers(captureArgs); 2350 final HardwareBuffer buffer = screenshotBuffer == null ? null 2351 : screenshotBuffer.getHardwareBuffer(); 2352 if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { 2353 // This can happen when display is not ready. 2354 Slog.w(TAG, "Failed to capture screenshot for " + wc); 2355 return false; 2356 } 2357 final boolean isDisplayRotation = wc.asDisplayContent() != null 2358 && wc.asDisplayContent().isRotationChanging(); 2359 // Some tests may check the name "RotationLayer" to detect display rotation. 2360 final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc; 2361 SurfaceControl snapshotSurface = wc.makeAnimationLeash() 2362 .setName(name) 2363 .setOpaque(true) 2364 .setParent(wc.getSurfaceControl()) 2365 .setSecure(screenshotBuffer.containsSecureLayers()) 2366 .setCallsite("Transition.ScreenshotSync") 2367 .setBLASTLayer() 2368 .build(); 2369 mFrozen.add(wc); 2370 final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc)); 2371 changeInfo.mSnapshot = snapshotSurface; 2372 if (isDisplayRotation) { 2373 // This isn't cheap, so only do it for display rotations. 2374 changeInfo.mSnapshotLuma = RotationAnimationUtils.getMedianBorderLuma( 2375 screenshotBuffer.getHardwareBuffer(), screenshotBuffer.getColorSpace()); 2376 } 2377 SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get(); 2378 2379 t.setBuffer(snapshotSurface, buffer); 2380 t.setDataSpace(snapshotSurface, screenshotBuffer.getColorSpace().getDataSpace()); 2381 t.show(snapshotSurface); 2382 2383 // Place it on top of anything else in the container. 2384 t.setLayer(snapshotSurface, Integer.MAX_VALUE); 2385 t.apply(); 2386 t.close(); 2387 2388 // Detach the screenshot on the sync transaction (the screenshot is just meant to 2389 // freeze the window until the sync transaction is applied (with all its other 2390 // corresponding changes), so this is how we unfreeze it. 2391 wc.getSyncTransaction().reparent(snapshotSurface, null /* newParent */); 2392 return true; 2393 } 2394 2395 @Override cleanUp(SurfaceControl.Transaction t)2396 public void cleanUp(SurfaceControl.Transaction t) { 2397 for (int i = 0; i < mFrozen.size(); ++i) { 2398 SurfaceControl snap = 2399 Objects.requireNonNull(mChanges.get(mFrozen.valueAt(i))).mSnapshot; 2400 // May be null if it was frozen via BLAST override. 2401 if (snap == null) continue; 2402 t.reparent(snap, null /* newParent */); 2403 } 2404 } 2405 } 2406 2407 private static class Token extends Binder { 2408 final WeakReference<Transition> mTransition; 2409 Token(Transition transition)2410 Token(Transition transition) { 2411 mTransition = new WeakReference<>(transition); 2412 } 2413 2414 @Override toString()2415 public String toString() { 2416 return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " " 2417 + mTransition.get() + "}"; 2418 } 2419 } 2420 } 2421