1 /* 2 * Copyright (C) 2022 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.wm.shell.transition; 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.WINDOWING_MODE_FULLSCREEN; 22 import static android.view.Display.DEFAULT_DISPLAY; 23 import static android.view.WindowManager.TRANSIT_CHANGE; 24 import static android.view.WindowManager.TRANSIT_PIP; 25 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; 26 27 import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; 28 29 import android.annotation.NonNull; 30 import android.annotation.Nullable; 31 import android.app.PendingIntent; 32 import android.os.IBinder; 33 import android.util.ArrayMap; 34 import android.util.Pair; 35 import android.view.SurfaceControl; 36 import android.view.WindowManager; 37 import android.window.TransitionInfo; 38 import android.window.TransitionRequestInfo; 39 import android.window.WindowContainerToken; 40 import android.window.WindowContainerTransaction; 41 42 import com.android.internal.protolog.ProtoLog; 43 import com.android.wm.shell.ShellTaskOrganizer; 44 import com.android.wm.shell.activityembedding.ActivityEmbeddingController; 45 import com.android.wm.shell.common.ComponentUtils; 46 import com.android.wm.shell.desktopmode.DesktopTasksController; 47 import com.android.wm.shell.keyguard.KeyguardTransitionHandler; 48 import com.android.wm.shell.pip.PipTransitionController; 49 import com.android.wm.shell.protolog.ShellProtoLogGroup; 50 import com.android.wm.shell.recents.RecentsTransitionHandler; 51 import com.android.wm.shell.shared.TransitionUtil; 52 import com.android.wm.shell.splitscreen.SplitScreenController; 53 import com.android.wm.shell.splitscreen.StageCoordinator; 54 import com.android.wm.shell.sysui.ShellInit; 55 import com.android.wm.shell.unfold.UnfoldTransitionHandler; 56 57 import java.util.ArrayList; 58 import java.util.Map; 59 import java.util.Optional; 60 import java.util.function.Consumer; 61 62 /** 63 * A handler for dealing with transitions involving multiple other handlers. For example: an 64 * activity in split-screen going into PiP. Note this is provided as a handset-specific 65 * implementation of {@code MixedTransitionHandler}. 66 */ 67 public class DefaultMixedHandler implements MixedTransitionHandler, 68 RecentsTransitionHandler.RecentsMixedHandler { 69 70 private final Transitions mPlayer; 71 private PipTransitionController mPipHandler; 72 private RecentsTransitionHandler mRecentsHandler; 73 private StageCoordinator mSplitHandler; 74 private final KeyguardTransitionHandler mKeyguardHandler; 75 private DesktopTasksController mDesktopTasksController; 76 private UnfoldTransitionHandler mUnfoldHandler; 77 private ActivityEmbeddingController mActivityEmbeddingController; 78 79 abstract static class MixedTransition { 80 /** Entering Pip from split, breaks split. */ 81 static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; 82 83 /** Both the display and split-state (enter/exit) is changing */ 84 static final int TYPE_DISPLAY_AND_SPLIT_CHANGE = 2; 85 86 /** 87 * While handling an intent with its own remoteTransition, a PIP enter or Desktop immersive 88 * exit change is found. 89 */ 90 static final int TYPE_OPTIONS_REMOTE_AND_PIP_OR_DESKTOP_CHANGE = 3; 91 92 /** Recents transition while split-screen foreground. */ 93 static final int TYPE_RECENTS_DURING_SPLIT = 4; 94 95 /** Keyguard exit/occlude/unocclude transition. */ 96 static final int TYPE_KEYGUARD = 5; 97 98 /** Recents transition on top of the lock screen. */ 99 static final int TYPE_RECENTS_DURING_KEYGUARD = 6; 100 101 /** Recents Transition while in desktop mode. */ 102 static final int TYPE_RECENTS_DURING_DESKTOP = 7; 103 104 /** Fold/Unfold transition. */ 105 static final int TYPE_UNFOLD = 8; 106 107 /** Enter pip from one of the Activity Embedding windows. */ 108 static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 9; 109 110 /** Entering Pip from split, but replace the Pip stage instead of breaking split. */ 111 static final int TYPE_ENTER_PIP_REPLACE_FROM_SPLIT = 10; 112 113 /** The display changes when pip is entering. */ 114 static final int TYPE_ENTER_PIP_WITH_DISPLAY_CHANGE = 11; 115 116 /** Open transition during a desktop session. */ 117 static final int TYPE_OPEN_IN_DESKTOP = 12; 118 119 /** The default animation for this mixed transition. */ 120 static final int ANIM_TYPE_DEFAULT = 0; 121 122 /** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */ 123 static final int ANIM_TYPE_GOING_HOME = 1; 124 125 /** For RECENTS_DURING_SPLIT, is set when this turns into a pair->pair task switch. */ 126 static final int ANIM_TYPE_PAIR_TO_PAIR = 1; 127 128 final int mType; 129 int mAnimType = ANIM_TYPE_DEFAULT; 130 final IBinder mTransition; 131 132 protected final Transitions mPlayer; 133 protected final MixedTransitionHandler mMixedHandler; 134 protected final PipTransitionController mPipHandler; 135 protected final StageCoordinator mSplitHandler; 136 protected final KeyguardTransitionHandler mKeyguardHandler; 137 138 Transitions.TransitionHandler mLeftoversHandler = null; 139 TransitionInfo mInfo = null; 140 WindowContainerTransaction mFinishWCT = null; 141 SurfaceControl.Transaction mFinishT = null; 142 Transitions.TransitionFinishCallback mFinishCB = null; 143 144 /** 145 * Whether the transition has request for remote transition while mLeftoversHandler 146 * isn't remote transition handler. 147 * If true and the mLeftoversHandler can handle the transition, need to notify remote 148 * transition handler to consume the transition. 149 */ 150 boolean mHasRequestToRemote; 151 152 /** 153 * Mixed transitions are made up of multiple "parts". This keeps track of how many 154 * parts are currently animating. 155 */ 156 int mInFlightSubAnimations = 0; 157 MixedTransition(int type, IBinder transition, Transitions player, MixedTransitionHandler mixedHandler, PipTransitionController pipHandler, StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler)158 MixedTransition(int type, IBinder transition, Transitions player, 159 MixedTransitionHandler mixedHandler, PipTransitionController pipHandler, 160 StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler) { 161 mType = type; 162 mTransition = transition; 163 mPlayer = player; 164 mMixedHandler = mixedHandler; 165 mPipHandler = pipHandler; 166 mSplitHandler = splitHandler; 167 mKeyguardHandler = keyguardHandler; 168 } 169 startAnimation( @onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)170 abstract boolean startAnimation( 171 @NonNull IBinder transition, @NonNull TransitionInfo info, 172 @NonNull SurfaceControl.Transaction startTransaction, 173 @NonNull SurfaceControl.Transaction finishTransaction, 174 @NonNull Transitions.TransitionFinishCallback finishCallback); 175 mergeAnimation( @onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)176 abstract void mergeAnimation( 177 @NonNull IBinder transition, @NonNull TransitionInfo info, 178 @NonNull SurfaceControl.Transaction startT, 179 @NonNull SurfaceControl.Transaction finishT, 180 @NonNull IBinder mergeTarget, 181 @NonNull Transitions.TransitionFinishCallback finishCallback); 182 onTransitionConsumed( @onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)183 abstract void onTransitionConsumed( 184 @NonNull IBinder transition, boolean aborted, 185 @Nullable SurfaceControl.Transaction finishT); 186 startSubAnimation( Transitions.TransitionHandler handler, TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT)187 protected boolean startSubAnimation( 188 Transitions.TransitionHandler handler, TransitionInfo info, 189 SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { 190 if (mInfo != null) { 191 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 192 "startSubAnimation #%d.%d", mInfo.getDebugId(), info.getDebugId()); 193 } 194 mInFlightSubAnimations++; 195 if (!handler.startAnimation( 196 mTransition, info, startT, finishT, wct -> onSubAnimationFinished(info, wct))) { 197 mInFlightSubAnimations--; 198 return false; 199 } 200 return true; 201 } 202 onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct)203 private void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) { 204 mInFlightSubAnimations--; 205 if (mInfo != null) { 206 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 207 "onSubAnimationFinished #%d.%d remaining=%d", 208 mInfo.getDebugId(), info.getDebugId(), mInFlightSubAnimations); 209 } 210 211 joinFinishArgs(wct); 212 213 if (mInFlightSubAnimations == 0) { 214 mFinishCB.onTransitionFinished(mFinishWCT); 215 } 216 } 217 joinFinishArgs(WindowContainerTransaction wct)218 void joinFinishArgs(WindowContainerTransaction wct) { 219 if (wct != null) { 220 if (mFinishWCT == null) { 221 mFinishWCT = wct; 222 } else { 223 mFinishWCT.merge(wct, true /* transfer */); 224 } 225 } 226 } 227 } 228 229 private final ArrayList<MixedTransition> mActiveTransitions = new ArrayList<>(); 230 DefaultMixedHandler(@onNull ShellInit shellInit, @NonNull Transitions player, Optional<SplitScreenController> splitScreenControllerOptional, @Nullable PipTransitionController pipTransitionController, Optional<RecentsTransitionHandler> recentsHandlerOptional, KeyguardTransitionHandler keyguardHandler, Optional<DesktopTasksController> desktopTasksControllerOptional, Optional<UnfoldTransitionHandler> unfoldHandler, Optional<ActivityEmbeddingController> activityEmbeddingController)231 public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player, 232 Optional<SplitScreenController> splitScreenControllerOptional, 233 @Nullable PipTransitionController pipTransitionController, 234 Optional<RecentsTransitionHandler> recentsHandlerOptional, 235 KeyguardTransitionHandler keyguardHandler, 236 Optional<DesktopTasksController> desktopTasksControllerOptional, 237 Optional<UnfoldTransitionHandler> unfoldHandler, 238 Optional<ActivityEmbeddingController> activityEmbeddingController) { 239 mPlayer = player; 240 mKeyguardHandler = keyguardHandler; 241 if (Transitions.ENABLE_SHELL_TRANSITIONS 242 && pipTransitionController != null 243 && splitScreenControllerOptional.isPresent()) { 244 // Add after dependencies because it is higher priority 245 shellInit.addInitCallback(() -> { 246 mPipHandler = pipTransitionController; 247 pipTransitionController.setMixedHandler(this); 248 mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler(); 249 mPlayer.addHandler(this); 250 if (mSplitHandler != null) { 251 mSplitHandler.setMixedHandler(this); 252 } 253 mRecentsHandler = recentsHandlerOptional.orElse(null); 254 if (mRecentsHandler != null) { 255 mRecentsHandler.addMixer(this); 256 } 257 mDesktopTasksController = desktopTasksControllerOptional.orElse(null); 258 mUnfoldHandler = unfoldHandler.orElse(null); 259 mActivityEmbeddingController = activityEmbeddingController.orElse(null); 260 }, this); 261 } 262 } 263 264 @Nullable 265 @Override handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)266 public WindowContainerTransaction handleRequest(@NonNull IBinder transition, 267 @NonNull TransitionRequestInfo request) { 268 if (mSplitHandler.requestImpliesSplitToPip(request)) { 269 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while " 270 + "Split-Screen is active, so treat it as Mixed."); 271 if (request.getRemoteTransition() != null) { 272 throw new IllegalStateException("Unexpected remote transition in" 273 + "pip-enter-from-split request"); 274 } 275 mActiveTransitions.add(createDefaultMixedTransition( 276 MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition)); 277 278 WindowContainerTransaction out = new WindowContainerTransaction(); 279 mPipHandler.augmentRequest(transition, request, out); 280 mSplitHandler.addEnterOrExitIfNeeded(request, out); 281 return out; 282 } else if (request.getType() == TRANSIT_PIP 283 && (request.getFlags() & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0 && ( 284 mActivityEmbeddingController != null)) { 285 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 286 " Got a PiP-enter request from an Activity Embedding split"); 287 mActiveTransitions.add(createDefaultMixedTransition( 288 MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition)); 289 // Postpone transition splitting to later. 290 WindowContainerTransaction out = new WindowContainerTransaction(); 291 mPipHandler.augmentRequest(transition, request, out); 292 return out; 293 } else if (request.getRemoteTransition() != null 294 && TransitionUtil.isOpeningType(request.getType()) 295 && (request.getTriggerTask() == null 296 || (request.getTriggerTask().topActivityType != ACTIVITY_TYPE_HOME 297 && request.getTriggerTask().topActivityType != ACTIVITY_TYPE_RECENTS))) { 298 // Only select transitions with an intent-provided remote-animation because that will 299 // usually grab priority and often won't handle PiP. If there isn't an intent-provided 300 // remote, then the transition will be dispatched normally and the PipHandler will 301 // pick it up. 302 Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler = 303 mPlayer.dispatchRequest(transition, request, this); 304 if (handler == null) { 305 return null; 306 } 307 final MixedTransition mixed = createDefaultMixedTransition( 308 MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_OR_DESKTOP_CHANGE, transition); 309 mixed.mLeftoversHandler = handler.first; 310 mActiveTransitions.add(mixed); 311 if (mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) { 312 mixed.mHasRequestToRemote = true; 313 mPlayer.getRemoteTransitionHandler().handleRequest(transition, request); 314 } 315 return handler.second; 316 } else if (mSplitHandler.isSplitScreenVisible() 317 && isOpeningType(request.getType()) 318 && request.getTriggerTask() != null 319 && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN 320 && request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME) { 321 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a going-home request while " 322 + "Split-Screen is foreground, so treat it as Mixed."); 323 Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler = 324 mPlayer.dispatchRequest(transition, request, this); 325 if (handler == null) { 326 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 327 " Lean on the remote transition handler to fetch a proper remote via" 328 + " TransitionFilter"); 329 handler = new Pair<>( 330 mPlayer.getRemoteTransitionHandler(), 331 new WindowContainerTransaction()); 332 } 333 final MixedTransition mixed = createRecentsMixedTransition( 334 MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition); 335 mixed.mLeftoversHandler = handler.first; 336 mActiveTransitions.add(mixed); 337 return handler.second; 338 } else if (mUnfoldHandler != null && mUnfoldHandler.shouldPlayUnfoldAnimation(request)) { 339 final WindowContainerTransaction wct = 340 mUnfoldHandler.handleRequest(transition, request); 341 if (wct != null) { 342 mActiveTransitions.add(createDefaultMixedTransition( 343 MixedTransition.TYPE_UNFOLD, transition)); 344 } 345 return wct; 346 } else if (mDesktopTasksController != null 347 && mDesktopTasksController.shouldPlayDesktopAnimation(request)) { 348 final Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler = 349 mPlayer.dispatchRequest(transition, request, /* skip= */ this); 350 if (handler == null) { 351 return null; 352 } 353 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a desktop request, so" 354 + " treat it as Mixed. handler=%s", handler.first); 355 final MixedTransition mixed = createDefaultMixedTransition( 356 MixedTransition.TYPE_OPEN_IN_DESKTOP, transition); 357 mixed.mLeftoversHandler = handler.first; 358 mActiveTransitions.add(mixed); 359 return handler.second; 360 } 361 return null; 362 } 363 createDefaultMixedTransition(int type, IBinder transition)364 private DefaultMixedTransition createDefaultMixedTransition(int type, IBinder transition) { 365 return new DefaultMixedTransition( 366 type, transition, mPlayer, this, mPipHandler, mSplitHandler, mKeyguardHandler, 367 mUnfoldHandler, mActivityEmbeddingController, mDesktopTasksController); 368 } 369 370 @Override handleRecentsRequest()371 public Consumer<IBinder> handleRecentsRequest() { 372 if (mRecentsHandler != null) { 373 if (mSplitHandler.isSplitScreenVisible()) { 374 return this::setRecentsTransitionDuringSplit; 375 } else if (mKeyguardHandler.isKeyguardShowing() 376 && !mKeyguardHandler.isKeyguardAnimating()) { 377 return this::setRecentsTransitionDuringKeyguard; 378 } else if (mDesktopTasksController != null 379 // Check on the default display. Recents/gesture nav is only available there 380 && mDesktopTasksController.isAnyDeskActive(DEFAULT_DISPLAY)) { 381 return this::setRecentsTransitionDuringDesktop; 382 } 383 } 384 return null; 385 } 386 387 @Override handleFinishRecents(boolean returnToApp, @NonNull WindowContainerTransaction finishWct, @NonNull SurfaceControl.Transaction finishT)388 public void handleFinishRecents(boolean returnToApp, 389 @NonNull WindowContainerTransaction finishWct, 390 @NonNull SurfaceControl.Transaction finishT) { 391 if (mRecentsHandler != null) { 392 for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { 393 final MixedTransition mixed = mActiveTransitions.get(i); 394 if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { 395 ((RecentsMixedTransition) mixed).onAnimateRecentsDuringSplitFinishing( 396 returnToApp, finishWct, finishT); 397 } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) { 398 ((RecentsMixedTransition) mixed).onAnimateRecentsDuringDesktopFinishing( 399 returnToApp, finishWct); 400 } 401 } 402 } 403 } 404 setRecentsTransitionDuringSplit(IBinder transition)405 private void setRecentsTransitionDuringSplit(IBinder transition) { 406 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " 407 + "Split-Screen is foreground, so treat it as Mixed."); 408 mActiveTransitions.add(createRecentsMixedTransition( 409 MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition)); 410 } 411 setRecentsTransitionDuringKeyguard(IBinder transition)412 private void setRecentsTransitionDuringKeyguard(IBinder transition) { 413 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " 414 + "keyguard is visible, so treat it as Mixed."); 415 mActiveTransitions.add(createRecentsMixedTransition( 416 MixedTransition.TYPE_RECENTS_DURING_KEYGUARD, transition)); 417 } 418 setRecentsTransitionDuringDesktop(IBinder transition)419 private void setRecentsTransitionDuringDesktop(IBinder transition) { 420 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " 421 + "desktop mode is active, so treat it as Mixed."); 422 mActiveTransitions.add(createRecentsMixedTransition( 423 MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition)); 424 } 425 createRecentsMixedTransition(int type, IBinder transition)426 private MixedTransition createRecentsMixedTransition(int type, IBinder transition) { 427 return new RecentsMixedTransition(type, transition, mPlayer, this, mPipHandler, 428 mSplitHandler, mKeyguardHandler, mRecentsHandler, mDesktopTasksController); 429 } 430 subCopy(@onNull TransitionInfo info, @WindowManager.TransitionType int newType, boolean withChanges)431 static TransitionInfo subCopy(@NonNull TransitionInfo info, 432 @WindowManager.TransitionType int newType, boolean withChanges) { 433 final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0); 434 out.setTrack(info.getTrack()); 435 out.setDebugId(info.getDebugId()); 436 if (withChanges) { 437 for (int i = 0; i < info.getChanges().size(); ++i) { 438 out.getChanges().add(info.getChanges().get(i)); 439 } 440 } 441 for (int i = 0; i < info.getRootCount(); ++i) { 442 out.addRoot(info.getRoot(i)); 443 } 444 return out; 445 } 446 447 @Override startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)448 public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 449 @NonNull SurfaceControl.Transaction startTransaction, 450 @NonNull SurfaceControl.Transaction finishTransaction, 451 @NonNull Transitions.TransitionFinishCallback finishCallback) { 452 453 MixedTransition mixed = null; 454 for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { 455 if (mActiveTransitions.get(i).mTransition != transition) continue; 456 mixed = mActiveTransitions.get(i); 457 break; 458 } 459 460 // Offer Keyguard the opportunity to take over lock transitions - ideally we could know by 461 // the time of handleRequest, but we need more information than is available at that time. 462 if (KeyguardTransitionHandler.handles(info)) { 463 if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) { 464 final MixedTransition keyguardMixed = 465 createDefaultMixedTransition(MixedTransition.TYPE_KEYGUARD, transition); 466 mActiveTransitions.add(keyguardMixed); 467 Transitions.TransitionFinishCallback callback = wct -> { 468 mActiveTransitions.remove(keyguardMixed); 469 finishCallback.onTransitionFinished(wct); 470 }; 471 final boolean hasAnimateKeyguard = animateKeyguard( 472 keyguardMixed, info, startTransaction, finishTransaction, callback, 473 mKeyguardHandler, mPipHandler); 474 if (hasAnimateKeyguard) { 475 ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 476 "Converting mixed transition into a keyguard transition"); 477 // Consume the original mixed transition 478 mActiveTransitions.remove(mixed); 479 mixed.onTransitionConsumed(transition, false, null); 480 return true; 481 } else { 482 // Keyguard handler cannot handle it, process through original mixed 483 mActiveTransitions.remove(keyguardMixed); 484 } 485 } else if (mPipHandler != null) { 486 mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); 487 } 488 } 489 490 if (mixed == null) return false; 491 492 final MixedTransition chosenTransition = mixed; 493 Transitions.TransitionFinishCallback callback = wct -> { 494 mActiveTransitions.remove(chosenTransition); 495 finishCallback.onTransitionFinished(wct); 496 }; 497 498 boolean handled = chosenTransition.startAnimation( 499 transition, info, startTransaction, finishTransaction, callback); 500 if (!handled) { 501 mActiveTransitions.remove(chosenTransition); 502 } 503 return handled; 504 } 505 unlinkMissingParents(TransitionInfo from)506 private void unlinkMissingParents(TransitionInfo from) { 507 for (int i = 0; i < from.getChanges().size(); ++i) { 508 final TransitionInfo.Change chg = from.getChanges().get(i); 509 if (chg.getParent() == null) continue; 510 if (from.getChange(chg.getParent()) == null) { 511 from.getChanges().get(i).setParent(null); 512 } 513 } 514 } 515 isWithinTask(TransitionInfo info, TransitionInfo.Change chg)516 private boolean isWithinTask(TransitionInfo info, TransitionInfo.Change chg) { 517 TransitionInfo.Change curr = chg; 518 while (curr != null) { 519 if (curr.getTaskInfo() != null) return true; 520 if (curr.getParent() == null) break; 521 curr = info.getChange(curr.getParent()); 522 } 523 return false; 524 } 525 526 /** 527 * This is intended to be called by SplitCoordinator as a helper to mix a split handling 528 * transition with an entering-pip change. The use-case for this is when an auto-pip change 529 * gets collected into the transition which has already claimed by 530 * StageCoordinator.handleRequest. This happens when launching a fullscreen app while having an 531 * auto-pip activity in the foreground split pair. 532 */ 533 // TODO(b/287704263): Remove when split/mixed are reversed. animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCallback, boolean replacingPip)534 public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info, 535 SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, 536 Transitions.TransitionFinishCallback finishCallback, boolean replacingPip) { 537 int type = replacingPip 538 ? MixedTransition.TYPE_ENTER_PIP_REPLACE_FROM_SPLIT 539 : MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT; 540 final MixedTransition mixed = createDefaultMixedTransition(type, transition); 541 mActiveTransitions.add(mixed); 542 Transitions.TransitionFinishCallback callback = wct -> { 543 mActiveTransitions.remove(mixed); 544 finishCallback.onTransitionFinished(wct); 545 }; 546 return mixed.startAnimation(transition, info, startT, finishT, callback); 547 } 548 549 /** 550 * This is intended to be called by SplitCoordinator as a helper to mix an already-pending 551 * split transition with a display-change. The use-case for this is when a display 552 * change/rotation gets collected into a split-screen enter/exit transition which has already 553 * been claimed by StageCoordinator.handleRequest. This happens during launcher tests. 554 */ animatePendingSplitWithDisplayChange(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull Transitions.TransitionFinishCallback finishCallback)555 public boolean animatePendingSplitWithDisplayChange(@NonNull IBinder transition, 556 @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, 557 @NonNull SurfaceControl.Transaction finishT, 558 @NonNull Transitions.TransitionFinishCallback finishCallback) { 559 final TransitionInfo everythingElse = subCopy(info, info.getType(), true /* withChanges */); 560 final TransitionInfo displayPart = subCopy(info, TRANSIT_CHANGE, false /* withChanges */); 561 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 562 TransitionInfo.Change change = info.getChanges().get(i); 563 if (isWithinTask(info, change)) continue; 564 displayPart.addChange(change); 565 everythingElse.getChanges().remove(i); 566 } 567 if (displayPart.getChanges().isEmpty()) return false; 568 unlinkMissingParents(everythingElse); 569 final MixedTransition mixed = createDefaultMixedTransition( 570 MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition); 571 mActiveTransitions.add(mixed); 572 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change " 573 + "and split change."); 574 // We need to split the transition into 2 parts: the split part and the display part. 575 mixed.mInFlightSubAnimations = 2; 576 577 Transitions.TransitionFinishCallback finishCB = (wct) -> { 578 --mixed.mInFlightSubAnimations; 579 mixed.joinFinishArgs(wct); 580 if (mixed.mInFlightSubAnimations > 0) return; 581 mActiveTransitions.remove(mixed); 582 finishCallback.onTransitionFinished(mixed.mFinishWCT); 583 }; 584 585 // Dispatch the display change. This will most-likely be taken by the default handler. 586 // Do this first since the first handler used will apply the startT; the display change 587 // needs to take a screenshot before that happens so we need it to be the first handler. 588 mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, displayPart, 589 startT, finishT, finishCB, mSplitHandler); 590 591 // Note: at this point, startT has probably already been applied, so we are basically 592 // giving splitHandler an empty startT. This is currently OK because display-change will 593 // grab a screenshot and paste it on top anyways. 594 mSplitHandler.startPendingAnimation(transition, everythingElse, startT, finishT, finishCB); 595 return true; 596 } 597 598 /** 599 * For example: pip is entering in rotation 0, and then the display changes to rotation 90 600 * before the pip transition is ready. So the info contains both the entering pip and display 601 * change. In this case, the pip can go to the end state in new rotation directly, and let the 602 * display level animation cover all changed participates. 603 */ animateEnteringPipWithDisplayChange(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull TransitionInfo.Change pipChange, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull Transitions.TransitionFinishCallback finishCallback)604 public void animateEnteringPipWithDisplayChange(@NonNull IBinder transition, 605 @NonNull TransitionInfo info, @NonNull TransitionInfo.Change pipChange, 606 @NonNull SurfaceControl.Transaction startT, 607 @NonNull SurfaceControl.Transaction finishT, 608 @NonNull Transitions.TransitionFinishCallback finishCallback) { 609 // In order to play display level animation, force the type to CHANGE (it could be PIP). 610 final TransitionInfo changeInfo = info.getType() != TRANSIT_CHANGE 611 ? subCopy(info, TRANSIT_CHANGE, true /* withChanges */) : info; 612 final MixedTransition mixed = createDefaultMixedTransition( 613 MixedTransition.TYPE_ENTER_PIP_WITH_DISPLAY_CHANGE, transition); 614 mActiveTransitions.add(mixed); 615 mixed.mInFlightSubAnimations = 2; 616 final Transitions.TransitionFinishCallback finishCB = wct -> { 617 --mixed.mInFlightSubAnimations; 618 mixed.joinFinishArgs(wct); 619 if (mixed.mInFlightSubAnimations > 0) return; 620 mActiveTransitions.remove(mixed); 621 finishCallback.onTransitionFinished(mixed.mFinishWCT); 622 }; 623 // Perform the display animation first. 624 mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, changeInfo, 625 startT, finishT, finishCB, mPipHandler); 626 // Use a standalone finish transaction for pip because it will apply immediately. 627 final SurfaceControl.Transaction pipFinishT = new SurfaceControl.Transaction(); 628 mPipHandler.startEnterAnimation(pipChange, startT, pipFinishT, wct -> { 629 // Apply immediately to avoid potential flickering by bounds change at the end of 630 // display animation. 631 mPipHandler.applyTransaction(wct); 632 finishCB.onTransitionFinished(null /* wct */); 633 }); 634 // Jump to the pip end state directly and make sure the real finishT have the latest state. 635 mPipHandler.end(); 636 mPipHandler.syncPipSurfaceState(info, startT, finishT); 637 } 638 animateKeyguard(@onNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull KeyguardTransitionHandler keyguardHandler, PipTransitionController pipHandler)639 private static boolean animateKeyguard(@NonNull final MixedTransition mixed, 640 @NonNull TransitionInfo info, 641 @NonNull SurfaceControl.Transaction startTransaction, 642 @NonNull SurfaceControl.Transaction finishTransaction, 643 @NonNull Transitions.TransitionFinishCallback finishCallback, 644 @NonNull KeyguardTransitionHandler keyguardHandler, 645 PipTransitionController pipHandler) { 646 if (mixed.mFinishT == null) { 647 mixed.mFinishT = finishTransaction; 648 mixed.mFinishCB = finishCallback; 649 } 650 // Sync pip state. 651 if (pipHandler != null) { 652 pipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); 653 } 654 return mixed.startSubAnimation(keyguardHandler, info, startTransaction, finishTransaction); 655 } 656 657 /** Use to when split use intent to enter, check if this enter transition should be mixed or 658 * not.*/ isIntentInPip(PendingIntent intent)659 public boolean isIntentInPip(PendingIntent intent) { 660 // Check if this intent package is same as pip one or not, if true we want let the pip 661 // task enter split. 662 if (mPipHandler != null) { 663 return mPipHandler 664 .isPackageActiveInPip(ComponentUtils.getPackageName(intent.getIntent())); 665 } 666 return false; 667 } 668 669 /** Use to when split use taskId to enter, check if this enter transition should be mixed or 670 * not.*/ isTaskInPip(int taskId, ShellTaskOrganizer shellTaskOrganizer)671 public boolean isTaskInPip(int taskId, ShellTaskOrganizer shellTaskOrganizer) { 672 // Check if this intent package is same as pip one or not, if true we want let the pip 673 // task enter split. 674 if (mPipHandler != null) { 675 return mPipHandler.isPackageActiveInPip( 676 ComponentUtils.getPackageName(taskId, shellTaskOrganizer)); 677 } 678 return false; 679 } 680 681 /** @return whether the transition-request represents a pip-entry. */ requestHasPipEnter(TransitionRequestInfo request)682 public boolean requestHasPipEnter(TransitionRequestInfo request) { 683 return mPipHandler.requestHasPipEnter(request); 684 } 685 686 /** Whether a particular change is a window that is entering pip. */ 687 // TODO(b/287704263): Remove when split/mixed are reversed. isEnteringPip(TransitionInfo.Change change, @WindowManager.TransitionType int transitType)688 public boolean isEnteringPip(TransitionInfo.Change change, 689 @WindowManager.TransitionType int transitType) { 690 return mPipHandler.isEnteringPip(change, transitType); 691 } 692 693 @Override mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)694 public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 695 @NonNull SurfaceControl.Transaction startT, 696 @NonNull SurfaceControl.Transaction finishT, 697 @NonNull IBinder mergeTarget, 698 @NonNull Transitions.TransitionFinishCallback finishCallback) { 699 for (int i = 0; i < mActiveTransitions.size(); ++i) { 700 if (mActiveTransitions.get(i).mTransition != mergeTarget) continue; 701 702 MixedTransition mixed = mActiveTransitions.get(i); 703 if (mixed.mInFlightSubAnimations <= 0) { 704 // Already done, so no need to end it. 705 return; 706 } 707 mixed.mergeAnimation(transition, info, startT, finishT, mergeTarget, finishCallback); 708 } 709 } 710 711 @Override onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)712 public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, 713 @Nullable SurfaceControl.Transaction finishT) { 714 MixedTransition mixed = null; 715 for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { 716 if (mActiveTransitions.get(i).mTransition != transition) continue; 717 mixed = mActiveTransitions.remove(i); 718 break; 719 } 720 if (mixed != null) { 721 mixed.onTransitionConsumed(transition, aborted, finishT); 722 } 723 } 724 725 /** 726 * Update an incoming {@link TransitionInfo} with the leashes from an existing 727 * {@link TransitionInfo} so that it can take over some parts of the animation without 728 * reparenting to new transition roots. 729 */ handoverTransitionLeashes( @onNull TransitionInfo from, @NonNull TransitionInfo to, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT)730 static void handoverTransitionLeashes( 731 @NonNull TransitionInfo from, 732 @NonNull TransitionInfo to, 733 @NonNull SurfaceControl.Transaction startT, 734 @NonNull SurfaceControl.Transaction finishT) { 735 736 // Show the roots in case they contain new changes not present in the original transition. 737 for (int j = to.getRootCount() - 1; j >= 0; --j) { 738 startT.show(to.getRoot(j).getLeash()); 739 } 740 741 // Find all of the leashes from the original transition. 742 Map<WindowContainerToken, TransitionInfo.Change> originalChanges = new ArrayMap<>(); 743 for (TransitionInfo.Change oldChange : from.getChanges()) { 744 if (oldChange.getContainer() != null) { 745 originalChanges.put(oldChange.getContainer(), oldChange); 746 } 747 } 748 749 // Merge the animation leashes by re-using the original ones if we see the same container 750 // in the new transition and the old. 751 for (TransitionInfo.Change newChange : to.getChanges()) { 752 if (originalChanges.containsKey(newChange.getContainer())) { 753 final TransitionInfo.Change oldChange = originalChanges.get( 754 newChange.getContainer()); 755 startT.reparent(newChange.getLeash(), null); 756 newChange.setLeash(oldChange.getLeash()); 757 } 758 } 759 } 760 } 761