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.WindowConfiguration.ACTIVITY_TYPE_HOME; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 22 import static android.app.WindowConfiguration.ROTATION_UNDEFINED; 23 import static android.view.Display.DEFAULT_DISPLAY; 24 import static android.view.Display.INVALID_DISPLAY; 25 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; 26 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; 27 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; 28 import static android.view.WindowManager.TRANSIT_CHANGE; 29 import static android.view.WindowManager.TRANSIT_CLOSE; 30 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; 31 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; 32 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; 33 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; 34 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; 35 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; 36 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; 37 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; 38 import static android.view.WindowManager.TRANSIT_NONE; 39 import static android.view.WindowManager.TRANSIT_OPEN; 40 import static android.view.WindowManager.TRANSIT_TO_BACK; 41 import static android.view.WindowManager.TRANSIT_TO_FRONT; 42 import static android.view.WindowManager.TransitionFlags; 43 import static android.view.WindowManager.TransitionType; 44 import static android.view.WindowManager.transitTypeToString; 45 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; 46 import static android.window.TransitionInfo.FLAG_IS_DISPLAY; 47 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; 48 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; 49 import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD; 50 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; 51 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; 52 import static android.window.TransitionInfo.FLAG_TRANSLUCENT; 53 54 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN; 55 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN; 56 57 import android.annotation.IntDef; 58 import android.annotation.NonNull; 59 import android.annotation.Nullable; 60 import android.app.ActivityManager; 61 import android.content.pm.ActivityInfo; 62 import android.graphics.Point; 63 import android.graphics.Rect; 64 import android.os.Binder; 65 import android.os.IBinder; 66 import android.os.IRemoteCallback; 67 import android.os.RemoteException; 68 import android.os.SystemClock; 69 import android.util.ArrayMap; 70 import android.util.ArraySet; 71 import android.util.Slog; 72 import android.view.SurfaceControl; 73 import android.view.animation.Animation; 74 import android.window.RemoteTransition; 75 import android.window.TransitionInfo; 76 77 import com.android.internal.annotations.VisibleForTesting; 78 import com.android.internal.protolog.ProtoLogGroup; 79 import com.android.internal.protolog.common.ProtoLog; 80 import com.android.internal.util.function.pooled.PooledLambda; 81 82 import java.lang.annotation.Retention; 83 import java.lang.annotation.RetentionPolicy; 84 import java.util.ArrayList; 85 86 /** 87 * Represents a logical transition. 88 * @see TransitionController 89 */ 90 class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener { 91 private static final String TAG = "Transition"; 92 93 /** The transition has been created and is collecting, but hasn't formally started. */ 94 private static final int STATE_COLLECTING = 0; 95 96 /** 97 * The transition has formally started. It is still collecting but will stop once all 98 * participants are ready to animate (finished drawing). 99 */ 100 private static final int STATE_STARTED = 1; 101 102 /** 103 * This transition is currently playing its animation and can no longer collect or be changed. 104 */ 105 private static final int STATE_PLAYING = 2; 106 107 /** 108 * This transition is aborting or has aborted. No animation will play nor will anything get 109 * sent to the player. 110 */ 111 private static final int STATE_ABORT = 3; 112 113 @IntDef(prefix = { "STATE_" }, value = { 114 STATE_COLLECTING, 115 STATE_STARTED, 116 STATE_PLAYING, 117 STATE_ABORT 118 }) 119 @Retention(RetentionPolicy.SOURCE) 120 @interface TransitionState {} 121 122 final @TransitionType int mType; 123 private int mSyncId; 124 private @TransitionFlags int mFlags; 125 private final TransitionController mController; 126 private final BLASTSyncEngine mSyncEngine; 127 private RemoteTransition mRemoteTransition = null; 128 129 /** Only use for clean-up after binder death! */ 130 private SurfaceControl.Transaction mStartTransaction = null; 131 private SurfaceControl.Transaction mFinishTransaction = null; 132 133 /** 134 * Contains change infos for both participants and all ancestors. We have to track ancestors 135 * because they are all promotion candidates and thus we need their start-states 136 * to be captured. 137 */ 138 final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>(); 139 140 /** The collected participants in the transition. */ 141 final ArraySet<WindowContainer> mParticipants = new ArraySet<>(); 142 143 /** The final animation targets derived from participants after promotion. */ 144 private ArraySet<WindowContainer> mTargets = null; 145 146 /** 147 * Set of participating windowtokens (activity/wallpaper) which are visible at the end of 148 * the transition animation. 149 */ 150 private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>(); 151 152 /** Set of transient activities (lifecycle initially tied to this transition). */ 153 private ArraySet<ActivityRecord> mTransientLaunches = null; 154 155 /** Custom activity-level animation options and callbacks. */ 156 private TransitionInfo.AnimationOptions mOverrideOptions; 157 private IRemoteCallback mClientAnimationStartCallback = null; 158 private IRemoteCallback mClientAnimationFinishCallback = null; 159 160 private @TransitionState int mState = STATE_COLLECTING; 161 private final ReadyTracker mReadyTracker = new ReadyTracker(); 162 163 // TODO(b/188595497): remove when not needed. 164 /** @see RecentsAnimationController#mNavigationBarAttachedToApp */ 165 private boolean mNavBarAttachedToApp = false; 166 private int mRecentsDisplayId = INVALID_DISPLAY; 167 Transition(@ransitionType int type, @TransitionFlags int flags, long timeoutMs, TransitionController controller, BLASTSyncEngine syncEngine)168 Transition(@TransitionType int type, @TransitionFlags int flags, long timeoutMs, 169 TransitionController controller, BLASTSyncEngine syncEngine) { 170 mType = type; 171 mFlags = flags; 172 mController = controller; 173 mSyncEngine = syncEngine; 174 mSyncId = mSyncEngine.startSyncSet(this, timeoutMs); 175 } 176 addFlag(int flag)177 void addFlag(int flag) { 178 mFlags |= flag; 179 } 180 181 /** Records an activity as transient-launch. This activity must be already collected. */ setTransientLaunch(@onNull ActivityRecord activity)182 void setTransientLaunch(@NonNull ActivityRecord activity) { 183 if (mTransientLaunches == null) { 184 mTransientLaunches = new ArraySet<>(); 185 } 186 mTransientLaunches.add(activity); 187 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as " 188 + "transient-launch", mSyncId, activity); 189 } 190 isTransientLaunch(@onNull ActivityRecord activity)191 boolean isTransientLaunch(@NonNull ActivityRecord activity) { 192 return mTransientLaunches != null && mTransientLaunches.contains(activity); 193 } 194 195 @VisibleForTesting getSyncId()196 int getSyncId() { 197 return mSyncId; 198 } 199 200 @TransitionFlags getFlags()201 int getFlags() { 202 return mFlags; 203 } 204 205 /** 206 * Formally starts the transition. Participants can be collected before this is started, 207 * but this won't consider itself ready until started -- even if all the participants have 208 * drawn. 209 */ start()210 void start() { 211 if (mState >= STATE_STARTED) { 212 Slog.w(TAG, "Transition already started: " + mSyncId); 213 } 214 mState = STATE_STARTED; 215 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d", 216 mSyncId); 217 applyReady(); 218 } 219 220 /** 221 * Adds wc to set of WindowContainers participating in this transition. 222 */ collect(@onNull WindowContainer wc)223 void collect(@NonNull WindowContainer wc) { 224 if (mSyncId < 0) return; 225 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s", 226 mSyncId, wc); 227 // "snapshot" all parents (as potential promotion targets). Do this before checking 228 // if this is already a participant in case it has since been re-parented. 229 for (WindowContainer curr = wc.getParent(); curr != null && !mChanges.containsKey(curr); 230 curr = curr.getParent()) { 231 mChanges.put(curr, new ChangeInfo(curr)); 232 if (isReadyGroup(curr)) { 233 mReadyTracker.addGroup(curr); 234 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for" 235 + " Transition %d with root=%s", mSyncId, curr); 236 } 237 } 238 if (mParticipants.contains(wc)) return; 239 mSyncEngine.addToSyncSet(mSyncId, wc); 240 ChangeInfo info = mChanges.get(wc); 241 if (info == null) { 242 info = new ChangeInfo(wc); 243 mChanges.put(wc, info); 244 } 245 mParticipants.add(wc); 246 if (info.mShowWallpaper) { 247 // Collect the wallpaper so it is part of the sync set. 248 final WindowContainer wallpaper = 249 wc.getDisplayContent().mWallpaperController.getTopVisibleWallpaper(); 250 if (wallpaper != null) { 251 collect(wallpaper); 252 } 253 } 254 } 255 256 /** 257 * Records wc as changing its state of existence during this transition. For example, a new 258 * task is considered an existence change while moving a task to front is not. wc is added 259 * to the collection set. Note: Existence is NOT a promotable characteristic. 260 * 261 * This must be explicitly recorded because there are o number of situations where the actual 262 * hierarchy operations don't align with the intent (eg. re-using a task with a new activity 263 * or waiting until after the animation to close). 264 */ collectExistenceChange(@onNull WindowContainer wc)265 void collectExistenceChange(@NonNull WindowContainer wc) { 266 if (mSyncId < 0) return; 267 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:" 268 + " %s", mSyncId, wc); 269 collect(wc); 270 mChanges.get(wc).mExistenceChanged = true; 271 } 272 273 /** 274 * Specifies configuration change explicitly for the window container, so it can be chosen as 275 * transition target. This is usually used with transition mode 276 * {@link android.view.WindowManager#TRANSIT_CHANGE}. 277 */ setKnownConfigChanges(WindowContainer<?> wc, @ActivityInfo.Config int changes)278 void setKnownConfigChanges(WindowContainer<?> wc, @ActivityInfo.Config int changes) { 279 final ChangeInfo changeInfo = mChanges.get(wc); 280 if (changeInfo != null) { 281 changeInfo.mKnownConfigChanges = changes; 282 } 283 } 284 sendRemoteCallback(@ullable IRemoteCallback callback)285 private void sendRemoteCallback(@Nullable IRemoteCallback callback) { 286 if (callback == null) return; 287 mController.mAtm.mH.sendMessage(PooledLambda.obtainMessage(cb -> { 288 try { 289 cb.sendResult(null); 290 } catch (RemoteException e) { } 291 }, callback)); 292 } 293 294 /** 295 * Set animation options for collecting transition by ActivityRecord. 296 * @param options AnimationOptions captured from ActivityOptions 297 */ setOverrideAnimation(TransitionInfo.AnimationOptions options, @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback)298 void setOverrideAnimation(TransitionInfo.AnimationOptions options, 299 @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) { 300 if (mSyncId < 0) return; 301 mOverrideOptions = options; 302 sendRemoteCallback(mClientAnimationStartCallback); 303 mClientAnimationStartCallback = startCallback; 304 mClientAnimationFinishCallback = finishCallback; 305 } 306 307 /** 308 * Call this when all known changes related to this transition have been applied. Until 309 * all participants have finished drawing, the transition can still collect participants. 310 * 311 * If this is called before the transition is started, it will be deferred until start. 312 * 313 * @param wc A reference point to determine which ready-group to update. For now, each display 314 * has its own ready-group, so this is used to look-up which display to mark ready. 315 * The transition will wait for all groups to be ready. 316 */ setReady(WindowContainer wc, boolean ready)317 void setReady(WindowContainer wc, boolean ready) { 318 if (mSyncId < 0) return; 319 mReadyTracker.setReadyFrom(wc, ready); 320 applyReady(); 321 } 322 applyReady()323 private void applyReady() { 324 if (mState < STATE_STARTED) return; 325 final boolean ready = mReadyTracker.allReady(); 326 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 327 "Set transition ready=%b %d", ready, mSyncId); 328 mSyncEngine.setReady(mSyncId, ready); 329 } 330 331 /** 332 * Sets all possible ready groups to ready. 333 * @see ReadyTracker#setAllReady. 334 */ setAllReady()335 void setAllReady() { 336 if (mSyncId < 0) return; 337 mReadyTracker.setAllReady(); 338 applyReady(); 339 } 340 341 @VisibleForTesting allReady()342 boolean allReady() { 343 return mReadyTracker.allReady(); 344 } 345 346 /** 347 * Build a transaction that "resets" all the re-parenting and layer changes. This is 348 * intended to be applied at the end of the transition but before the finish callback. This 349 * needs to be passed/applied in shell because until finish is called, shell owns the surfaces. 350 * Additionally, this gives shell the ability to better deal with merged transitions. 351 */ buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash)352 private void buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash) { 353 final Point tmpPos = new Point(); 354 // usually only size 1 355 final ArraySet<DisplayContent> displays = new ArraySet<>(); 356 for (int i = mTargets.size() - 1; i >= 0; --i) { 357 final WindowContainer target = mTargets.valueAt(i); 358 if (target.getParent() != null) { 359 final SurfaceControl targetLeash = getLeashSurface(target); 360 final SurfaceControl origParent = getOrigParentSurface(target); 361 // Ensure surfaceControls are re-parented back into the hierarchy. 362 t.reparent(targetLeash, origParent); 363 t.setLayer(targetLeash, target.getLastLayer()); 364 target.getRelativePosition(tmpPos); 365 t.setPosition(targetLeash, tmpPos.x, tmpPos.y); 366 t.setCornerRadius(targetLeash, 0); 367 t.setShadowRadius(targetLeash, 0); 368 displays.add(target.getDisplayContent()); 369 } 370 } 371 // Need to update layers on involved displays since they were all paused while 372 // the animation played. This puts the layers back into the correct order. 373 for (int i = displays.size() - 1; i >= 0; --i) { 374 if (displays.valueAt(i) == null) continue; 375 displays.valueAt(i).assignChildLayers(t); 376 } 377 if (rootLeash.isValid()) { 378 t.reparent(rootLeash, null); 379 } 380 } 381 382 /** 383 * The transition has finished animating and is ready to finalize WM state. This should not 384 * be called directly; use {@link TransitionController#finishTransition} instead. 385 */ finishTransition()386 void finishTransition() { 387 mStartTransaction = mFinishTransaction = null; 388 if (mState < STATE_PLAYING) { 389 throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId); 390 } 391 392 // Commit all going-invisible containers 393 boolean activitiesWentInvisible = false; 394 for (int i = 0; i < mParticipants.size(); ++i) { 395 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 396 if (ar != null) { 397 boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar); 398 // We need both the expected visibility AND current requested-visibility to be 399 // false. If it is expected-visible but not currently visible, it means that 400 // another animation is queued-up to animate this to invisibility, so we can't 401 // remove the surfaces yet. If it is currently visible, but not expected-visible, 402 // then doing commitVisibility here would actually be out-of-order and leave the 403 // activity in a bad state. 404 if (!visibleAtTransitionEnd && !ar.isVisibleRequested()) { 405 boolean commitVisibility = true; 406 if (ar.getDeferHidingClient() && ar.getTask() != null) { 407 if (ar.pictureInPictureArgs != null 408 && ar.pictureInPictureArgs.isAutoEnterEnabled()) { 409 mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs); 410 // Avoid commit visibility to false here, or else we will get a sudden 411 // "flash" / surface going invisible for a split second. 412 commitVisibility = false; 413 } else { 414 mController.mAtm.mTaskSupervisor.mUserLeaving = true; 415 ar.getTaskFragment().startPausing(false /* uiSleeping */, 416 null /* resuming */, "finishTransition"); 417 mController.mAtm.mTaskSupervisor.mUserLeaving = false; 418 } 419 } 420 if (commitVisibility) { 421 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 422 " Commit activity becoming invisible: %s", ar); 423 final Task task = ar.getTask(); 424 if (task != null && !task.isVisibleRequested() 425 && mTransientLaunches != null) { 426 // If transition is transient, then snapshots are taken at end of 427 // transition. 428 mController.mTaskSnapshotController.recordTaskSnapshot( 429 task, false /* allowSnapshotHome */); 430 } 431 ar.commitVisibility(false /* visible */, false /* performLayout */, 432 true /* fromTransition */); 433 activitiesWentInvisible = true; 434 } 435 } 436 if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) { 437 // Legacy dispatch relies on this (for now). 438 ar.mEnteringAnimation = visibleAtTransitionEnd; 439 } 440 mController.dispatchLegacyAppTransitionFinished(ar); 441 } 442 final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken(); 443 if (wt != null) { 444 final boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(wt); 445 if (!visibleAtTransitionEnd && !wt.isVisibleRequested()) { 446 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 447 " Commit wallpaper becoming invisible: %s", wt); 448 wt.commitVisibility(false /* visible */); 449 } 450 } 451 } 452 if (activitiesWentInvisible) { 453 // Always schedule stop processing when transition finishes because activities don't 454 // stop while they are in a transition thus their stop could still be pending. 455 mController.mAtm.mTaskSupervisor 456 .scheduleProcessStoppingAndFinishingActivitiesIfNeeded(); 457 } 458 459 sendRemoteCallback(mClientAnimationFinishCallback); 460 461 legacyRestoreNavigationBarFromApp(); 462 463 if (mRecentsDisplayId != INVALID_DISPLAY) { 464 // Clean up input monitors (for recents) 465 final DisplayContent dc = 466 mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId); 467 dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */); 468 } 469 } 470 abort()471 void abort() { 472 // This calls back into itself via controller.abort, so just early return here. 473 if (mState == STATE_ABORT) return; 474 if (mState != STATE_COLLECTING) { 475 throw new IllegalStateException("Too late to abort."); 476 } 477 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId); 478 mController.dispatchLegacyAppTransitionCancelled(); 479 mState = STATE_ABORT; 480 // Syncengine abort will call through to onTransactionReady() 481 mSyncEngine.abort(mSyncId); 482 } 483 setRemoteTransition(RemoteTransition remoteTransition)484 void setRemoteTransition(RemoteTransition remoteTransition) { 485 mRemoteTransition = remoteTransition; 486 } 487 getRemoteTransition()488 RemoteTransition getRemoteTransition() { 489 return mRemoteTransition; 490 } 491 492 @Override onTransactionReady(int syncId, SurfaceControl.Transaction transaction)493 public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) { 494 if (syncId != mSyncId) { 495 Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId); 496 return; 497 } 498 int displayId = DEFAULT_DISPLAY; 499 for (WindowContainer container : mParticipants) { 500 if (container.mDisplayContent == null) continue; 501 displayId = container.mDisplayContent.getDisplayId(); 502 } 503 504 if (mState == STATE_ABORT) { 505 mController.abort(this); 506 mController.mAtm.mRootWindowContainer.getDisplayContent(displayId) 507 .getPendingTransaction().merge(transaction); 508 mSyncId = -1; 509 mOverrideOptions = null; 510 return; 511 } 512 513 mState = STATE_PLAYING; 514 mController.moveToPlaying(this); 515 516 if (mController.mAtm.mTaskSupervisor.getKeyguardController().isKeyguardLocked()) { 517 mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED; 518 } 519 520 // Resolve the animating targets from the participants 521 mTargets = calculateTargets(mParticipants, mChanges); 522 final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges); 523 info.setAnimationOptions(mOverrideOptions); 524 525 // TODO(b/188669821): Move to animation impl in shell. 526 handleLegacyRecentsStartBehavior(displayId, info); 527 528 handleNonAppWindowsInTransition(displayId, mType, mFlags); 529 530 reportStartReasonsToLogger(); 531 532 // The callback is only populated for custom activity-level client animations 533 sendRemoteCallback(mClientAnimationStartCallback); 534 535 // Manually show any activities that are visibleRequested. This is needed to properly 536 // support simultaneous animation queueing/merging. Specifically, if transition A makes 537 // an activity invisible, it's finishTransaction (which is applied *after* the animation) 538 // will hide the activity surface. If transition B then makes the activity visible again, 539 // the normal surfaceplacement logic won't add a show to this start transaction because 540 // the activity visibility hasn't been committed yet. To deal with this, we have to manually 541 // show here in the same way that we manually hide in finishTransaction. 542 for (int i = mParticipants.size() - 1; i >= 0; --i) { 543 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 544 if (ar == null || !ar.mVisibleRequested) continue; 545 transaction.show(ar.getSurfaceControl()); 546 547 // Also manually show any non-reported parents. This is necessary in a few cases 548 // where a task is NOT organized but had its visibility changed within its direct 549 // parent. An example of this is if an alternate home leaf-task HB is started atop the 550 // normal home leaf-task HA: these are both in the Home root-task HR, so there will be a 551 // transition containing HA and HB where HA surface is hidden. If a standard task SA is 552 // launched on top, then HB finishes, no transition will happen since neither home is 553 // visible. When SA finishes, the transition contains HR rather than HA. Since home 554 // leaf-tasks are NOT organized, HA won't be in the transition and thus its surface 555 // wouldn't be shown. Just show is safe here since all other properties will have 556 // already been reset by the original hiding-transition's finishTransaction (we can't 557 // show in the finishTransaction because by then the activity doesn't hide until 558 // surface placement). 559 for (WindowContainer p = ar.getParent(); p != null && !mTargets.contains(p); 560 p = p.getParent()) { 561 if (p.getSurfaceControl() != null) { 562 transaction.show(p.getSurfaceControl()); 563 } 564 } 565 } 566 567 // Record windowtokens (activity/wallpaper) that are expected to be visible after the 568 // transition animation. This will be used in finishTransition to prevent prematurely 569 // committing visibility. 570 for (int i = mParticipants.size() - 1; i >= 0; --i) { 571 final WindowContainer wc = mParticipants.valueAt(i); 572 if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue; 573 mVisibleAtTransitionEndTokens.add(wc.asWindowToken()); 574 } 575 576 // Take task snapshots before the animation so that we can capture IME before it gets 577 // transferred. If transition is transient, IME won't be moved during the transition and 578 // the tasks are still live, so we take the snapshot at the end of the transition instead. 579 if (mTransientLaunches == null) { 580 for (int i = mParticipants.size() - 1; i >= 0; --i) { 581 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 582 if (ar == null || ar.isVisibleRequested() || ar.getTask() == null 583 || ar.getTask().isVisibleRequested()) continue; 584 mController.mTaskSnapshotController.recordTaskSnapshot( 585 ar.getTask(), false /* allowSnapshotHome */); 586 } 587 } 588 589 mStartTransaction = transaction; 590 mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get(); 591 buildFinishTransaction(mFinishTransaction, info.getRootLeash()); 592 if (mController.getTransitionPlayer() != null) { 593 mController.dispatchLegacyAppTransitionStarting(info); 594 try { 595 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 596 "Calling onTransitionReady: %s", info); 597 mController.getTransitionPlayer().onTransitionReady( 598 this, info, transaction, mFinishTransaction); 599 } catch (RemoteException e) { 600 // If there's an exception when trying to send the mergedTransaction to the 601 // client, we should finish and apply it here so the transactions aren't lost. 602 cleanUpOnFailure(); 603 } 604 } else { 605 // No player registered, so just finish/apply immediately 606 cleanUpOnFailure(); 607 } 608 mSyncId = -1; 609 mOverrideOptions = null; 610 } 611 612 /** 613 * If the remote failed for any reason, use this to do any appropriate clean-up. Do not call 614 * this directly, it's designed to by called by {@link TransitionController} only. 615 */ cleanUpOnFailure()616 void cleanUpOnFailure() { 617 // No need to clean-up if this isn't playing yet. 618 if (mState < STATE_PLAYING) return; 619 620 if (mStartTransaction != null) { 621 mStartTransaction.apply(); 622 } 623 if (mFinishTransaction != null) { 624 mFinishTransaction.apply(); 625 } 626 mController.finishTransition(this); 627 } 628 629 /** @see RecentsAnimationController#attachNavigationBarToApp */ handleLegacyRecentsStartBehavior(int displayId, TransitionInfo info)630 private void handleLegacyRecentsStartBehavior(int displayId, TransitionInfo info) { 631 if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) { 632 return; 633 } 634 final DisplayContent dc = 635 mController.mAtm.mRootWindowContainer.getDisplayContent(displayId); 636 if (dc == null) return; 637 mRecentsDisplayId = displayId; 638 639 // Recents has an input-consumer to grab input from the "live tile" app. Set that up here 640 final InputConsumerImpl recentsAnimationInputConsumer = 641 dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); 642 if (recentsAnimationInputConsumer != null) { 643 // find the top-most going-away activity and the recents activity. The top-most 644 // is used as layer reference while the recents is used for registering the consumer 645 // override. 646 ActivityRecord recentsActivity = null; 647 ActivityRecord topActivity = null; 648 for (int i = 0; i < info.getChanges().size(); ++i) { 649 final TransitionInfo.Change change = info.getChanges().get(i); 650 if (change.getTaskInfo() == null) continue; 651 final Task task = Task.fromWindowContainerToken( 652 info.getChanges().get(i).getTaskInfo().token); 653 if (task == null) continue; 654 final int activityType = change.getTaskInfo().topActivityType; 655 final boolean isRecents = activityType == ACTIVITY_TYPE_HOME 656 || activityType == ACTIVITY_TYPE_RECENTS; 657 if (isRecents && recentsActivity == null) { 658 recentsActivity = task.getTopVisibleActivity(); 659 } else if (!isRecents && topActivity == null) { 660 topActivity = task.getTopNonFinishingActivity(); 661 } 662 } 663 if (recentsActivity != null && topActivity != null) { 664 recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set( 665 topActivity.getBounds()); 666 dc.getInputMonitor().setActiveRecents(recentsActivity, topActivity); 667 } 668 } 669 670 // The rest of this function handles nav-bar reparenting 671 672 if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition() 673 // Skip the case where the nav bar is controlled by fade rotation. 674 || dc.getFadeRotationAnimationController() != null) { 675 return; 676 } 677 678 WindowContainer topWC = null; 679 // Find the top-most non-home, closing app. 680 for (int i = 0; i < info.getChanges().size(); ++i) { 681 final TransitionInfo.Change c = info.getChanges().get(i); 682 if (c.getTaskInfo() == null || c.getTaskInfo().displayId != displayId 683 || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD 684 || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) { 685 continue; 686 } 687 topWC = WindowContainer.fromBinder(c.getContainer().asBinder()); 688 break; 689 } 690 if (topWC == null || topWC.inMultiWindowMode()) { 691 return; 692 } 693 694 final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar(); 695 if (navWindow == null || navWindow.mToken == null) { 696 return; 697 } 698 mNavBarAttachedToApp = true; 699 navWindow.mToken.cancelAnimation(); 700 final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction(); 701 final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl(); 702 t.reparent(navSurfaceControl, topWC.getSurfaceControl()); 703 t.show(navSurfaceControl); 704 705 final WindowContainer imeContainer = dc.getImeContainer(); 706 if (imeContainer.isVisible()) { 707 t.setRelativeLayer(navSurfaceControl, imeContainer.getSurfaceControl(), 1); 708 } else { 709 // Place the nav bar on top of anything else in the top activity. 710 t.setLayer(navSurfaceControl, Integer.MAX_VALUE); 711 } 712 if (mController.mStatusBar != null) { 713 mController.mStatusBar.setNavigationBarLumaSamplingEnabled(displayId, false); 714 } 715 } 716 717 /** @see RecentsAnimationController#restoreNavigationBarFromApp */ legacyRestoreNavigationBarFromApp()718 void legacyRestoreNavigationBarFromApp() { 719 if (!mNavBarAttachedToApp) return; 720 mNavBarAttachedToApp = false; 721 722 if (mRecentsDisplayId == INVALID_DISPLAY) { 723 Slog.e(TAG, "Reparented navigation bar without a valid display"); 724 mRecentsDisplayId = DEFAULT_DISPLAY; 725 } 726 727 if (mController.mStatusBar != null) { 728 mController.mStatusBar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, true); 729 } 730 731 final DisplayContent dc = 732 mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId); 733 final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar(); 734 if (navWindow == null) return; 735 navWindow.setSurfaceTranslationY(0); 736 737 final WindowToken navToken = navWindow.mToken; 738 if (navToken == null) return; 739 final SurfaceControl.Transaction t = dc.getPendingTransaction(); 740 final WindowContainer parent = navToken.getParent(); 741 t.setLayer(navToken.getSurfaceControl(), navToken.getLastLayer()); 742 743 boolean animate = false; 744 // Search for the home task. If it is supposed to be visible, then the navbar is not at 745 // the bottom of the screen, so we need to animate it. 746 for (int i = 0; i < mTargets.size(); ++i) { 747 final Task task = mTargets.valueAt(i).asTask(); 748 if (task == null || !task.isHomeOrRecentsRootTask()) continue; 749 animate = task.isVisibleRequested(); 750 break; 751 } 752 753 if (animate) { 754 final NavBarFadeAnimationController controller = 755 new NavBarFadeAnimationController(dc); 756 controller.fadeWindowToken(true); 757 } else { 758 // Reparent the SurfaceControl of nav bar token back. 759 t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl()); 760 } 761 } 762 handleNonAppWindowsInTransition(int displayId, @TransitionType int transit, @TransitionFlags int flags)763 private void handleNonAppWindowsInTransition(int displayId, 764 @TransitionType int transit, @TransitionFlags int flags) { 765 final DisplayContent dc = 766 mController.mAtm.mRootWindowContainer.getDisplayContent(displayId); 767 if (dc == null) { 768 return; 769 } 770 if ((transit == TRANSIT_KEYGUARD_GOING_AWAY 771 || (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) 772 && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) { 773 if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0 774 && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0 775 && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) { 776 Animation anim = mController.mAtm.mWindowManager.mPolicy 777 .createKeyguardWallpaperExit( 778 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0); 779 if (anim != null) { 780 anim.scaleCurrentDuration( 781 mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked()); 782 dc.mWallpaperController.startWallpaperAnimation(anim); 783 } 784 } 785 dc.startKeyguardExitOnNonAppWindows( 786 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0, 787 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0, 788 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0); 789 if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) { 790 // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI 791 // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't 792 // need to call IKeyguardService#keyguardGoingAway here. 793 mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation( 794 SystemClock.uptimeMillis(), 0 /* duration */); 795 } 796 } 797 if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { 798 mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange( 799 true /* keyguardOccludingStarted */); 800 } 801 } 802 reportStartReasonsToLogger()803 private void reportStartReasonsToLogger() { 804 // Record transition start in metrics logger. We just assume everything is "DRAWN" 805 // at this point since splash-screen is a presentation (shell) detail. 806 ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>(); 807 for (int i = mParticipants.size() - 1; i >= 0; --i) { 808 ActivityRecord r = mParticipants.valueAt(i).asActivityRecord(); 809 if (r == null || !r.mVisibleRequested) continue; 810 // At this point, r is "ready", but if it's not "ALL ready" then it is probably only 811 // ready due to starting-window. 812 reasons.put(r, (r.mStartingData instanceof SplashScreenStartingData 813 && !r.mLastAllReadyAtSync) 814 ? APP_TRANSITION_SPLASH_SCREEN : APP_TRANSITION_WINDOWS_DRAWN); 815 } 816 mController.mAtm.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting( 817 reasons); 818 } 819 820 @Override toString()821 public String toString() { 822 StringBuilder sb = new StringBuilder(64); 823 sb.append("TransitionRecord{"); 824 sb.append(Integer.toHexString(System.identityHashCode(this))); 825 sb.append(" id=" + mSyncId); 826 sb.append(" type=" + transitTypeToString(mType)); 827 sb.append(" flags=" + mFlags); 828 sb.append('}'); 829 return sb.toString(); 830 } 831 reportIfNotTop(WindowContainer wc)832 private static boolean reportIfNotTop(WindowContainer wc) { 833 // Organized tasks need to be reported anyways because Core won't show() their surfaces 834 // and we can't rely on onTaskAppeared because it isn't in sync. 835 // Also report wallpaper so it can be handled properly during display change/rotation. 836 // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN. 837 return wc.isOrganized() || isWallpaper(wc); 838 } 839 840 /** @return the depth of child within ancestor, 0 if child == ancestor, or -1 if not a child. */ getChildDepth(WindowContainer child, WindowContainer ancestor)841 private static int getChildDepth(WindowContainer child, WindowContainer ancestor) { 842 WindowContainer parent = child; 843 int depth = 0; 844 while (parent != null) { 845 if (parent == ancestor) { 846 return depth; 847 } 848 parent = parent.getParent(); 849 ++depth; 850 } 851 return -1; 852 } 853 isWallpaper(WindowContainer wc)854 private static boolean isWallpaper(WindowContainer wc) { 855 return wc.asWallpaperToken() != null; 856 } 857 occludesKeyguard(WindowContainer wc)858 private static boolean occludesKeyguard(WindowContainer wc) { 859 final ActivityRecord ar = wc.asActivityRecord(); 860 if (ar != null) { 861 return ar.canShowWhenLocked(); 862 } 863 final Task t = wc.asTask(); 864 if (t != null) { 865 // Get the top activity which was visible (since this is going away, it will remain 866 // client visible until the transition is finished). 867 // skip hidden (or about to hide) apps 868 final ActivityRecord top = t.getActivity(WindowToken::isClientVisible); 869 return top != null && top.canShowWhenLocked(); 870 } 871 return false; 872 } 873 874 /** 875 * Under some conditions (eg. all visible targets within a parent container are transitioning 876 * the same way) the transition can be "promoted" to the parent container. This means an 877 * animation can play just on the parent rather than all the individual children. 878 * 879 * @return {@code true} if transition in target can be promoted to its parent. 880 */ canPromote(WindowContainer target, ArraySet<WindowContainer> topTargets, ArrayMap<WindowContainer, ChangeInfo> changes)881 private static boolean canPromote(WindowContainer target, ArraySet<WindowContainer> topTargets, 882 ArrayMap<WindowContainer, ChangeInfo> changes) { 883 final WindowContainer parent = target.getParent(); 884 final ChangeInfo parentChanges = parent != null ? changes.get(parent) : null; 885 if (parent == null || !parent.canCreateRemoteAnimationTarget() 886 || parentChanges == null || !parentChanges.hasChanged(parent)) { 887 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s", 888 parent == null ? "no parent" : ("parent can't be target " + parent)); 889 return false; 890 } 891 if (isWallpaper(target)) { 892 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: is wallpaper"); 893 return false; 894 } 895 @TransitionInfo.TransitionMode int mode = TRANSIT_NONE; 896 // Go through all siblings of this target to see if any of them would prevent 897 // the target from promoting. 898 siblingLoop: 899 for (int i = parent.getChildCount() - 1; i >= 0; --i) { 900 final WindowContainer sibling = parent.getChildAt(i); 901 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s", 902 sibling); 903 // Check if any topTargets are the sibling or within it 904 for (int j = topTargets.size() - 1; j >= 0; --j) { 905 final int depth = getChildDepth(topTargets.valueAt(j), sibling); 906 if (depth < 0) continue; 907 if (depth == 0) { 908 final int siblingMode = changes.get(sibling).getTransitMode(sibling); 909 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 910 " sibling is a top target with mode %s", 911 TransitionInfo.modeToString(siblingMode)); 912 if (mode == TRANSIT_NONE) { 913 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 914 " no common mode yet, so set it"); 915 mode = siblingMode; 916 } else if (mode != siblingMode) { 917 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 918 " SKIP: common mode mismatch. was %s", 919 TransitionInfo.modeToString(mode)); 920 return false; 921 } 922 continue siblingLoop; 923 } else { 924 // Sibling subtree may not be promotable. 925 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 926 " SKIP: sibling contains top target %s", 927 topTargets.valueAt(j)); 928 return false; 929 } 930 } 931 // No other animations are playing in this sibling 932 if (sibling.isVisibleRequested()) { 933 // Sibling is visible but not animating, so no promote. 934 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 935 " SKIP: sibling is visible but not part of transition"); 936 return false; 937 } 938 } 939 return true; 940 } 941 942 /** 943 * Go through topTargets and try to promote (see {@link #canPromote}) one of them. 944 * 945 * @param topTargets set of just the top-most targets in the hierarchy of participants. 946 * @param targets all targets that will be sent to the player. 947 * @return {@code true} if something was promoted. 948 */ tryPromote(ArraySet<WindowContainer> topTargets, ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes)949 private static boolean tryPromote(ArraySet<WindowContainer> topTargets, 950 ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) { 951 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " --- Start combine pass ---"); 952 // Go through each target until we find one that can be promoted. 953 for (WindowContainer targ : topTargets) { 954 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", targ); 955 if (!canPromote(targ, topTargets, changes)) { 956 continue; 957 } 958 // No obstructions found to promotion, so promote 959 final WindowContainer parent = targ.getParent(); 960 final ChangeInfo parentInfo = changes.get(parent); 961 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 962 " CAN PROMOTE: promoting to parent %s", parent); 963 targets.add(parent); 964 965 // Go through all children of newly-promoted container and remove them from the 966 // top-targets. 967 for (int i = parent.getChildCount() - 1; i >= 0; --i) { 968 final WindowContainer child = parent.getChildAt(i); 969 int idx = targets.indexOf(child); 970 if (idx >= 0) { 971 final ChangeInfo childInfo = changes.get(child); 972 if (reportIfNotTop(child)) { 973 childInfo.mParent = parent; 974 parentInfo.addChild(child); 975 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 976 " keep as target %s", child); 977 } else { 978 if (childInfo.mChildren != null) { 979 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 980 " merging children in from %s: %s", child, 981 childInfo.mChildren); 982 parentInfo.addChildren(childInfo.mChildren); 983 } 984 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 985 " remove from targets %s", child); 986 targets.removeAt(idx); 987 } 988 } 989 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 990 " remove from topTargets %s", child); 991 topTargets.remove(child); 992 } 993 topTargets.add(parent); 994 return true; 995 } 996 return false; 997 } 998 999 /** 1000 * Find WindowContainers to be animated from a set of opening and closing apps. We will promote 1001 * animation targets to higher level in the window hierarchy if possible. 1002 */ 1003 @VisibleForTesting 1004 @NonNull calculateTargets(ArraySet<WindowContainer> participants, ArrayMap<WindowContainer, ChangeInfo> changes)1005 static ArraySet<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants, 1006 ArrayMap<WindowContainer, ChangeInfo> changes) { 1007 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1008 "Start calculating TransitionInfo based on participants: %s", participants); 1009 1010 final ArraySet<WindowContainer> topTargets = new ArraySet<>(); 1011 // The final animation targets which cannot promote to higher level anymore. 1012 final ArraySet<WindowContainer> targets = new ArraySet<>(); 1013 1014 final ArrayList<WindowContainer> tmpList = new ArrayList<>(); 1015 1016 // Build initial set of top-level participants by removing any participants that are no-ops 1017 // or children of other participants or are otherwise invalid; however, keep around a list 1018 // of participants that should always be reported even if they aren't top. 1019 for (WindowContainer wc : participants) { 1020 // Don't include detached windows. 1021 if (!wc.isAttached()) { 1022 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1023 " Rejecting as detached: %s", wc); 1024 continue; 1025 } 1026 1027 final ChangeInfo changeInfo = changes.get(wc); 1028 1029 // Reject no-ops 1030 if (!changeInfo.hasChanged(wc)) { 1031 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 1032 " Rejecting as no-op: %s", wc); 1033 continue; 1034 } 1035 1036 // Search through ancestors to find the top-most participant (if one exists) 1037 WindowContainer topParent = null; 1038 tmpList.clear(); 1039 if (reportIfNotTop(wc)) { 1040 tmpList.add(wc); 1041 } 1042 // Wallpaper must be the top (regardless of how nested it is in DisplayAreas). 1043 boolean skipIntermediateReports = isWallpaper(wc); 1044 for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) { 1045 if (!p.isAttached() || changes.get(p) == null || !changes.get(p).hasChanged(p)) { 1046 // Again, we're skipping no-ops 1047 break; 1048 } 1049 if (participants.contains(p)) { 1050 topParent = p; 1051 break; 1052 } else if (isWallpaper(p)) { 1053 skipIntermediateReports = true; 1054 } else if (reportIfNotTop(p) && !skipIntermediateReports) { 1055 tmpList.add(p); 1056 } 1057 } 1058 if (topParent != null) { 1059 // There was an ancestor participant, so don't add wc to targets unless always- 1060 // report. Similarly, add any always-report parents along the way. 1061 for (int i = 0; i < tmpList.size(); ++i) { 1062 targets.add(tmpList.get(i)); 1063 final ChangeInfo info = changes.get(tmpList.get(i)); 1064 info.mParent = i < tmpList.size() - 1 ? tmpList.get(i + 1) : topParent; 1065 } 1066 continue; 1067 } 1068 // No ancestors in participant-list, so wc is a top target. 1069 targets.add(wc); 1070 topTargets.add(wc); 1071 } 1072 1073 // Populate children lists 1074 for (int i = targets.size() - 1; i >= 0; --i) { 1075 if (changes.get(targets.valueAt(i)).mParent != null) { 1076 changes.get(changes.get(targets.valueAt(i)).mParent).addChild(targets.valueAt(i)); 1077 } 1078 } 1079 1080 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Initial targets: %s", targets); 1081 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Top targets: %s", topTargets); 1082 1083 // Combine targets by repeatedly going through the topTargets to see if they can be 1084 // promoted until there aren't any promotions possible. 1085 while (tryPromote(topTargets, targets, changes)) { 1086 // Empty on purpose 1087 } 1088 return targets; 1089 } 1090 1091 /** Add any of `members` within `root` to `out` in top-to-bottom z-order. */ addMembersInOrder(WindowContainer root, ArraySet<WindowContainer> members, ArrayList<WindowContainer> out)1092 private static void addMembersInOrder(WindowContainer root, ArraySet<WindowContainer> members, 1093 ArrayList<WindowContainer> out) { 1094 for (int i = root.getChildCount() - 1; i >= 0; --i) { 1095 final WindowContainer child = root.getChildAt(i); 1096 addMembersInOrder(child, members, out); 1097 if (members.contains(child)) { 1098 out.add(child); 1099 } 1100 } 1101 } 1102 1103 /** Gets the leash surface for a window container */ getLeashSurface(WindowContainer wc)1104 private static SurfaceControl getLeashSurface(WindowContainer wc) { 1105 final DisplayContent asDC = wc.asDisplayContent(); 1106 if (asDC != null) { 1107 // DisplayContent is the "root", so we use the windowing layer instead to avoid 1108 // hardware-screen-level surfaces. 1109 return asDC.getWindowingLayer(); 1110 } 1111 return wc.getSurfaceControl(); 1112 } 1113 getOrigParentSurface(WindowContainer wc)1114 private static SurfaceControl getOrigParentSurface(WindowContainer wc) { 1115 if (wc.asDisplayContent() != null) { 1116 // DisplayContent is the "root", so we reinterpret it's wc as the window layer 1117 // making the parent surface the displaycontent's surface. 1118 return wc.getSurfaceControl(); 1119 } 1120 return wc.getParent().getSurfaceControl(); 1121 } 1122 1123 /** 1124 * A ready group is defined by a root window-container where all transitioning windows under 1125 * it are expected to animate together as a group. At the moment, this treats each display as 1126 * a ready-group to match the existing legacy transition behavior. 1127 */ isReadyGroup(WindowContainer wc)1128 private static boolean isReadyGroup(WindowContainer wc) { 1129 return wc instanceof DisplayContent; 1130 } 1131 1132 /** 1133 * Construct a TransitionInfo object from a set of targets and changes. Also populates the 1134 * root surface. 1135 */ 1136 @VisibleForTesting 1137 @NonNull calculateTransitionInfo(@ransitionType int type, int flags, ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes)1138 static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags, 1139 ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) { 1140 final TransitionInfo out = new TransitionInfo(type, flags); 1141 1142 final ArraySet<WindowContainer> appTargets = new ArraySet<>(); 1143 final ArraySet<WindowContainer> wallpapers = new ArraySet<>(); 1144 for (int i = targets.size() - 1; i >= 0; --i) { 1145 (isWallpaper(targets.valueAt(i)) ? wallpapers : appTargets).add(targets.valueAt(i)); 1146 } 1147 1148 // Find the top-most shared ancestor of app targets 1149 if (appTargets.isEmpty()) { 1150 out.setRootLeash(new SurfaceControl(), 0, 0); 1151 return out; 1152 } 1153 WindowContainer ancestor = appTargets.valueAt(appTargets.size() - 1).getParent(); 1154 1155 // Go up ancestor parent chain until all targets are descendants. 1156 ancestorLoop: 1157 while (ancestor != null) { 1158 for (int i = appTargets.size() - 1; i >= 0; --i) { 1159 final WindowContainer wc = appTargets.valueAt(i); 1160 if (!wc.isDescendantOf(ancestor)) { 1161 ancestor = ancestor.getParent(); 1162 continue ancestorLoop; 1163 } 1164 } 1165 break; 1166 } 1167 1168 // Sort targets top-to-bottom in Z. Check ALL targets here in case the display area itself 1169 // is animating: then we want to include wallpapers at the right position. 1170 ArrayList<WindowContainer> sortedTargets = new ArrayList<>(); 1171 addMembersInOrder(ancestor, targets, sortedTargets); 1172 1173 // make leash based on highest (z-order) direct child of ancestor with a participant. 1174 WindowContainer leashReference = sortedTargets.get(0); 1175 while (leashReference.getParent() != ancestor) { 1176 leashReference = leashReference.getParent(); 1177 } 1178 final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName( 1179 "Transition Root: " + leashReference.getName()).build(); 1180 SurfaceControl.Transaction t = ancestor.mWmService.mTransactionFactory.get(); 1181 t.setLayer(rootLeash, leashReference.getLastLayer()); 1182 t.apply(); 1183 t.close(); 1184 out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top); 1185 1186 // add the wallpapers at the bottom 1187 for (int i = wallpapers.size() - 1; i >= 0; --i) { 1188 final WindowContainer wc = wallpapers.valueAt(i); 1189 // If the displayarea itself is animating, then the wallpaper was already added. 1190 if (wc.isDescendantOf(ancestor)) break; 1191 sortedTargets.add(wc); 1192 } 1193 1194 // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order. 1195 final int count = sortedTargets.size(); 1196 for (int i = 0; i < count; ++i) { 1197 final WindowContainer target = sortedTargets.get(i); 1198 final ChangeInfo info = changes.get(target); 1199 final TransitionInfo.Change change = new TransitionInfo.Change( 1200 target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken() 1201 : null, getLeashSurface(target)); 1202 // TODO(shell-transitions): Use leash for non-organized windows. 1203 if (info.mParent != null) { 1204 change.setParent(info.mParent.mRemoteToken.toWindowContainerToken()); 1205 } 1206 change.setMode(info.getTransitMode(target)); 1207 change.setStartAbsBounds(info.mAbsoluteBounds); 1208 change.setEndAbsBounds(target.getBounds()); 1209 change.setEndRelOffset(target.getBounds().left - target.getParent().getBounds().left, 1210 target.getBounds().top - target.getParent().getBounds().top); 1211 change.setFlags(info.getChangeFlags(target)); 1212 change.setRotation(info.mRotation, target.getWindowConfiguration().getRotation()); 1213 final Task task = target.asTask(); 1214 if (task != null) { 1215 final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo(); 1216 task.fillTaskInfo(tinfo); 1217 change.setTaskInfo(tinfo); 1218 change.setRotationAnimation(getTaskRotationAnimation(task)); 1219 final ActivityRecord topMostActivity = task.getTopMostActivity(); 1220 change.setAllowEnterPip(topMostActivity != null 1221 && topMostActivity.checkEnterPictureInPictureAppOpsState()); 1222 } 1223 out.addChange(change); 1224 } 1225 1226 return out; 1227 } 1228 getTaskRotationAnimation(@onNull Task task)1229 private static int getTaskRotationAnimation(@NonNull Task task) { 1230 final ActivityRecord top = task.getTopVisibleActivity(); 1231 if (top == null) return ROTATION_ANIMATION_UNSPECIFIED; 1232 final WindowState mainWin = top.findMainWindow(false); 1233 if (mainWin == null) return ROTATION_ANIMATION_UNSPECIFIED; 1234 int anim = mainWin.getRotationAnimationHint(); 1235 if (anim >= 0) return anim; 1236 anim = mainWin.getAttrs().rotationAnimation; 1237 if (anim != ROTATION_ANIMATION_SEAMLESS) return anim; 1238 if (mainWin != task.mDisplayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow() 1239 || !top.matchParentBounds()) { 1240 // At the moment, we only support seamless rotation if there is only one window showing. 1241 return ROTATION_ANIMATION_UNSPECIFIED; 1242 } 1243 return mainWin.getAttrs().rotationAnimation; 1244 } 1245 getLegacyIsReady()1246 boolean getLegacyIsReady() { 1247 return mState == STATE_STARTED && mSyncId >= 0 && mSyncEngine.isReady(mSyncId); 1248 } 1249 fromBinder(IBinder binder)1250 static Transition fromBinder(IBinder binder) { 1251 return (Transition) binder; 1252 } 1253 1254 @VisibleForTesting 1255 static class ChangeInfo { 1256 // Usually "post" change state. 1257 WindowContainer mParent; 1258 ArraySet<WindowContainer> mChildren; 1259 1260 // State tracking 1261 boolean mExistenceChanged = false; 1262 // before change state 1263 boolean mVisible; 1264 int mWindowingMode; 1265 final Rect mAbsoluteBounds = new Rect(); 1266 boolean mShowWallpaper; 1267 int mRotation = ROTATION_UNDEFINED; 1268 @ActivityInfo.Config int mKnownConfigChanges; 1269 ChangeInfo(@onNull WindowContainer origState)1270 ChangeInfo(@NonNull WindowContainer origState) { 1271 mVisible = origState.isVisibleRequested(); 1272 mWindowingMode = origState.getWindowingMode(); 1273 mAbsoluteBounds.set(origState.getBounds()); 1274 mShowWallpaper = origState.showWallpaper(); 1275 mRotation = origState.getWindowConfiguration().getRotation(); 1276 } 1277 1278 @VisibleForTesting ChangeInfo(boolean visible, boolean existChange)1279 ChangeInfo(boolean visible, boolean existChange) { 1280 mVisible = visible; 1281 mExistenceChanged = existChange; 1282 mShowWallpaper = false; 1283 } 1284 hasChanged(@onNull WindowContainer newState)1285 boolean hasChanged(@NonNull WindowContainer newState) { 1286 // If it's invisible and hasn't changed visibility, always return false since even if 1287 // something changed, it wouldn't be a visible change. 1288 final boolean currVisible = newState.isVisibleRequested(); 1289 if (currVisible == mVisible && !mVisible) return false; 1290 return currVisible != mVisible 1291 || mKnownConfigChanges != 0 1292 // if mWindowingMode is 0, this container wasn't attached at collect time, so 1293 // assume no change in windowing-mode. 1294 || (mWindowingMode != 0 && newState.getWindowingMode() != mWindowingMode) 1295 || !newState.getBounds().equals(mAbsoluteBounds) 1296 || mRotation != newState.getWindowConfiguration().getRotation(); 1297 } 1298 1299 @TransitionInfo.TransitionMode getTransitMode(@onNull WindowContainer wc)1300 int getTransitMode(@NonNull WindowContainer wc) { 1301 final boolean nowVisible = wc.isVisibleRequested(); 1302 if (nowVisible == mVisible) { 1303 return TRANSIT_CHANGE; 1304 } 1305 if (mExistenceChanged) { 1306 return nowVisible ? TRANSIT_OPEN : TRANSIT_CLOSE; 1307 } else { 1308 return nowVisible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK; 1309 } 1310 } 1311 1312 @TransitionInfo.ChangeFlags getChangeFlags(@onNull WindowContainer wc)1313 int getChangeFlags(@NonNull WindowContainer wc) { 1314 int flags = 0; 1315 if (mShowWallpaper || wc.showWallpaper()) { 1316 flags |= FLAG_SHOW_WALLPAPER; 1317 } 1318 if (!wc.fillsParent()) { 1319 // TODO(b/172695805): hierarchical check. This is non-trivial because for containers 1320 // it is effected by child visibility but needs to work even 1321 // before visibility is committed. This means refactoring some 1322 // checks to use requested visibility. 1323 flags |= FLAG_TRANSLUCENT; 1324 } 1325 final Task task = wc.asTask(); 1326 if (task != null && task.voiceSession != null) { 1327 flags |= FLAG_IS_VOICE_INTERACTION; 1328 } 1329 final ActivityRecord record = wc.asActivityRecord(); 1330 if (record != null) { 1331 if (record.mUseTransferredAnimation) { 1332 flags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; 1333 } 1334 if (record.mVoiceInteraction) { 1335 flags |= FLAG_IS_VOICE_INTERACTION; 1336 } 1337 } 1338 final DisplayContent dc = wc.asDisplayContent(); 1339 if (dc != null) { 1340 flags |= FLAG_IS_DISPLAY; 1341 if (dc.hasAlertWindowSurfaces()) { 1342 flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS; 1343 } 1344 } 1345 if (isWallpaper(wc)) { 1346 flags |= FLAG_IS_WALLPAPER; 1347 } 1348 if (occludesKeyguard(wc)) { 1349 flags |= FLAG_OCCLUDES_KEYGUARD; 1350 } 1351 return flags; 1352 } 1353 addChild(@onNull WindowContainer wc)1354 void addChild(@NonNull WindowContainer wc) { 1355 if (mChildren == null) { 1356 mChildren = new ArraySet<>(); 1357 } 1358 mChildren.add(wc); 1359 } addChildren(@onNull ArraySet<WindowContainer> wcs)1360 void addChildren(@NonNull ArraySet<WindowContainer> wcs) { 1361 if (mChildren == null) { 1362 mChildren = new ArraySet<>(); 1363 } 1364 mChildren.addAll(wcs); 1365 } 1366 } 1367 1368 /** 1369 * The transition sync mechanism has 2 parts: 1370 * 1. Whether all WM operations for a particular transition are "ready" (eg. did the app 1371 * launch or stop or get a new configuration?). 1372 * 2. Whether all the windows involved have finished drawing their final-state content. 1373 * 1374 * A transition animation can play once both parts are complete. This ready-tracker keeps track 1375 * of part (1). Currently, WM code assumes that "readiness" (part 1) is grouped. This means that 1376 * even if the WM operations in one group are ready, the whole transition itself may not be 1377 * ready if there are WM operations still pending in another group. This class helps keep track 1378 * of readiness across the multiple groups. Currently, we assume that each display is a group 1379 * since that is how it has been until now. 1380 */ 1381 private static class ReadyTracker { 1382 private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>(); 1383 1384 /** 1385 * Ensures that this doesn't report as allReady before it has been used. This is needed 1386 * in very niche cases where a transition is a no-op (nothing has been collected) but we 1387 * still want to be marked ready (via. setAllReady). 1388 */ 1389 private boolean mUsed = false; 1390 1391 /** 1392 * If true, this overrides all ready groups and reports ready. Used by shell-initiated 1393 * transitions via {@link #setAllReady()}. 1394 */ 1395 private boolean mReadyOverride = false; 1396 1397 /** 1398 * Adds a ready-group. Any setReady calls in this subtree will be tracked together. For 1399 * now these are only DisplayContents. 1400 */ addGroup(WindowContainer wc)1401 void addGroup(WindowContainer wc) { 1402 if (mReadyGroups.containsKey(wc)) { 1403 Slog.e(TAG, "Trying to add a ready-group twice: " + wc); 1404 return; 1405 } 1406 mReadyGroups.put(wc, false); 1407 } 1408 1409 /** 1410 * Sets a group's ready state. 1411 * @param wc Any container within a group's subtree. Used to identify the ready-group. 1412 */ setReadyFrom(WindowContainer wc, boolean ready)1413 void setReadyFrom(WindowContainer wc, boolean ready) { 1414 mUsed = true; 1415 WindowContainer current = wc; 1416 while (current != null) { 1417 if (isReadyGroup(current)) { 1418 mReadyGroups.put(current, ready); 1419 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting Ready-group to" 1420 + " %b. group=%s from %s", ready, current, wc); 1421 break; 1422 } 1423 current = current.getParent(); 1424 } 1425 } 1426 1427 /** Marks this as ready regardless of individual groups. */ setAllReady()1428 void setAllReady() { 1429 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting allReady override"); 1430 mUsed = true; 1431 mReadyOverride = true; 1432 } 1433 1434 /** @return true if all tracked subtrees are ready. */ allReady()1435 boolean allReady() { 1436 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b " 1437 + "override=%b states=[%s]", mUsed, mReadyOverride, groupsToString()); 1438 if (!mUsed) return false; 1439 if (mReadyOverride) return true; 1440 for (int i = mReadyGroups.size() - 1; i >= 0; --i) { 1441 final WindowContainer wc = mReadyGroups.keyAt(i); 1442 if (!wc.isAttached() || !wc.isVisibleRequested()) continue; 1443 if (!mReadyGroups.valueAt(i)) return false; 1444 } 1445 return true; 1446 } 1447 groupsToString()1448 private String groupsToString() { 1449 StringBuilder b = new StringBuilder(); 1450 for (int i = 0; i < mReadyGroups.size(); ++i) { 1451 if (i != 0) b.append(','); 1452 b.append(mReadyGroups.keyAt(i)).append(':') 1453 .append(mReadyGroups.valueAt(i)); 1454 } 1455 return b.toString(); 1456 } 1457 } 1458 } 1459