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 20 import static android.app.WindowConfiguration.ROTATION_UNDEFINED; 21 import static android.view.Display.DEFAULT_DISPLAY; 22 import static android.view.WindowManager.TRANSIT_CHANGE; 23 import static android.view.WindowManager.TRANSIT_CLOSE; 24 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; 25 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; 26 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; 27 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; 28 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; 29 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; 30 import static android.view.WindowManager.TRANSIT_NONE; 31 import static android.view.WindowManager.TRANSIT_OPEN; 32 import static android.view.WindowManager.TRANSIT_TO_BACK; 33 import static android.view.WindowManager.TRANSIT_TO_FRONT; 34 import static android.view.WindowManager.transitTypeToString; 35 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; 36 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; 37 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; 38 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; 39 import static android.window.TransitionInfo.FLAG_TRANSLUCENT; 40 41 import android.annotation.IntDef; 42 import android.annotation.NonNull; 43 import android.app.ActivityManager; 44 import android.graphics.Point; 45 import android.graphics.Rect; 46 import android.os.Binder; 47 import android.os.IBinder; 48 import android.os.RemoteException; 49 import android.os.SystemClock; 50 import android.util.ArrayMap; 51 import android.util.ArraySet; 52 import android.util.Slog; 53 import android.view.SurfaceControl; 54 import android.view.WindowManager; 55 import android.view.animation.Animation; 56 import android.window.IRemoteTransition; 57 import android.window.TransitionInfo; 58 59 import com.android.internal.annotations.VisibleForTesting; 60 import com.android.internal.protolog.ProtoLogGroup; 61 import com.android.internal.protolog.common.ProtoLog; 62 63 import java.lang.annotation.Retention; 64 import java.lang.annotation.RetentionPolicy; 65 import java.util.ArrayList; 66 67 /** 68 * Represents a logical transition. 69 * @see TransitionController 70 */ 71 class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener { 72 private static final String TAG = "Transition"; 73 74 /** The transition has been created and is collecting, but hasn't formally started. */ 75 private static final int STATE_COLLECTING = 0; 76 77 /** 78 * The transition has formally started. It is still collecting but will stop once all 79 * participants are ready to animate (finished drawing). 80 */ 81 private static final int STATE_STARTED = 1; 82 83 /** 84 * This transition is currently playing its animation and can no longer collect or be changed. 85 */ 86 private static final int STATE_PLAYING = 2; 87 88 /** 89 * This transition is aborting or has aborted. No animation will play nor will anything get 90 * sent to the player. 91 */ 92 private static final int STATE_ABORT = 3; 93 94 @IntDef(prefix = { "STATE_" }, value = { 95 STATE_COLLECTING, 96 STATE_STARTED, 97 STATE_PLAYING, 98 STATE_ABORT 99 }) 100 @Retention(RetentionPolicy.SOURCE) 101 @interface TransitionState {} 102 103 final @WindowManager.TransitionType int mType; 104 private int mSyncId; 105 private @WindowManager.TransitionFlags int mFlags; 106 private final TransitionController mController; 107 private final BLASTSyncEngine mSyncEngine; 108 private IRemoteTransition mRemoteTransition = null; 109 110 /** Only use for clean-up after binder death! */ 111 private SurfaceControl.Transaction mStartTransaction = null; 112 private SurfaceControl.Transaction mFinishTransaction = null; 113 114 /** 115 * Contains change infos for both participants and all ancestors. We have to track ancestors 116 * because they are all promotion candidates and thus we need their start-states 117 * to be captured. 118 */ 119 final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>(); 120 121 /** The collected participants in the transition. */ 122 final ArraySet<WindowContainer> mParticipants = new ArraySet<>(); 123 124 /** The final animation targets derived from participants after promotion. */ 125 private ArraySet<WindowContainer> mTargets = null; 126 127 private @TransitionState int mState = STATE_COLLECTING; 128 private boolean mReadyCalled = false; 129 Transition(@indowManager.TransitionType int type, @WindowManager.TransitionFlags int flags, TransitionController controller, BLASTSyncEngine syncEngine)130 Transition(@WindowManager.TransitionType int type, @WindowManager.TransitionFlags int flags, 131 TransitionController controller, BLASTSyncEngine syncEngine) { 132 mType = type; 133 mFlags = flags; 134 mController = controller; 135 mSyncEngine = syncEngine; 136 mSyncId = mSyncEngine.startSyncSet(this); 137 } 138 139 @VisibleForTesting getSyncId()140 int getSyncId() { 141 return mSyncId; 142 } 143 144 /** 145 * Formally starts the transition. Participants can be collected before this is started, 146 * but this won't consider itself ready until started -- even if all the participants have 147 * drawn. 148 */ start()149 void start() { 150 if (mState >= STATE_STARTED) { 151 Slog.w(TAG, "Transition already started: " + mSyncId); 152 } 153 mState = STATE_STARTED; 154 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d", 155 mSyncId); 156 if (mReadyCalled) { 157 setReady(); 158 } 159 } 160 161 /** 162 * Adds wc to set of WindowContainers participating in this transition. 163 */ collect(@onNull WindowContainer wc)164 void collect(@NonNull WindowContainer wc) { 165 if (mSyncId < 0) return; 166 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s", 167 mSyncId, wc); 168 // "snapshot" all parents (as potential promotion targets). Do this before checking 169 // if this is already a participant in case it has since been re-parented. 170 for (WindowContainer curr = wc.getParent(); curr != null && !mChanges.containsKey(curr); 171 curr = curr.getParent()) { 172 mChanges.put(curr, new ChangeInfo(curr)); 173 } 174 if (mParticipants.contains(wc)) return; 175 mSyncEngine.addToSyncSet(mSyncId, wc); 176 ChangeInfo info = mChanges.get(wc); 177 if (info == null) { 178 info = new ChangeInfo(wc); 179 mChanges.put(wc, info); 180 } 181 mParticipants.add(wc); 182 if (info.mShowWallpaper) { 183 // Collect the wallpaper so it is part of the sync set. 184 final WindowContainer wallpaper = 185 wc.getDisplayContent().mWallpaperController.getTopVisibleWallpaper(); 186 if (wallpaper != null) { 187 collect(wallpaper); 188 } 189 } 190 } 191 192 /** 193 * Records wc as changing its state of existence during this transition. For example, a new 194 * task is considered an existence change while moving a task to front is not. wc is added 195 * to the collection set. Note: Existence is NOT a promotable characteristic. 196 * 197 * This must be explicitly recorded because there are o number of situations where the actual 198 * hierarchy operations don't align with the intent (eg. re-using a task with a new activity 199 * or waiting until after the animation to close). 200 */ collectExistenceChange(@onNull WindowContainer wc)201 void collectExistenceChange(@NonNull WindowContainer wc) { 202 if (mSyncId < 0) return; 203 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:" 204 + " %s", mSyncId, wc); 205 collect(wc); 206 mChanges.get(wc).mExistenceChanged = true; 207 } 208 209 /** 210 * Call this when all known changes related to this transition have been applied. Until 211 * all participants have finished drawing, the transition can still collect participants. 212 * 213 * If this is called before the transition is started, it will be deferred until start. 214 */ setReady(boolean ready)215 void setReady(boolean ready) { 216 if (mSyncId < 0) return; 217 if (mState < STATE_STARTED) { 218 mReadyCalled = ready; 219 return; 220 } 221 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 222 "Set transition ready=%b %d", ready, mSyncId); 223 mSyncEngine.setReady(mSyncId, ready); 224 } 225 226 /** @see #setReady . This calls with parameter true. */ setReady()227 void setReady() { 228 setReady(true); 229 } 230 231 /** 232 * Build a transaction that "resets" all the re-parenting and layer changes. This is 233 * intended to be applied at the end of the transition but before the finish callback. This 234 * needs to be passed/applied in shell because until finish is called, shell owns the surfaces. 235 * Additionally, this gives shell the ability to better deal with merged transitions. 236 */ buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash)237 private void buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash) { 238 final Point tmpPos = new Point(); 239 // usually only size 1 240 final ArraySet<DisplayContent> displays = new ArraySet<>(); 241 for (int i = mTargets.size() - 1; i >= 0; --i) { 242 final WindowContainer target = mTargets.valueAt(i); 243 if (target.getParent() != null) { 244 final SurfaceControl targetLeash = getLeashSurface(target); 245 final SurfaceControl origParent = getOrigParentSurface(target); 246 // Ensure surfaceControls are re-parented back into the hierarchy. 247 t.reparent(targetLeash, origParent); 248 t.setLayer(targetLeash, target.getLastLayer()); 249 target.getRelativePosition(tmpPos); 250 t.setPosition(targetLeash, tmpPos.x, tmpPos.y); 251 t.setCornerRadius(targetLeash, 0); 252 t.setShadowRadius(targetLeash, 0); 253 displays.add(target.getDisplayContent()); 254 } 255 } 256 // Need to update layers on involved displays since they were all paused while 257 // the animation played. This puts the layers back into the correct order. 258 for (int i = displays.size() - 1; i >= 0; --i) { 259 if (displays.valueAt(i) == null) continue; 260 displays.valueAt(i).assignChildLayers(t); 261 } 262 if (rootLeash.isValid()) { 263 t.reparent(rootLeash, null); 264 } 265 } 266 267 /** 268 * The transition has finished animating and is ready to finalize WM state. This should not 269 * be called directly; use {@link TransitionController#finishTransition} instead. 270 */ finishTransition()271 void finishTransition() { 272 mStartTransaction = mFinishTransaction = null; 273 if (mState < STATE_PLAYING) { 274 throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId); 275 } 276 277 // Commit all going-invisible containers 278 for (int i = 0; i < mParticipants.size(); ++i) { 279 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 280 if (ar != null && !ar.isVisibleRequested()) { 281 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 282 " Commit activity becoming invisible: %s", ar); 283 ar.commitVisibility(false /* visible */, false /* performLayout */); 284 } 285 final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken(); 286 if (wt != null && !wt.isVisibleRequested()) { 287 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 288 " Commit wallpaper becoming invisible: %s", ar); 289 wt.commitVisibility(false /* visible */); 290 } 291 } 292 } 293 abort()294 void abort() { 295 // This calls back into itself via controller.abort, so just early return here. 296 if (mState == STATE_ABORT) return; 297 if (mState != STATE_COLLECTING) { 298 throw new IllegalStateException("Too late to abort."); 299 } 300 mState = STATE_ABORT; 301 // Syncengine abort will call through to onTransactionReady() 302 mSyncEngine.abort(mSyncId); 303 } 304 setRemoteTransition(IRemoteTransition remoteTransition)305 void setRemoteTransition(IRemoteTransition remoteTransition) { 306 mRemoteTransition = remoteTransition; 307 } 308 getRemoteTransition()309 IRemoteTransition getRemoteTransition() { 310 return mRemoteTransition; 311 } 312 313 @Override onTransactionReady(int syncId, SurfaceControl.Transaction transaction)314 public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) { 315 if (syncId != mSyncId) { 316 Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId); 317 return; 318 } 319 int displayId = DEFAULT_DISPLAY; 320 for (WindowContainer container : mParticipants) { 321 if (container.mDisplayContent == null) continue; 322 displayId = container.mDisplayContent.getDisplayId(); 323 } 324 325 if (mState == STATE_ABORT) { 326 mController.abort(this); 327 mController.mAtm.mRootWindowContainer.getDisplayContent(displayId) 328 .getPendingTransaction().merge(transaction); 329 mSyncId = -1; 330 return; 331 } 332 333 mState = STATE_PLAYING; 334 mController.moveToPlaying(this); 335 336 if (mController.mAtm.mTaskSupervisor.getKeyguardController().isKeyguardLocked()) { 337 mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED; 338 } 339 340 // Resolve the animating targets from the participants 341 mTargets = calculateTargets(mParticipants, mChanges); 342 final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges); 343 344 handleNonAppWindowsInTransition(displayId, mType, mFlags); 345 346 // Manually show any activities that are visibleRequested. This is needed to properly 347 // support simultaneous animation queueing/merging. Specifically, if transition A makes 348 // an activity invisible, it's finishTransaction (which is applied *after* the animation) 349 // will hide the activity surface. If transition B then makes the activity visible again, 350 // the normal surfaceplacement logic won't add a show to this start transaction because 351 // the activity visibility hasn't been committed yet. To deal with this, we have to manually 352 // show here in the same way that we manually hide in finishTransaction. 353 for (int i = mParticipants.size() - 1; i >= 0; --i) { 354 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); 355 if (ar == null || !ar.mVisibleRequested) continue; 356 transaction.show(ar.getSurfaceControl()); 357 } 358 359 mStartTransaction = transaction; 360 mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get(); 361 buildFinishTransaction(mFinishTransaction, info.getRootLeash()); 362 if (mController.getTransitionPlayer() != null) { 363 try { 364 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 365 "Calling onTransitionReady: %s", info); 366 mController.getTransitionPlayer().onTransitionReady( 367 this, info, transaction, mFinishTransaction); 368 } catch (RemoteException e) { 369 // If there's an exception when trying to send the mergedTransaction to the 370 // client, we should finish and apply it here so the transactions aren't lost. 371 cleanUpOnFailure(); 372 } 373 } else { 374 // No player registered, so just finish/apply immediately 375 cleanUpOnFailure(); 376 } 377 mSyncId = -1; 378 } 379 380 /** 381 * If the remote failed for any reason, use this to do any appropriate clean-up. Do not call 382 * this directly, it's designed to by called by {@link TransitionController} only. 383 */ cleanUpOnFailure()384 void cleanUpOnFailure() { 385 // No need to clean-up if this isn't playing yet. 386 if (mState < STATE_PLAYING) return; 387 388 if (mStartTransaction != null) { 389 mStartTransaction.apply(); 390 } 391 if (mFinishTransaction != null) { 392 mFinishTransaction.apply(); 393 } 394 finishTransition(); 395 } 396 handleNonAppWindowsInTransition(int displayId, @WindowManager.TransitionType int transit, int flags)397 private void handleNonAppWindowsInTransition(int displayId, 398 @WindowManager.TransitionType int transit, int flags) { 399 final DisplayContent dc = 400 mController.mAtm.mRootWindowContainer.getDisplayContent(displayId); 401 if (dc == null) { 402 return; 403 } 404 if (transit == TRANSIT_KEYGUARD_GOING_AWAY 405 && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) { 406 if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0 407 && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0 408 && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) { 409 Animation anim = mController.mAtm.mWindowManager.mPolicy 410 .createKeyguardWallpaperExit( 411 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0); 412 if (anim != null) { 413 anim.scaleCurrentDuration( 414 mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked()); 415 dc.mWallpaperController.startWallpaperAnimation(anim); 416 } 417 } 418 dc.startKeyguardExitOnNonAppWindows( 419 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0, 420 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0, 421 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0); 422 mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation( 423 SystemClock.uptimeMillis(), 0 /* duration */); 424 } 425 if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { 426 mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange(); 427 } 428 } 429 430 @Override toString()431 public String toString() { 432 StringBuilder sb = new StringBuilder(64); 433 sb.append("TransitionRecord{"); 434 sb.append(Integer.toHexString(System.identityHashCode(this))); 435 sb.append(" id=" + mSyncId); 436 sb.append(" type=" + transitTypeToString(mType)); 437 sb.append(" flags=" + mFlags); 438 sb.append('}'); 439 return sb.toString(); 440 } 441 reportIfNotTop(WindowContainer wc)442 private static boolean reportIfNotTop(WindowContainer wc) { 443 // Organized tasks need to be reported anyways because Core won't show() their surfaces 444 // and we can't rely on onTaskAppeared because it isn't in sync. 445 // Also report wallpaper so it can be handled properly during display change/rotation. 446 // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN. 447 return wc.isOrganized() || isWallpaper(wc); 448 } 449 450 /** @return the depth of child within ancestor, 0 if child == ancestor, or -1 if not a child. */ getChildDepth(WindowContainer child, WindowContainer ancestor)451 private static int getChildDepth(WindowContainer child, WindowContainer ancestor) { 452 WindowContainer parent = child; 453 int depth = 0; 454 while (parent != null) { 455 if (parent == ancestor) { 456 return depth; 457 } 458 parent = parent.getParent(); 459 ++depth; 460 } 461 return -1; 462 } 463 isWallpaper(WindowContainer wc)464 private static boolean isWallpaper(WindowContainer wc) { 465 return wc.asWallpaperToken() != null; 466 } 467 468 /** 469 * Under some conditions (eg. all visible targets within a parent container are transitioning 470 * the same way) the transition can be "promoted" to the parent container. This means an 471 * animation can play just on the parent rather than all the individual children. 472 * 473 * @return {@code true} if transition in target can be promoted to its parent. 474 */ canPromote(WindowContainer target, ArraySet<WindowContainer> topTargets, ArrayMap<WindowContainer, ChangeInfo> changes)475 private static boolean canPromote(WindowContainer target, ArraySet<WindowContainer> topTargets, 476 ArrayMap<WindowContainer, ChangeInfo> changes) { 477 final WindowContainer parent = target.getParent(); 478 final ChangeInfo parentChanges = parent != null ? changes.get(parent) : null; 479 if (parent == null || !parent.canCreateRemoteAnimationTarget() 480 || parentChanges == null || !parentChanges.hasChanged(parent)) { 481 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s", 482 parent == null ? "no parent" : ("parent can't be target " + parent)); 483 return false; 484 } 485 if (isWallpaper(target)) { 486 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: is wallpaper"); 487 return false; 488 } 489 @TransitionInfo.TransitionMode int mode = TRANSIT_NONE; 490 // Go through all siblings of this target to see if any of them would prevent 491 // the target from promoting. 492 siblingLoop: 493 for (int i = parent.getChildCount() - 1; i >= 0; --i) { 494 final WindowContainer sibling = parent.getChildAt(i); 495 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s", 496 sibling); 497 // Check if any topTargets are the sibling or within it 498 for (int j = topTargets.size() - 1; j >= 0; --j) { 499 final int depth = getChildDepth(topTargets.valueAt(j), sibling); 500 if (depth < 0) continue; 501 if (depth == 0) { 502 final int siblingMode = changes.get(sibling).getTransitMode(sibling); 503 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 504 " sibling is a top target with mode %s", 505 TransitionInfo.modeToString(siblingMode)); 506 if (mode == TRANSIT_NONE) { 507 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 508 " no common mode yet, so set it"); 509 mode = siblingMode; 510 } else if (mode != siblingMode) { 511 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 512 " SKIP: common mode mismatch. was %s", 513 TransitionInfo.modeToString(mode)); 514 return false; 515 } 516 continue siblingLoop; 517 } else { 518 // Sibling subtree may not be promotable. 519 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 520 " SKIP: sibling contains top target %s", 521 topTargets.valueAt(j)); 522 return false; 523 } 524 } 525 // No other animations are playing in this sibling 526 if (sibling.isVisibleRequested()) { 527 // Sibling is visible but not animating, so no promote. 528 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 529 " SKIP: sibling is visible but not part of transition"); 530 return false; 531 } 532 } 533 return true; 534 } 535 536 /** 537 * Go through topTargets and try to promote (see {@link #canPromote}) one of them. 538 * 539 * @param topTargets set of just the top-most targets in the hierarchy of participants. 540 * @param targets all targets that will be sent to the player. 541 * @return {@code true} if something was promoted. 542 */ tryPromote(ArraySet<WindowContainer> topTargets, ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes)543 private static boolean tryPromote(ArraySet<WindowContainer> topTargets, 544 ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) { 545 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " --- Start combine pass ---"); 546 // Go through each target until we find one that can be promoted. 547 for (WindowContainer targ : topTargets) { 548 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", targ); 549 if (!canPromote(targ, topTargets, changes)) { 550 continue; 551 } 552 // No obstructions found to promotion, so promote 553 final WindowContainer parent = targ.getParent(); 554 final ChangeInfo parentInfo = changes.get(parent); 555 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 556 " CAN PROMOTE: promoting to parent %s", parent); 557 targets.add(parent); 558 559 // Go through all children of newly-promoted container and remove them from the 560 // top-targets. 561 for (int i = parent.getChildCount() - 1; i >= 0; --i) { 562 final WindowContainer child = parent.getChildAt(i); 563 int idx = targets.indexOf(child); 564 if (idx >= 0) { 565 final ChangeInfo childInfo = changes.get(child); 566 if (reportIfNotTop(child)) { 567 childInfo.mParent = parent; 568 parentInfo.addChild(child); 569 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 570 " keep as target %s", child); 571 } else { 572 if (childInfo.mChildren != null) { 573 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 574 " merging children in from %s: %s", child, 575 childInfo.mChildren); 576 parentInfo.addChildren(childInfo.mChildren); 577 } 578 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 579 " remove from targets %s", child); 580 targets.removeAt(idx); 581 } 582 } 583 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 584 " remove from topTargets %s", child); 585 topTargets.remove(child); 586 } 587 topTargets.add(parent); 588 return true; 589 } 590 return false; 591 } 592 593 /** 594 * Find WindowContainers to be animated from a set of opening and closing apps. We will promote 595 * animation targets to higher level in the window hierarchy if possible. 596 */ 597 @VisibleForTesting 598 @NonNull calculateTargets(ArraySet<WindowContainer> participants, ArrayMap<WindowContainer, ChangeInfo> changes)599 static ArraySet<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants, 600 ArrayMap<WindowContainer, ChangeInfo> changes) { 601 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 602 "Start calculating TransitionInfo based on participants: %s", participants); 603 604 final ArraySet<WindowContainer> topTargets = new ArraySet<>(); 605 // The final animation targets which cannot promote to higher level anymore. 606 final ArraySet<WindowContainer> targets = new ArraySet<>(); 607 608 final ArrayList<WindowContainer> tmpList = new ArrayList<>(); 609 610 // Build initial set of top-level participants by removing any participants that are no-ops 611 // or children of other participants or are otherwise invalid; however, keep around a list 612 // of participants that should always be reported even if they aren't top. 613 for (WindowContainer wc : participants) { 614 // Don't include detached windows. 615 if (!wc.isAttached()) continue; 616 617 final ChangeInfo changeInfo = changes.get(wc); 618 619 // Reject no-ops 620 if (!changeInfo.hasChanged(wc)) { 621 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 622 " Rejecting as no-op: %s", wc); 623 continue; 624 } 625 626 // Search through ancestors to find the top-most participant (if one exists) 627 WindowContainer topParent = null; 628 tmpList.clear(); 629 if (reportIfNotTop(wc)) { 630 tmpList.add(wc); 631 } 632 for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) { 633 if (!p.isAttached() || !changes.get(p).hasChanged(p)) { 634 // Again, we're skipping no-ops 635 break; 636 } 637 if (participants.contains(p)) { 638 topParent = p; 639 break; 640 } else if (reportIfNotTop(p)) { 641 tmpList.add(p); 642 } 643 } 644 if (topParent != null) { 645 // There was an ancestor participant, so don't add wc to targets unless always- 646 // report. Similarly, add any always-report parents along the way. 647 for (int i = 0; i < tmpList.size(); ++i) { 648 targets.add(tmpList.get(i)); 649 final ChangeInfo info = changes.get(tmpList.get(i)); 650 info.mParent = i < tmpList.size() - 1 ? tmpList.get(i + 1) : topParent; 651 } 652 continue; 653 } 654 // No ancestors in participant-list, so wc is a top target. 655 targets.add(wc); 656 topTargets.add(wc); 657 } 658 659 // Populate children lists 660 for (int i = targets.size() - 1; i >= 0; --i) { 661 if (changes.get(targets.valueAt(i)).mParent != null) { 662 changes.get(changes.get(targets.valueAt(i)).mParent).addChild(targets.valueAt(i)); 663 } 664 } 665 666 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Initial targets: %s", targets); 667 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Top targets: %s", topTargets); 668 669 // Combine targets by repeatedly going through the topTargets to see if they can be 670 // promoted until there aren't any promotions possible. 671 while (tryPromote(topTargets, targets, changes)) { 672 // Empty on purpose 673 } 674 return targets; 675 } 676 677 /** Add any of `members` within `root` to `out` in top-to-bottom z-order. */ addMembersInOrder(WindowContainer root, ArraySet<WindowContainer> members, ArrayList<WindowContainer> out)678 private static void addMembersInOrder(WindowContainer root, ArraySet<WindowContainer> members, 679 ArrayList<WindowContainer> out) { 680 for (int i = root.getChildCount() - 1; i >= 0; --i) { 681 final WindowContainer child = root.getChildAt(i); 682 addMembersInOrder(child, members, out); 683 if (members.contains(child)) { 684 out.add(child); 685 } 686 } 687 } 688 689 /** Gets the leash surface for a window container */ getLeashSurface(WindowContainer wc)690 private static SurfaceControl getLeashSurface(WindowContainer wc) { 691 final DisplayContent asDC = wc.asDisplayContent(); 692 if (asDC != null) { 693 // DisplayContent is the "root", so we use the windowing layer instead to avoid 694 // hardware-screen-level surfaces. 695 return asDC.getWindowingLayer(); 696 } 697 return wc.getSurfaceControl(); 698 } 699 getOrigParentSurface(WindowContainer wc)700 private static SurfaceControl getOrigParentSurface(WindowContainer wc) { 701 if (wc.asDisplayContent() != null) { 702 // DisplayContent is the "root", so we reinterpret it's wc as the window layer 703 // making the parent surface the displaycontent's surface. 704 return wc.getSurfaceControl(); 705 } 706 return wc.getParent().getSurfaceControl(); 707 } 708 709 /** 710 * Construct a TransitionInfo object from a set of targets and changes. Also populates the 711 * root surface. 712 */ 713 @VisibleForTesting 714 @NonNull calculateTransitionInfo(int type, int flags, ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes)715 static TransitionInfo calculateTransitionInfo(int type, int flags, 716 ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) { 717 final TransitionInfo out = new TransitionInfo(type, flags); 718 719 final ArraySet<WindowContainer> appTargets = new ArraySet<>(); 720 final ArraySet<WindowContainer> wallpapers = new ArraySet<>(); 721 for (int i = targets.size() - 1; i >= 0; --i) { 722 (isWallpaper(targets.valueAt(i)) ? wallpapers : appTargets).add(targets.valueAt(i)); 723 } 724 725 // Find the top-most shared ancestor of app targets 726 WindowContainer ancestor = null; 727 for (int i = appTargets.size() - 1; i >= 0; --i) { 728 final WindowContainer wc = appTargets.valueAt(i); 729 ancestor = wc; 730 break; 731 } 732 if (ancestor == null) { 733 out.setRootLeash(new SurfaceControl(), 0, 0); 734 return out; 735 } 736 ancestor = ancestor.getParent(); 737 738 // Go up ancestor parent chain until all targets are descendants. 739 ancestorLoop: 740 while (ancestor != null) { 741 for (int i = appTargets.size() - 1; i >= 0; --i) { 742 final WindowContainer wc = appTargets.valueAt(i); 743 if (!wc.isDescendantOf(ancestor)) { 744 ancestor = ancestor.getParent(); 745 continue ancestorLoop; 746 } 747 } 748 break; 749 } 750 751 // Sort targets top-to-bottom in Z. Check ALL targets here in case the display area itself 752 // is animating: then we want to include wallpapers at the right position. 753 ArrayList<WindowContainer> sortedTargets = new ArrayList<>(); 754 addMembersInOrder(ancestor, targets, sortedTargets); 755 756 // make leash based on highest (z-order) direct child of ancestor with a participant. 757 WindowContainer leashReference = sortedTargets.get(0); 758 while (leashReference.getParent() != ancestor) { 759 leashReference = leashReference.getParent(); 760 } 761 final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName( 762 "Transition Root: " + leashReference.getName()).build(); 763 SurfaceControl.Transaction t = ancestor.mWmService.mTransactionFactory.get(); 764 t.setLayer(rootLeash, leashReference.getLastLayer()); 765 t.apply(); 766 t.close(); 767 out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top); 768 769 // add the wallpapers at the bottom 770 for (int i = wallpapers.size() - 1; i >= 0; --i) { 771 final WindowContainer wc = wallpapers.valueAt(i); 772 // If the displayarea itself is animating, then the wallpaper was already added. 773 if (wc.isDescendantOf(ancestor)) break; 774 sortedTargets.add(wc); 775 } 776 777 // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order. 778 final int count = sortedTargets.size(); 779 for (int i = 0; i < count; ++i) { 780 final WindowContainer target = sortedTargets.get(i); 781 final ChangeInfo info = changes.get(target); 782 final TransitionInfo.Change change = new TransitionInfo.Change( 783 target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken() 784 : null, getLeashSurface(target)); 785 // TODO(shell-transitions): Use leash for non-organized windows. 786 if (info.mParent != null) { 787 change.setParent(info.mParent.mRemoteToken.toWindowContainerToken()); 788 } 789 change.setMode(info.getTransitMode(target)); 790 change.setStartAbsBounds(info.mAbsoluteBounds); 791 change.setEndAbsBounds(target.getBounds()); 792 change.setEndRelOffset(target.getBounds().left - target.getParent().getBounds().left, 793 target.getBounds().top - target.getParent().getBounds().top); 794 change.setFlags(info.getChangeFlags(target)); 795 change.setRotation(info.mRotation, target.getWindowConfiguration().getRotation()); 796 final Task task = target.asTask(); 797 if (task != null) { 798 final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo(); 799 task.fillTaskInfo(tinfo); 800 change.setTaskInfo(tinfo); 801 } 802 out.addChange(change); 803 } 804 805 return out; 806 } 807 fromBinder(IBinder binder)808 static Transition fromBinder(IBinder binder) { 809 return (Transition) binder; 810 } 811 812 @VisibleForTesting 813 static class ChangeInfo { 814 // Usually "post" change state. 815 WindowContainer mParent; 816 ArraySet<WindowContainer> mChildren; 817 818 // State tracking 819 boolean mExistenceChanged = false; 820 // before change state 821 boolean mVisible; 822 int mWindowingMode; 823 final Rect mAbsoluteBounds = new Rect(); 824 boolean mShowWallpaper; 825 int mRotation = ROTATION_UNDEFINED; 826 ChangeInfo(@onNull WindowContainer origState)827 ChangeInfo(@NonNull WindowContainer origState) { 828 mVisible = origState.isVisibleRequested(); 829 mWindowingMode = origState.getWindowingMode(); 830 mAbsoluteBounds.set(origState.getBounds()); 831 mShowWallpaper = origState.showWallpaper(); 832 mRotation = origState.getWindowConfiguration().getRotation(); 833 } 834 835 @VisibleForTesting ChangeInfo(boolean visible, boolean existChange)836 ChangeInfo(boolean visible, boolean existChange) { 837 mVisible = visible; 838 mExistenceChanged = existChange; 839 mShowWallpaper = false; 840 } 841 hasChanged(@onNull WindowContainer newState)842 boolean hasChanged(@NonNull WindowContainer newState) { 843 // If it's invisible and hasn't changed visibility, always return false since even if 844 // something changed, it wouldn't be a visible change. 845 final boolean currVisible = newState.isVisibleRequested(); 846 if (currVisible == mVisible && !mVisible) return false; 847 return currVisible != mVisible 848 // if mWindowingMode is 0, this container wasn't attached at collect time, so 849 // assume no change in windowing-mode. 850 || (mWindowingMode != 0 && newState.getWindowingMode() != mWindowingMode) 851 || !newState.getBounds().equals(mAbsoluteBounds) 852 || mRotation != newState.getWindowConfiguration().getRotation(); 853 } 854 855 @TransitionInfo.TransitionMode getTransitMode(@onNull WindowContainer wc)856 int getTransitMode(@NonNull WindowContainer wc) { 857 final boolean nowVisible = wc.isVisibleRequested(); 858 if (nowVisible == mVisible) { 859 return TRANSIT_CHANGE; 860 } 861 if (mExistenceChanged) { 862 return nowVisible ? TRANSIT_OPEN : TRANSIT_CLOSE; 863 } else { 864 return nowVisible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK; 865 } 866 } 867 868 @TransitionInfo.ChangeFlags getChangeFlags(@onNull WindowContainer wc)869 int getChangeFlags(@NonNull WindowContainer wc) { 870 int flags = 0; 871 if (mShowWallpaper || wc.showWallpaper()) { 872 flags |= FLAG_SHOW_WALLPAPER; 873 } 874 if (!wc.fillsParent()) { 875 // TODO(b/172695805): hierarchical check. This is non-trivial because for containers 876 // it is effected by child visibility but needs to work even 877 // before visibility is committed. This means refactoring some 878 // checks to use requested visibility. 879 flags |= FLAG_TRANSLUCENT; 880 } 881 final Task task = wc.asTask(); 882 if (task != null && task.voiceSession != null) { 883 flags |= FLAG_IS_VOICE_INTERACTION; 884 } 885 final ActivityRecord record = wc.asActivityRecord(); 886 if (record != null) { 887 if (record.mUseTransferredAnimation) { 888 flags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; 889 } 890 if (record.mVoiceInteraction) { 891 flags |= FLAG_IS_VOICE_INTERACTION; 892 } 893 } 894 if (isWallpaper(wc)) { 895 flags |= FLAG_IS_WALLPAPER; 896 } 897 return flags; 898 } 899 addChild(@onNull WindowContainer wc)900 void addChild(@NonNull WindowContainer wc) { 901 if (mChildren == null) { 902 mChildren = new ArraySet<>(); 903 } 904 mChildren.add(wc); 905 } addChildren(@onNull ArraySet<WindowContainer> wcs)906 void addChildren(@NonNull ArraySet<WindowContainer> wcs) { 907 if (mChildren == null) { 908 mChildren = new ArraySet<>(); 909 } 910 mChildren.addAll(wcs); 911 } 912 } 913 } 914