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.view.WindowManager.TRANSIT_CHANGE; 20 import static android.view.WindowManager.TRANSIT_CLOSE; 21 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; 22 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; 23 import static android.view.WindowManager.TRANSIT_NONE; 24 import static android.view.WindowManager.TRANSIT_OPEN; 25 26 import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY; 27 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.app.ActivityManager; 31 import android.app.IApplicationThread; 32 import android.app.WindowConfiguration; 33 import android.os.IBinder; 34 import android.os.IRemoteCallback; 35 import android.os.RemoteException; 36 import android.os.SystemClock; 37 import android.os.SystemProperties; 38 import android.util.ArrayMap; 39 import android.util.Slog; 40 import android.util.proto.ProtoOutputStream; 41 import android.view.SurfaceControl; 42 import android.view.WindowManager; 43 import android.window.ITransitionMetricsReporter; 44 import android.window.ITransitionPlayer; 45 import android.window.RemoteTransition; 46 import android.window.TransitionInfo; 47 import android.window.TransitionRequestInfo; 48 49 import com.android.internal.annotations.GuardedBy; 50 import com.android.internal.annotations.VisibleForTesting; 51 import com.android.internal.protolog.ProtoLogGroup; 52 import com.android.internal.protolog.common.ProtoLog; 53 import com.android.server.LocalServices; 54 import com.android.server.statusbar.StatusBarManagerInternal; 55 56 import java.util.ArrayList; 57 import java.util.function.LongConsumer; 58 59 /** 60 * Handles all the aspects of recording and synchronizing transitions. 61 */ 62 class TransitionController { 63 private static final String TAG = "TransitionController"; 64 65 /** Whether to use shell-transitions rotation instead of fixed-rotation. */ 66 private static final boolean SHELL_TRANSITIONS_ROTATION = 67 SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false); 68 69 /** Which sync method to use for transition syncs. */ 70 static final int SYNC_METHOD = 71 android.os.SystemProperties.getBoolean("persist.wm.debug.shell_transit_blast", false) 72 ? BLASTSyncEngine.METHOD_BLAST : BLASTSyncEngine.METHOD_NONE; 73 74 /** The same as legacy APP_TRANSITION_TIMEOUT_MS. */ 75 private static final int DEFAULT_TIMEOUT_MS = 5000; 76 /** Less duration for CHANGE type because it does not involve app startup. */ 77 private static final int CHANGE_TIMEOUT_MS = 2000; 78 79 // State constants to line-up with legacy app-transition proto expectations. 80 private static final int LEGACY_STATE_IDLE = 0; 81 private static final int LEGACY_STATE_READY = 1; 82 private static final int LEGACY_STATE_RUNNING = 2; 83 84 private ITransitionPlayer mTransitionPlayer; 85 final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter(); 86 final TransitionTracer mTransitionTracer; 87 88 private WindowProcessController mTransitionPlayerProc; 89 final ActivityTaskManagerService mAtm; 90 final TaskSnapshotController mTaskSnapshotController; 91 final RemotePlayer mRemotePlayer; 92 93 private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners = 94 new ArrayList<>(); 95 96 /** 97 * Currently playing transitions (in the order they were started). When finished, records are 98 * removed from this list. 99 */ 100 private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>(); 101 102 final Lock mRunningLock = new Lock(); 103 104 private final IBinder.DeathRecipient mTransitionPlayerDeath; 105 106 /** The transition currently being constructed (collecting participants). */ 107 private Transition mCollectingTransition = null; 108 109 // TODO(b/188595497): remove when not needed. 110 final StatusBarManagerInternal mStatusBar; 111 112 /** 113 * `true` when building surface layer order for the finish transaction. We want to prevent 114 * wm from touching z-order of surfaces during transitions, but we still need to be able to 115 * calculate the layers for the finishTransaction. So, when assigning layers into the finish 116 * transaction, set this to true so that the {@link canAssignLayers} will allow it. 117 */ 118 boolean mBuildingFinishLayers = false; 119 120 private final SurfaceControl.Transaction mWakeT = new SurfaceControl.Transaction(); 121 TransitionController(ActivityTaskManagerService atm, TaskSnapshotController taskSnapshotController, TransitionTracer transitionTracer)122 TransitionController(ActivityTaskManagerService atm, 123 TaskSnapshotController taskSnapshotController, 124 TransitionTracer transitionTracer) { 125 mAtm = atm; 126 mRemotePlayer = new RemotePlayer(atm); 127 mStatusBar = LocalServices.getService(StatusBarManagerInternal.class); 128 mTaskSnapshotController = taskSnapshotController; 129 mTransitionTracer = transitionTracer; 130 mTransitionPlayerDeath = () -> { 131 synchronized (mAtm.mGlobalLock) { 132 detachPlayer(); 133 } 134 }; 135 } 136 detachPlayer()137 private void detachPlayer() { 138 if (mTransitionPlayer == null) return; 139 // Clean-up/finish any playing transitions. 140 for (int i = 0; i < mPlayingTransitions.size(); ++i) { 141 mPlayingTransitions.get(i).cleanUpOnFailure(); 142 } 143 mPlayingTransitions.clear(); 144 if (mCollectingTransition != null) { 145 mCollectingTransition.abort(); 146 } 147 mTransitionPlayer = null; 148 mTransitionPlayerProc = null; 149 mRemotePlayer.clear(); 150 mRunningLock.doNotifyLocked(); 151 } 152 153 /** @see #createTransition(int, int) */ 154 @NonNull createTransition(int type)155 Transition createTransition(int type) { 156 return createTransition(type, 0 /* flags */); 157 } 158 159 /** 160 * Creates a transition. It can immediately collect participants. 161 */ 162 @NonNull createTransition(@indowManager.TransitionType int type, @WindowManager.TransitionFlags int flags)163 private Transition createTransition(@WindowManager.TransitionType int type, 164 @WindowManager.TransitionFlags int flags) { 165 if (mTransitionPlayer == null) { 166 throw new IllegalStateException("Shell Transitions not enabled"); 167 } 168 if (mCollectingTransition != null) { 169 throw new IllegalStateException("Simultaneous transition collection not supported" 170 + " yet. Use {@link #createPendingTransition} for explicit queueing."); 171 } 172 Transition transit = new Transition(type, flags, this, mAtm.mWindowManager.mSyncEngine); 173 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s", transit); 174 moveToCollecting(transit); 175 return transit; 176 } 177 178 /** Starts Collecting */ moveToCollecting(@onNull Transition transition)179 void moveToCollecting(@NonNull Transition transition) { 180 moveToCollecting(transition, SYNC_METHOD); 181 } 182 183 /** Starts Collecting */ 184 @VisibleForTesting moveToCollecting(@onNull Transition transition, int method)185 void moveToCollecting(@NonNull Transition transition, int method) { 186 if (mCollectingTransition != null) { 187 throw new IllegalStateException("Simultaneous transition collection not supported."); 188 } 189 mCollectingTransition = transition; 190 // Distinguish change type because the response time is usually expected to be not too long. 191 final long timeoutMs = 192 transition.mType == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS; 193 mCollectingTransition.startCollecting(timeoutMs, method); 194 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s", 195 mCollectingTransition); 196 dispatchLegacyAppTransitionPending(); 197 } 198 registerTransitionPlayer(@ullable ITransitionPlayer player, @Nullable WindowProcessController playerProc)199 void registerTransitionPlayer(@Nullable ITransitionPlayer player, 200 @Nullable WindowProcessController playerProc) { 201 try { 202 // Note: asBinder() can be null if player is same process (likely in a test). 203 if (mTransitionPlayer != null) { 204 if (mTransitionPlayer.asBinder() != null) { 205 mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0); 206 } 207 detachPlayer(); 208 } 209 if (player.asBinder() != null) { 210 player.asBinder().linkToDeath(mTransitionPlayerDeath, 0); 211 } 212 mTransitionPlayer = player; 213 mTransitionPlayerProc = playerProc; 214 } catch (RemoteException e) { 215 throw new RuntimeException("Unable to set transition player"); 216 } 217 } 218 getTransitionPlayer()219 @Nullable ITransitionPlayer getTransitionPlayer() { 220 return mTransitionPlayer; 221 } 222 isShellTransitionsEnabled()223 boolean isShellTransitionsEnabled() { 224 return mTransitionPlayer != null; 225 } 226 227 /** @return {@code true} if using shell-transitions rotation instead of fixed-rotation. */ useShellTransitionsRotation()228 boolean useShellTransitionsRotation() { 229 return isShellTransitionsEnabled() && SHELL_TRANSITIONS_ROTATION; 230 } 231 232 /** 233 * @return {@code true} if transition is actively collecting changes. This is {@code false} 234 * once a transition is playing 235 */ isCollecting()236 boolean isCollecting() { 237 return mCollectingTransition != null; 238 } 239 240 /** 241 * @return the collecting transition. {@code null} if there is no collecting transition. 242 */ 243 @Nullable getCollectingTransition()244 Transition getCollectingTransition() { 245 return mCollectingTransition; 246 } 247 248 /** 249 * @return the collecting transition sync Id. This should only be called when there is a 250 * collecting transition. 251 */ getCollectingTransitionId()252 int getCollectingTransitionId() { 253 if (mCollectingTransition == null) { 254 throw new IllegalStateException("There is no collecting transition"); 255 } 256 return mCollectingTransition.getSyncId(); 257 } 258 259 /** 260 * @return {@code true} if transition is actively collecting changes and `wc` is one of them. 261 * This is {@code false} once a transition is playing. 262 */ isCollecting(@onNull WindowContainer wc)263 boolean isCollecting(@NonNull WindowContainer wc) { 264 return mCollectingTransition != null && mCollectingTransition.mParticipants.contains(wc); 265 } 266 267 /** 268 * @return {@code true} if transition is actively collecting changes and `wc` is one of them 269 * or a descendant of one of them. {@code false} once playing. 270 */ inCollectingTransition(@onNull WindowContainer wc)271 boolean inCollectingTransition(@NonNull WindowContainer wc) { 272 if (!isCollecting()) return false; 273 return mCollectingTransition.isInTransition(wc); 274 } 275 276 /** 277 * @return {@code true} if transition is actively playing. This is not necessarily {@code true} 278 * during collection. 279 */ isPlaying()280 boolean isPlaying() { 281 return !mPlayingTransitions.isEmpty(); 282 } 283 284 /** 285 * @return {@code true} if one of the playing transitions contains `wc`. 286 */ inPlayingTransition(@onNull WindowContainer wc)287 boolean inPlayingTransition(@NonNull WindowContainer wc) { 288 for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { 289 if (mPlayingTransitions.get(i).isInTransition(wc)) return true; 290 } 291 return false; 292 } 293 294 /** @return {@code true} if a transition is running */ inTransition()295 boolean inTransition() { 296 // TODO(shell-transitions): eventually properly support multiple 297 return isCollecting() || isPlaying(); 298 } 299 300 /** @return {@code true} if a transition is running in a participant subtree of wc */ inTransition(@onNull WindowContainer wc)301 boolean inTransition(@NonNull WindowContainer wc) { 302 return inCollectingTransition(wc) || inPlayingTransition(wc); 303 } 304 inRecentsTransition(@onNull WindowContainer wc)305 boolean inRecentsTransition(@NonNull WindowContainer wc) { 306 for (WindowContainer p = wc; p != null; p = p.getParent()) { 307 // TODO(b/221417431): replace this with deterministic snapshots 308 if (mCollectingTransition == null) break; 309 if ((mCollectingTransition.getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0 310 && mCollectingTransition.mParticipants.contains(wc)) { 311 return true; 312 } 313 } 314 315 for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { 316 for (WindowContainer p = wc; p != null; p = p.getParent()) { 317 // TODO(b/221417431): replace this with deterministic snapshots 318 if ((mPlayingTransitions.get(i).getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0 319 && mPlayingTransitions.get(i).mParticipants.contains(p)) { 320 return true; 321 } 322 } 323 } 324 return false; 325 } 326 327 /** @return {@code true} if wc is in a participant subtree */ isTransitionOnDisplay(@onNull DisplayContent dc)328 boolean isTransitionOnDisplay(@NonNull DisplayContent dc) { 329 if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) { 330 return true; 331 } 332 for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { 333 if (mPlayingTransitions.get(i).isOnDisplay(dc)) return true; 334 } 335 return false; 336 } 337 isTransientHide(@onNull Task task)338 boolean isTransientHide(@NonNull Task task) { 339 if (mCollectingTransition != null && mCollectingTransition.isTransientHide(task)) { 340 return true; 341 } 342 for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { 343 if (mPlayingTransitions.get(i).isTransientHide(task)) return true; 344 } 345 return false; 346 } 347 348 /** 349 * @return {@code true} if {@param ar} is part of a transient-launch activity in an active 350 * transition. 351 */ isTransientLaunch(@onNull ActivityRecord ar)352 boolean isTransientLaunch(@NonNull ActivityRecord ar) { 353 if (mCollectingTransition != null && mCollectingTransition.isTransientLaunch(ar)) { 354 return true; 355 } 356 for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { 357 if (mPlayingTransitions.get(i).isTransientLaunch(ar)) return true; 358 } 359 return false; 360 } 361 362 /** 363 * Whether WM can assign layers to window surfaces at this time. This is usually false while 364 * playing, but can be "opened-up" for certain transition operations like calculating layers 365 * for finishTransaction. 366 */ canAssignLayers()367 boolean canAssignLayers() { 368 return mBuildingFinishLayers || !isPlaying(); 369 } 370 371 @WindowConfiguration.WindowingMode getWindowingModeAtStart(@onNull WindowContainer wc)372 int getWindowingModeAtStart(@NonNull WindowContainer wc) { 373 if (mCollectingTransition == null) return wc.getWindowingMode(); 374 final Transition.ChangeInfo ci = mCollectingTransition.mChanges.get(wc); 375 if (ci == null) { 376 // not part of transition, so use current state. 377 return wc.getWindowingMode(); 378 } 379 return ci.mWindowingMode; 380 } 381 382 @WindowManager.TransitionType getCollectingTransitionType()383 int getCollectingTransitionType() { 384 return mCollectingTransition != null ? mCollectingTransition.mType : TRANSIT_NONE; 385 } 386 387 /** 388 * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition) 389 */ 390 @Nullable requestTransitionIfNeeded(@indowManager.TransitionType int type, @NonNull WindowContainer trigger)391 Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type, 392 @NonNull WindowContainer trigger) { 393 return requestTransitionIfNeeded(type, 0 /* flags */, trigger, trigger /* readyGroupRef */); 394 } 395 396 /** 397 * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition) 398 */ 399 @Nullable requestTransitionIfNeeded(@indowManager.TransitionType int type, @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger, @NonNull WindowContainer readyGroupRef)400 Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type, 401 @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger, 402 @NonNull WindowContainer readyGroupRef) { 403 return requestTransitionIfNeeded(type, flags, trigger, readyGroupRef, 404 null /* remoteTransition */, null /* displayChange */); 405 } 406 isExistenceType(@indowManager.TransitionType int type)407 private static boolean isExistenceType(@WindowManager.TransitionType int type) { 408 return type == TRANSIT_OPEN || type == TRANSIT_CLOSE; 409 } 410 411 /** 412 * If a transition isn't requested yet, creates one and asks the TransitionPlayer (Shell) to 413 * start it. Collection can start immediately. 414 * @param trigger if non-null, this is the first container that will be collected 415 * @param readyGroupRef Used to identify which ready-group this request is for. 416 * @return the created transition if created or null otherwise. 417 */ 418 @Nullable requestTransitionIfNeeded(@indowManager.TransitionType int type, @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger, @NonNull WindowContainer readyGroupRef, @Nullable RemoteTransition remoteTransition, @Nullable TransitionRequestInfo.DisplayChange displayChange)419 Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type, 420 @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger, 421 @NonNull WindowContainer readyGroupRef, @Nullable RemoteTransition remoteTransition, 422 @Nullable TransitionRequestInfo.DisplayChange displayChange) { 423 if (mTransitionPlayer == null) { 424 return null; 425 } 426 Transition newTransition = null; 427 if (isCollecting()) { 428 if (displayChange != null) { 429 throw new IllegalArgumentException("Provided displayChange for a non-new request"); 430 } 431 // Make the collecting transition wait until this request is ready. 432 mCollectingTransition.setReady(readyGroupRef, false); 433 if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) { 434 // Add keyguard flag to dismiss keyguard 435 mCollectingTransition.addFlag(flags); 436 } 437 } else { 438 newTransition = requestStartTransition(createTransition(type, flags), 439 trigger != null ? trigger.asTask() : null, remoteTransition, displayChange); 440 } 441 if (trigger != null) { 442 if (isExistenceType(type)) { 443 collectExistenceChange(trigger); 444 } else { 445 collect(trigger); 446 } 447 } 448 return newTransition; 449 } 450 451 /** Asks the transition player (shell) to start a created but not yet started transition. */ 452 @NonNull requestStartTransition(@onNull Transition transition, @Nullable Task startTask, @Nullable RemoteTransition remoteTransition, @Nullable TransitionRequestInfo.DisplayChange displayChange)453 Transition requestStartTransition(@NonNull Transition transition, @Nullable Task startTask, 454 @Nullable RemoteTransition remoteTransition, 455 @Nullable TransitionRequestInfo.DisplayChange displayChange) { 456 try { 457 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, 458 "Requesting StartTransition: %s", transition); 459 ActivityManager.RunningTaskInfo info = null; 460 if (startTask != null) { 461 info = new ActivityManager.RunningTaskInfo(); 462 startTask.fillTaskInfo(info); 463 } 464 mTransitionPlayer.requestStartTransition(transition.getToken(), 465 new TransitionRequestInfo(transition.mType, info, remoteTransition, 466 displayChange)); 467 transition.setRemoteTransition(remoteTransition); 468 } catch (RemoteException e) { 469 Slog.e(TAG, "Error requesting transition", e); 470 transition.start(); 471 } 472 return transition; 473 } 474 475 /** Requests transition for a window container which will be removed or invisible. */ requestCloseTransitionIfNeeded(@onNull WindowContainer<?> wc)476 void requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) { 477 if (mTransitionPlayer == null) return; 478 if (wc.isVisibleRequested()) { 479 if (!isCollecting()) { 480 requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */), 481 wc.asTask(), null /* remoteTransition */, null /* displayChange */); 482 } 483 collectExistenceChange(wc); 484 } else { 485 // Removing a non-visible window doesn't require a transition, but if there is one 486 // collecting, this should be a member just in case. 487 collect(wc); 488 } 489 } 490 491 /** @see Transition#collect */ collect(@onNull WindowContainer wc)492 void collect(@NonNull WindowContainer wc) { 493 if (mCollectingTransition == null) return; 494 mCollectingTransition.collect(wc); 495 } 496 497 /** @see Transition#collectExistenceChange */ collectExistenceChange(@onNull WindowContainer wc)498 void collectExistenceChange(@NonNull WindowContainer wc) { 499 if (mCollectingTransition == null) return; 500 mCollectingTransition.collectExistenceChange(wc); 501 } 502 503 /** 504 * Collects the window containers which need to be synced with the changing display area into 505 * the current collecting transition. 506 */ collectForDisplayAreaChange(@onNull DisplayArea<?> wc)507 void collectForDisplayAreaChange(@NonNull DisplayArea<?> wc) { 508 final Transition transition = mCollectingTransition; 509 if (transition == null || !transition.mParticipants.contains(wc)) return; 510 transition.collectVisibleChange(wc); 511 // Collect all visible tasks. 512 wc.forAllLeafTasks(task -> { 513 if (task.isVisible()) { 514 transition.collect(task); 515 } 516 }, true /* traverseTopToBottom */); 517 // Collect all visible non-app windows which need to be drawn before the animation starts. 518 final DisplayContent dc = wc.asDisplayContent(); 519 if (dc != null) { 520 final boolean noAsyncRotation = dc.getAsyncRotationController() == null; 521 wc.forAllWindows(w -> { 522 if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken) 523 && (noAsyncRotation || !AsyncRotationController.canBeAsync(w.mToken))) { 524 transition.collect(w.mToken); 525 } 526 }, true /* traverseTopToBottom */); 527 } 528 } 529 530 /** 531 * Records that a particular container is changing visibly (ie. something about it is changing 532 * while it remains visible). This only effects windows that are already in the collecting 533 * transition. 534 */ collectVisibleChange(WindowContainer wc)535 void collectVisibleChange(WindowContainer wc) { 536 if (!isCollecting()) return; 537 mCollectingTransition.collectVisibleChange(wc); 538 } 539 540 /** 541 * Records that a particular container has been reparented. This only effects windows that have 542 * already been collected in the transition. This should be called before reparenting because 543 * the old parent may be removed during reparenting, for example: 544 * {@link Task#shouldRemoveSelfOnLastChildRemoval} 545 */ collectReparentChange(@onNull WindowContainer wc, @NonNull WindowContainer newParent)546 void collectReparentChange(@NonNull WindowContainer wc, @NonNull WindowContainer newParent) { 547 if (!isCollecting()) return; 548 mCollectingTransition.collectReparentChange(wc, newParent); 549 } 550 551 /** @see Transition#mStatusBarTransitionDelay */ setStatusBarTransitionDelay(long delay)552 void setStatusBarTransitionDelay(long delay) { 553 if (mCollectingTransition == null) return; 554 mCollectingTransition.mStatusBarTransitionDelay = delay; 555 } 556 557 /** @see Transition#setOverrideAnimation */ setOverrideAnimation(TransitionInfo.AnimationOptions options, @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback)558 void setOverrideAnimation(TransitionInfo.AnimationOptions options, 559 @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) { 560 if (mCollectingTransition == null) return; 561 mCollectingTransition.setOverrideAnimation(options, startCallback, finishCallback); 562 } 563 564 /** @see Transition#setReady */ setReady(WindowContainer wc, boolean ready)565 void setReady(WindowContainer wc, boolean ready) { 566 if (mCollectingTransition == null) return; 567 mCollectingTransition.setReady(wc, ready); 568 } 569 570 /** @see Transition#setReady */ setReady(WindowContainer wc)571 void setReady(WindowContainer wc) { 572 setReady(wc, true); 573 } 574 575 /** @see Transition#deferTransitionReady */ deferTransitionReady()576 void deferTransitionReady() { 577 if (!isShellTransitionsEnabled()) return; 578 if (mCollectingTransition == null) { 579 throw new IllegalStateException("No collecting transition to defer readiness for."); 580 } 581 mCollectingTransition.deferTransitionReady(); 582 } 583 584 /** @see Transition#continueTransitionReady */ continueTransitionReady()585 void continueTransitionReady() { 586 if (!isShellTransitionsEnabled()) return; 587 if (mCollectingTransition == null) { 588 throw new IllegalStateException("No collecting transition to defer readiness for."); 589 } 590 mCollectingTransition.continueTransitionReady(); 591 } 592 593 /** @see Transition#finishTransition */ finishTransition(@onNull IBinder token)594 void finishTransition(@NonNull IBinder token) { 595 // It is usually a no-op but make sure that the metric consumer is removed. 596 mTransitionMetricsReporter.reportAnimationStart(token, 0 /* startTime */); 597 // It is a no-op if the transition did not change the display. 598 mAtm.endLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); 599 final Transition record = Transition.fromBinder(token); 600 if (record == null || !mPlayingTransitions.contains(record)) { 601 Slog.e(TAG, "Trying to finish a non-playing transition " + token); 602 return; 603 } 604 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record); 605 mPlayingTransitions.remove(record); 606 updateRunningRemoteAnimation(record, false /* isPlaying */); 607 record.finishTransition(); 608 mRunningLock.doNotifyLocked(); 609 } 610 moveToPlaying(Transition transition)611 void moveToPlaying(Transition transition) { 612 if (transition != mCollectingTransition) { 613 throw new IllegalStateException("Trying to move non-collecting transition to playing"); 614 } 615 mCollectingTransition = null; 616 mPlayingTransitions.add(transition); 617 updateRunningRemoteAnimation(transition, true /* isPlaying */); 618 mTransitionTracer.logState(transition); 619 } 620 621 /** Updates the process state of animation player. */ updateRunningRemoteAnimation(Transition transition, boolean isPlaying)622 private void updateRunningRemoteAnimation(Transition transition, boolean isPlaying) { 623 if (mTransitionPlayerProc == null) return; 624 if (isPlaying) { 625 mWakeT.setEarlyWakeupStart(); 626 mWakeT.apply(); 627 // Usually transitions put quite a load onto the system already (with all the things 628 // happening in app), so pause task snapshot persisting to not increase the load. 629 mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(true); 630 mTransitionPlayerProc.setRunningRemoteAnimation(true); 631 } else if (mPlayingTransitions.isEmpty()) { 632 mWakeT.setEarlyWakeupEnd(); 633 mWakeT.apply(); 634 mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(false); 635 mTransitionPlayerProc.setRunningRemoteAnimation(false); 636 mRemotePlayer.clear(); 637 return; 638 } 639 final RemoteTransition remote = transition.getRemoteTransition(); 640 if (remote == null) return; 641 final IApplicationThread appThread = remote.getAppThread(); 642 final WindowProcessController delegate = mAtm.getProcessController(appThread); 643 if (delegate == null) return; 644 mRemotePlayer.update(delegate, isPlaying, true /* predict */); 645 } 646 abort(Transition transition)647 void abort(Transition transition) { 648 if (transition != mCollectingTransition) { 649 throw new IllegalStateException("Too late to abort."); 650 } 651 transition.abort(); 652 mCollectingTransition = null; 653 mTransitionTracer.logState(transition); 654 } 655 656 /** 657 * Record that the launch of {@param activity} is transient (meaning its lifecycle is currently 658 * tied to the transition). 659 * @param restoreBelowTask If non-null, the activity's task will be ordered right below this 660 * task if requested. 661 */ setTransientLaunch(@onNull ActivityRecord activity, @Nullable Task restoreBelowTask)662 void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelowTask) { 663 if (mCollectingTransition == null) return; 664 mCollectingTransition.setTransientLaunch(activity, restoreBelowTask); 665 666 // TODO(b/188669821): Remove once legacy recents behavior is moved to shell. 667 // Also interpret HOME transient launch as recents 668 if (activity.isActivityTypeHomeOrRecents()) { 669 mCollectingTransition.addFlag(TRANSIT_FLAG_IS_RECENTS); 670 // When starting recents animation, we assume the recents activity is behind the app 671 // task and should not affect system bar appearance, 672 // until WMS#setRecentsAppBehindSystemBars be called from launcher when passing 673 // the gesture threshold. 674 activity.getTask().setCanAffectSystemUiFlags(false); 675 } 676 } 677 678 /** @see Transition#setCanPipOnFinish */ setCanPipOnFinish(boolean canPipOnFinish)679 void setCanPipOnFinish(boolean canPipOnFinish) { 680 if (mCollectingTransition == null) return; 681 mCollectingTransition.setCanPipOnFinish(canPipOnFinish); 682 } 683 legacyDetachNavigationBarFromApp(@onNull IBinder token)684 void legacyDetachNavigationBarFromApp(@NonNull IBinder token) { 685 final Transition transition = Transition.fromBinder(token); 686 if (transition == null || !mPlayingTransitions.contains(transition)) { 687 Slog.e(TAG, "Transition isn't playing: " + token); 688 return; 689 } 690 transition.legacyRestoreNavigationBarFromApp(); 691 } 692 registerLegacyListener(WindowManagerInternal.AppTransitionListener listener)693 void registerLegacyListener(WindowManagerInternal.AppTransitionListener listener) { 694 mLegacyListeners.add(listener); 695 } 696 unregisterLegacyListener(WindowManagerInternal.AppTransitionListener listener)697 void unregisterLegacyListener(WindowManagerInternal.AppTransitionListener listener) { 698 mLegacyListeners.remove(listener); 699 } 700 dispatchLegacyAppTransitionPending()701 void dispatchLegacyAppTransitionPending() { 702 for (int i = 0; i < mLegacyListeners.size(); ++i) { 703 mLegacyListeners.get(i).onAppTransitionPendingLocked(); 704 } 705 } 706 dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay)707 void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) { 708 final boolean keyguardGoingAway = info.isKeyguardGoingAway(); 709 for (int i = 0; i < mLegacyListeners.size(); ++i) { 710 // TODO(shell-transitions): handle (un)occlude transition. 711 mLegacyListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway, 712 false /* keyguardOcclude */, 0 /* durationHint */, 713 SystemClock.uptimeMillis() + statusBarTransitionDelay, 714 AnimationAdapter.STATUS_BAR_TRANSITION_DURATION); 715 } 716 } 717 dispatchLegacyAppTransitionFinished(ActivityRecord ar)718 void dispatchLegacyAppTransitionFinished(ActivityRecord ar) { 719 for (int i = 0; i < mLegacyListeners.size(); ++i) { 720 mLegacyListeners.get(i).onAppTransitionFinishedLocked(ar.token); 721 } 722 } 723 dispatchLegacyAppTransitionCancelled()724 void dispatchLegacyAppTransitionCancelled() { 725 for (int i = 0; i < mLegacyListeners.size(); ++i) { 726 mLegacyListeners.get(i).onAppTransitionCancelledLocked( 727 false /* keyguardGoingAway */); 728 } 729 } 730 dumpDebugLegacy(ProtoOutputStream proto, long fieldId)731 void dumpDebugLegacy(ProtoOutputStream proto, long fieldId) { 732 final long token = proto.start(fieldId); 733 int state = LEGACY_STATE_IDLE; 734 if (!mPlayingTransitions.isEmpty()) { 735 state = LEGACY_STATE_RUNNING; 736 } else if ((mCollectingTransition != null && mCollectingTransition.getLegacyIsReady()) 737 || mAtm.mWindowManager.mSyncEngine.hasPendingSyncSets()) { 738 // The transition may not be "ready", but we have a sync-transaction waiting to start. 739 // Usually the pending transaction is for a transition, so assuming that is the case, 740 // we can't be IDLE for test purposes. Ideally, we should have a STATE_COLLECTING. 741 state = LEGACY_STATE_READY; 742 } 743 proto.write(AppTransitionProto.APP_TRANSITION_STATE, state); 744 proto.end(token); 745 } 746 747 /** 748 * This manages the animating state of processes that are running remote animations for 749 * {@link #mTransitionPlayerProc}. 750 */ 751 static class RemotePlayer { 752 private static final long REPORT_RUNNING_GRACE_PERIOD_MS = 100; 753 @GuardedBy("itself") 754 private final ArrayMap<IBinder, DelegateProcess> mDelegateProcesses = new ArrayMap<>(); 755 private final ActivityTaskManagerService mAtm; 756 757 private class DelegateProcess implements Runnable { 758 final WindowProcessController mProc; 759 /** Requires {@link RemotePlayer#reportRunning} to confirm it is really running. */ 760 boolean mNeedReport; 761 DelegateProcess(WindowProcessController proc)762 DelegateProcess(WindowProcessController proc) { 763 mProc = proc; 764 } 765 766 /** This runs when the remote player doesn't report running in time. */ 767 @Override run()768 public void run() { 769 synchronized (mAtm.mGlobalLockWithoutBoost) { 770 update(mProc, false /* running */, false /* predict */); 771 } 772 } 773 } 774 RemotePlayer(ActivityTaskManagerService atm)775 RemotePlayer(ActivityTaskManagerService atm) { 776 mAtm = atm; 777 } 778 update(@onNull WindowProcessController delegate, boolean running, boolean predict)779 void update(@NonNull WindowProcessController delegate, boolean running, boolean predict) { 780 if (!running) { 781 synchronized (mDelegateProcesses) { 782 boolean removed = false; 783 for (int i = mDelegateProcesses.size() - 1; i >= 0; i--) { 784 if (mDelegateProcesses.valueAt(i).mProc == delegate) { 785 mDelegateProcesses.removeAt(i); 786 removed = true; 787 break; 788 } 789 } 790 if (!removed) return; 791 } 792 delegate.setRunningRemoteAnimation(false); 793 return; 794 } 795 if (delegate.isRunningRemoteTransition() || !delegate.hasThread()) return; 796 delegate.setRunningRemoteAnimation(true); 797 final DelegateProcess delegateProc = new DelegateProcess(delegate); 798 // If "predict" is true, that means the remote animation is set from 799 // ActivityOptions#makeRemoteAnimation(). But it is still up to shell side to decide 800 // whether to use the remote animation, so there is a timeout to cancel the prediction 801 // if the remote animation doesn't happen. 802 if (predict) { 803 delegateProc.mNeedReport = true; 804 mAtm.mH.postDelayed(delegateProc, REPORT_RUNNING_GRACE_PERIOD_MS); 805 } 806 synchronized (mDelegateProcesses) { 807 mDelegateProcesses.put(delegate.getThread().asBinder(), delegateProc); 808 } 809 } 810 clear()811 void clear() { 812 synchronized (mDelegateProcesses) { 813 for (int i = mDelegateProcesses.size() - 1; i >= 0; i--) { 814 mDelegateProcesses.valueAt(i).mProc.setRunningRemoteAnimation(false); 815 } 816 mDelegateProcesses.clear(); 817 } 818 } 819 820 /** Returns {@code true} if the app is known to be running remote transition. */ reportRunning(@onNull IApplicationThread appThread)821 boolean reportRunning(@NonNull IApplicationThread appThread) { 822 final DelegateProcess delegate; 823 synchronized (mDelegateProcesses) { 824 delegate = mDelegateProcesses.get(appThread.asBinder()); 825 if (delegate != null && delegate.mNeedReport) { 826 // It was predicted to run remote transition. Now it is really requesting so 827 // remove the timeout of restoration. 828 delegate.mNeedReport = false; 829 mAtm.mH.removeCallbacks(delegate); 830 } 831 } 832 return delegate != null; 833 } 834 } 835 836 static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub { 837 private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>(); 838 associate(IBinder transitionToken, LongConsumer consumer)839 void associate(IBinder transitionToken, LongConsumer consumer) { 840 synchronized (mMetricConsumers) { 841 mMetricConsumers.put(transitionToken, consumer); 842 } 843 } 844 845 @Override reportAnimationStart(IBinder transitionToken, long startTime)846 public void reportAnimationStart(IBinder transitionToken, long startTime) { 847 final LongConsumer c; 848 synchronized (mMetricConsumers) { 849 if (mMetricConsumers.isEmpty()) return; 850 c = mMetricConsumers.remove(transitionToken); 851 } 852 if (c != null) { 853 c.accept(startTime); 854 } 855 } 856 } 857 858 class Lock { 859 private int mTransitionWaiters = 0; runWhenIdle(long timeout, Runnable r)860 void runWhenIdle(long timeout, Runnable r) { 861 synchronized (mAtm.mGlobalLock) { 862 if (!inTransition()) { 863 r.run(); 864 return; 865 } 866 mTransitionWaiters += 1; 867 } 868 final long startTime = SystemClock.uptimeMillis(); 869 final long endTime = startTime + timeout; 870 while (true) { 871 synchronized (mAtm.mGlobalLock) { 872 if (!inTransition() || SystemClock.uptimeMillis() > endTime) { 873 mTransitionWaiters -= 1; 874 r.run(); 875 return; 876 } 877 } 878 synchronized (this) { 879 try { 880 this.wait(timeout); 881 } catch (InterruptedException e) { 882 return; 883 } 884 } 885 } 886 } 887 doNotifyLocked()888 void doNotifyLocked() { 889 synchronized (this) { 890 if (mTransitionWaiters > 0) { 891 this.notifyAll(); 892 } 893 } 894 } 895 } 896 } 897