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.WindowManager.TRANSIT_CHANGE; 23 import static android.view.WindowManager.TRANSIT_TO_BACK; 24 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; 25 26 import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; 27 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; 28 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; 29 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; 30 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; 31 import static com.android.wm.shell.util.TransitionUtil.isOpeningType; 32 33 import android.annotation.NonNull; 34 import android.annotation.Nullable; 35 import android.app.PendingIntent; 36 import android.os.IBinder; 37 import android.util.Pair; 38 import android.view.SurfaceControl; 39 import android.view.WindowManager; 40 import android.window.TransitionInfo; 41 import android.window.TransitionRequestInfo; 42 import android.window.WindowContainerTransaction; 43 44 import com.android.internal.protolog.common.ProtoLog; 45 import com.android.wm.shell.common.split.SplitScreenUtils; 46 import com.android.wm.shell.desktopmode.DesktopModeController; 47 import com.android.wm.shell.desktopmode.DesktopModeStatus; 48 import com.android.wm.shell.desktopmode.DesktopTasksController; 49 import com.android.wm.shell.keyguard.KeyguardTransitionHandler; 50 import com.android.wm.shell.pip.PipTransitionController; 51 import com.android.wm.shell.protolog.ShellProtoLogGroup; 52 import com.android.wm.shell.recents.RecentsTransitionHandler; 53 import com.android.wm.shell.splitscreen.SplitScreen; 54 import com.android.wm.shell.splitscreen.SplitScreenController; 55 import com.android.wm.shell.splitscreen.StageCoordinator; 56 import com.android.wm.shell.sysui.ShellInit; 57 import com.android.wm.shell.unfold.UnfoldTransitionHandler; 58 import com.android.wm.shell.util.TransitionUtil; 59 60 import java.util.ArrayList; 61 import java.util.Optional; 62 63 /** 64 * A handler for dealing with transitions involving multiple other handlers. For example: an 65 * activity in split-screen going into PiP. 66 */ 67 public class DefaultMixedHandler implements Transitions.TransitionHandler, 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 DesktopModeController mDesktopModeController; 76 private DesktopTasksController mDesktopTasksController; 77 private UnfoldTransitionHandler mUnfoldHandler; 78 79 private static class MixedTransition { 80 static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; 81 82 /** Both the display and split-state (enter/exit) is changing */ 83 static final int TYPE_DISPLAY_AND_SPLIT_CHANGE = 2; 84 85 /** Pip was entered while handling an intent with its own remoteTransition. */ 86 static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3; 87 88 /** Recents transition while split-screen foreground. */ 89 static final int TYPE_RECENTS_DURING_SPLIT = 4; 90 91 /** Keyguard exit/occlude/unocclude transition. */ 92 static final int TYPE_KEYGUARD = 5; 93 94 /** Recents Transition while in desktop mode. */ 95 static final int TYPE_RECENTS_DURING_DESKTOP = 6; 96 97 /** Fuld/Unfold transition. */ 98 static final int TYPE_UNFOLD = 7; 99 100 /** The default animation for this mixed transition. */ 101 static final int ANIM_TYPE_DEFAULT = 0; 102 103 /** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */ 104 static final int ANIM_TYPE_GOING_HOME = 1; 105 106 /** For RECENTS_DURING_SPLIT, is set when this turns into a pair->pair task switch. */ 107 static final int ANIM_TYPE_PAIR_TO_PAIR = 1; 108 109 final int mType; 110 int mAnimType = ANIM_TYPE_DEFAULT; 111 final IBinder mTransition; 112 113 Transitions.TransitionHandler mLeftoversHandler = null; 114 WindowContainerTransaction mFinishWCT = null; 115 116 /** 117 * Whether the transition has request for remote transition while mLeftoversHandler 118 * isn't remote transition handler. 119 * If true and the mLeftoversHandler can handle the transition, need to notify remote 120 * transition handler to consume the transition. 121 */ 122 boolean mHasRequestToRemote; 123 124 /** 125 * Mixed transitions are made up of multiple "parts". This keeps track of how many 126 * parts are currently animating. 127 */ 128 int mInFlightSubAnimations = 0; 129 MixedTransition(int type, IBinder transition)130 MixedTransition(int type, IBinder transition) { 131 mType = type; 132 mTransition = transition; 133 } 134 joinFinishArgs(WindowContainerTransaction wct)135 void joinFinishArgs(WindowContainerTransaction wct) { 136 if (wct != null) { 137 if (mFinishWCT == null) { 138 mFinishWCT = wct; 139 } else { 140 mFinishWCT.merge(wct, true /* transfer */); 141 } 142 } 143 } 144 } 145 146 private final ArrayList<MixedTransition> mActiveTransitions = new ArrayList<>(); 147 DefaultMixedHandler(@onNull ShellInit shellInit, @NonNull Transitions player, Optional<SplitScreenController> splitScreenControllerOptional, @Nullable PipTransitionController pipTransitionController, Optional<RecentsTransitionHandler> recentsHandlerOptional, KeyguardTransitionHandler keyguardHandler, Optional<DesktopModeController> desktopModeControllerOptional, Optional<DesktopTasksController> desktopTasksControllerOptional, Optional<UnfoldTransitionHandler> unfoldHandler)148 public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player, 149 Optional<SplitScreenController> splitScreenControllerOptional, 150 @Nullable PipTransitionController pipTransitionController, 151 Optional<RecentsTransitionHandler> recentsHandlerOptional, 152 KeyguardTransitionHandler keyguardHandler, 153 Optional<DesktopModeController> desktopModeControllerOptional, 154 Optional<DesktopTasksController> desktopTasksControllerOptional, 155 Optional<UnfoldTransitionHandler> unfoldHandler) { 156 mPlayer = player; 157 mKeyguardHandler = keyguardHandler; 158 if (Transitions.ENABLE_SHELL_TRANSITIONS 159 && pipTransitionController != null 160 && splitScreenControllerOptional.isPresent()) { 161 // Add after dependencies because it is higher priority 162 shellInit.addInitCallback(() -> { 163 mPipHandler = pipTransitionController; 164 mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler(); 165 mPlayer.addHandler(this); 166 if (mSplitHandler != null) { 167 mSplitHandler.setMixedHandler(this); 168 } 169 mRecentsHandler = recentsHandlerOptional.orElse(null); 170 if (mRecentsHandler != null) { 171 mRecentsHandler.addMixer(this); 172 } 173 mDesktopModeController = desktopModeControllerOptional.orElse(null); 174 mDesktopTasksController = desktopTasksControllerOptional.orElse(null); 175 mUnfoldHandler = unfoldHandler.orElse(null); 176 }, this); 177 } 178 } 179 180 @Nullable 181 @Override handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)182 public WindowContainerTransaction handleRequest(@NonNull IBinder transition, 183 @NonNull TransitionRequestInfo request) { 184 if (mSplitHandler.requestImpliesSplitToPip(request)) { 185 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while " 186 + "Split-Screen is active, so treat it as Mixed."); 187 if (request.getRemoteTransition() != null) { 188 throw new IllegalStateException("Unexpected remote transition in" 189 + "pip-enter-from-split request"); 190 } 191 mActiveTransitions.add(new MixedTransition(MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, 192 transition)); 193 194 WindowContainerTransaction out = new WindowContainerTransaction(); 195 mPipHandler.augmentRequest(transition, request, out); 196 mSplitHandler.addEnterOrExitIfNeeded(request, out); 197 return out; 198 } else if (request.getRemoteTransition() != null 199 && TransitionUtil.isOpeningType(request.getType()) 200 && (request.getTriggerTask() == null 201 || (request.getTriggerTask().topActivityType != ACTIVITY_TYPE_HOME 202 && request.getTriggerTask().topActivityType != ACTIVITY_TYPE_RECENTS))) { 203 // Only select transitions with an intent-provided remote-animation because that will 204 // usually grab priority and often won't handle PiP. If there isn't an intent-provided 205 // remote, then the transition will be dispatched normally and the PipHandler will 206 // pick it up. 207 Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler = 208 mPlayer.dispatchRequest(transition, request, this); 209 if (handler == null) { 210 return null; 211 } 212 final MixedTransition mixed = new MixedTransition( 213 MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition); 214 mixed.mLeftoversHandler = handler.first; 215 mActiveTransitions.add(mixed); 216 if (mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) { 217 mixed.mHasRequestToRemote = true; 218 mPlayer.getRemoteTransitionHandler().handleRequest(transition, request); 219 } 220 return handler.second; 221 } else if (mSplitHandler.isSplitScreenVisible() 222 && isOpeningType(request.getType()) 223 && request.getTriggerTask() != null 224 && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN 225 && request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME) { 226 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a going-home request while " 227 + "Split-Screen is foreground, so treat it as Mixed."); 228 Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler = 229 mPlayer.dispatchRequest(transition, request, this); 230 if (handler == null) { 231 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 232 " Lean on the remote transition handler to fetch a proper remote via" 233 + " TransitionFilter"); 234 handler = new Pair<>( 235 mPlayer.getRemoteTransitionHandler(), 236 new WindowContainerTransaction()); 237 } 238 final MixedTransition mixed = new MixedTransition( 239 MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition); 240 mixed.mLeftoversHandler = handler.first; 241 mActiveTransitions.add(mixed); 242 return handler.second; 243 } else if (mUnfoldHandler != null && mUnfoldHandler.hasUnfold(request)) { 244 final WindowContainerTransaction wct = 245 mUnfoldHandler.handleRequest(transition, request); 246 if (wct != null) { 247 final MixedTransition mixed = new MixedTransition( 248 MixedTransition.TYPE_UNFOLD, transition); 249 mixed.mLeftoversHandler = mUnfoldHandler; 250 mActiveTransitions.add(mixed); 251 } 252 return wct; 253 } 254 return null; 255 } 256 257 @Override handleRecentsRequest(WindowContainerTransaction outWCT)258 public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) { 259 if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible() 260 || DesktopModeStatus.isActive(mPlayer.getContext()))) { 261 return this; 262 } 263 return null; 264 } 265 266 @Override setRecentsTransition(IBinder transition)267 public void setRecentsTransition(IBinder transition) { 268 if (mSplitHandler.isSplitScreenVisible()) { 269 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " 270 + "Split-Screen is foreground, so treat it as Mixed."); 271 final MixedTransition mixed = new MixedTransition( 272 MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition); 273 mixed.mLeftoversHandler = mRecentsHandler; 274 mActiveTransitions.add(mixed); 275 } else if (DesktopModeStatus.isActive(mPlayer.getContext())) { 276 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " 277 + "desktop mode is active, so treat it as Mixed."); 278 final MixedTransition mixed = new MixedTransition( 279 MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition); 280 mixed.mLeftoversHandler = mRecentsHandler; 281 mActiveTransitions.add(mixed); 282 } else { 283 throw new IllegalStateException("Accepted a recents transition but don't know how to" 284 + " handle it"); 285 } 286 } 287 subCopy(@onNull TransitionInfo info, @WindowManager.TransitionType int newType, boolean withChanges)288 private TransitionInfo subCopy(@NonNull TransitionInfo info, 289 @WindowManager.TransitionType int newType, boolean withChanges) { 290 final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0); 291 out.setTrack(info.getTrack()); 292 out.setDebugId(info.getDebugId()); 293 if (withChanges) { 294 for (int i = 0; i < info.getChanges().size(); ++i) { 295 out.getChanges().add(info.getChanges().get(i)); 296 } 297 } 298 for (int i = 0; i < info.getRootCount(); ++i) { 299 out.addRoot(info.getRoot(i)); 300 } 301 out.setAnimationOptions(info.getAnimationOptions()); 302 return out; 303 } 304 isHomeOpening(@onNull TransitionInfo.Change change)305 private boolean isHomeOpening(@NonNull TransitionInfo.Change change) { 306 return change.getTaskInfo() != null 307 && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME; 308 } 309 isWallpaper(@onNull TransitionInfo.Change change)310 private boolean isWallpaper(@NonNull TransitionInfo.Change change) { 311 return (change.getFlags() & FLAG_IS_WALLPAPER) != 0; 312 } 313 314 @Override startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)315 public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 316 @NonNull SurfaceControl.Transaction startTransaction, 317 @NonNull SurfaceControl.Transaction finishTransaction, 318 @NonNull Transitions.TransitionFinishCallback finishCallback) { 319 320 MixedTransition mixed = null; 321 for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { 322 if (mActiveTransitions.get(i).mTransition != transition) continue; 323 mixed = mActiveTransitions.get(i); 324 break; 325 } 326 327 // Offer Keyguard the opportunity to take over lock transitions - ideally we could know by 328 // the time of handleRequest, but we need more information than is available at that time. 329 if (KeyguardTransitionHandler.handles(info)) { 330 if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) { 331 final MixedTransition keyguardMixed = 332 new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition); 333 mActiveTransitions.add(keyguardMixed); 334 final boolean hasAnimateKeyguard = animateKeyguard(keyguardMixed, info, 335 startTransaction, finishTransaction, finishCallback); 336 if (hasAnimateKeyguard) { 337 ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 338 "Converting mixed transition into a keyguard transition"); 339 // Consume the original mixed transition 340 onTransitionConsumed(transition, false, null); 341 return true; 342 } else { 343 // Keyguard handler cannot handle it, process through original mixed 344 mActiveTransitions.remove(keyguardMixed); 345 } 346 } else if (mPipHandler != null) { 347 mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); 348 } 349 } 350 351 if (mixed == null) return false; 352 353 if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { 354 return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction, 355 finishCallback); 356 } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { 357 return false; 358 } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { 359 final boolean handledToPip = animateOpenIntentWithRemoteAndPip(mixed, info, 360 startTransaction, finishTransaction, finishCallback); 361 // Consume the transition on remote handler if the leftover handler already handle this 362 // transition. And if it cannot, the transition will be handled by remote handler, so 363 // don't consume here. 364 // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip 365 if (handledToPip && mixed.mHasRequestToRemote 366 && mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) { 367 mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null); 368 } 369 return handledToPip; 370 } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { 371 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 372 final TransitionInfo.Change change = info.getChanges().get(i); 373 // Pip auto-entering info might be appended to recent transition like pressing 374 // home-key in 3-button navigation. This offers split handler the opportunity to 375 // handle split to pip animation. 376 if (mPipHandler.isEnteringPip(change, info.getType()) 377 && mSplitHandler.getSplitItemPosition(change.getLastParent()) 378 != SPLIT_POSITION_UNDEFINED) { 379 return animateEnterPipFromSplit(mixed, info, startTransaction, 380 finishTransaction, finishCallback); 381 } 382 } 383 384 return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction, 385 finishCallback); 386 } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) { 387 return animateKeyguard(mixed, info, startTransaction, finishTransaction, 388 finishCallback); 389 } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) { 390 return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction, 391 finishCallback); 392 } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) { 393 return animateUnfold(mixed, info, startTransaction, finishTransaction, finishCallback); 394 } else { 395 mActiveTransitions.remove(mixed); 396 throw new IllegalStateException("Starting mixed animation without a known mixed type? " 397 + mixed.mType); 398 } 399 } 400 animateOpenIntentWithRemoteAndPip(@onNull MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)401 private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed, 402 @NonNull TransitionInfo info, 403 @NonNull SurfaceControl.Transaction startTransaction, 404 @NonNull SurfaceControl.Transaction finishTransaction, 405 @NonNull Transitions.TransitionFinishCallback finishCallback) { 406 TransitionInfo.Change pipChange = null; 407 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 408 TransitionInfo.Change change = info.getChanges().get(i); 409 if (mPipHandler.isEnteringPip(change, info.getType())) { 410 if (pipChange != null) { 411 throw new IllegalStateException("More than 1 pip-entering changes in one" 412 + " transition? " + info); 413 } 414 pipChange = change; 415 info.getChanges().remove(i); 416 } 417 } 418 Transitions.TransitionFinishCallback finishCB = (wct) -> { 419 --mixed.mInFlightSubAnimations; 420 mixed.joinFinishArgs(wct); 421 if (mixed.mInFlightSubAnimations > 0) return; 422 mActiveTransitions.remove(mixed); 423 finishCallback.onTransitionFinished(mixed.mFinishWCT); 424 }; 425 if (pipChange == null) { 426 if (mixed.mLeftoversHandler != null) { 427 mixed.mInFlightSubAnimations = 1; 428 if (mixed.mLeftoversHandler.startAnimation(mixed.mTransition, 429 info, startTransaction, finishTransaction, finishCB)) { 430 return true; 431 } 432 } 433 mActiveTransitions.remove(mixed); 434 return false; 435 } 436 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate" 437 + " animation because remote-animation likely doesn't support it"); 438 // Split the transition into 2 parts: the pip part and the rest. 439 mixed.mInFlightSubAnimations = 2; 440 // make a new startTransaction because pip's startEnterAnimation "consumes" it so 441 // we need a separate one to send over to launcher. 442 SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); 443 444 mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB); 445 446 // Dispatch the rest of the transition normally. 447 if (mixed.mLeftoversHandler != null 448 && mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info, 449 startTransaction, finishTransaction, finishCB)) { 450 return true; 451 } 452 mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, info, 453 startTransaction, finishTransaction, finishCB, this); 454 return true; 455 } 456 animateEnterPipFromSplit(@onNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)457 private boolean animateEnterPipFromSplit(@NonNull final MixedTransition mixed, 458 @NonNull TransitionInfo info, 459 @NonNull SurfaceControl.Transaction startTransaction, 460 @NonNull SurfaceControl.Transaction finishTransaction, 461 @NonNull Transitions.TransitionFinishCallback finishCallback) { 462 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " 463 + "entering PIP while Split-Screen is foreground."); 464 TransitionInfo.Change pipChange = null; 465 TransitionInfo.Change wallpaper = null; 466 final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */); 467 boolean homeIsOpening = false; 468 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 469 TransitionInfo.Change change = info.getChanges().get(i); 470 if (mPipHandler.isEnteringPip(change, info.getType())) { 471 if (pipChange != null) { 472 throw new IllegalStateException("More than 1 pip-entering changes in one" 473 + " transition? " + info); 474 } 475 pipChange = change; 476 // going backwards, so remove-by-index is fine. 477 everythingElse.getChanges().remove(i); 478 } else if (isHomeOpening(change)) { 479 homeIsOpening = true; 480 } else if (isWallpaper(change)) { 481 wallpaper = change; 482 } 483 } 484 if (pipChange == null) { 485 // um, something probably went wrong. 486 mActiveTransitions.remove(mixed); 487 return false; 488 } 489 final boolean isGoingHome = homeIsOpening; 490 Transitions.TransitionFinishCallback finishCB = (wct) -> { 491 --mixed.mInFlightSubAnimations; 492 mixed.joinFinishArgs(wct); 493 if (mixed.mInFlightSubAnimations > 0) return; 494 mActiveTransitions.remove(mixed); 495 if (isGoingHome) { 496 mSplitHandler.onTransitionAnimationComplete(); 497 } 498 finishCallback.onTransitionFinished(mixed.mFinishWCT); 499 }; 500 if (isGoingHome || mSplitHandler.getSplitItemPosition(pipChange.getLastParent()) 501 != SPLIT_POSITION_UNDEFINED) { 502 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed " 503 + "since entering-PiP caused us to leave split and return home."); 504 // We need to split the transition into 2 parts: the pip part (animated by pip) 505 // and the dismiss-part (animated by launcher). 506 mixed.mInFlightSubAnimations = 2; 507 // immediately make the wallpaper visible (so that we don't see it pop-in during 508 // the time it takes to start recents animation (which is remote). 509 if (wallpaper != null) { 510 startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f); 511 } 512 // make a new startTransaction because pip's startEnterAnimation "consumes" it so 513 // we need a separate one to send over to launcher. 514 SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); 515 @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED; 516 if (mSplitHandler.isSplitScreenVisible()) { 517 // The non-going home case, we could be pip-ing one of the split stages and keep 518 // showing the other 519 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 520 TransitionInfo.Change change = info.getChanges().get(i); 521 if (change == pipChange) { 522 // Ignore the change/task that's going into Pip 523 continue; 524 } 525 @SplitScreen.StageType int splitItemStage = 526 mSplitHandler.getSplitItemStage(change.getLastParent()); 527 if (splitItemStage != STAGE_TYPE_UNDEFINED) { 528 topStageToKeep = splitItemStage; 529 break; 530 } 531 } 532 } 533 // Let split update internal state for dismiss. 534 mSplitHandler.prepareDismissAnimation(topStageToKeep, 535 EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, 536 finishTransaction); 537 538 // We are trying to accommodate launcher's close animation which can't handle the 539 // divider-bar, so if split-handler is closing the divider-bar, just hide it and remove 540 // from transition info. 541 for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) { 542 if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR) != 0) { 543 everythingElse.getChanges().remove(i); 544 break; 545 } 546 } 547 548 mPipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA); 549 mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, 550 finishCB); 551 // Dispatch the rest of the transition normally. This will most-likely be taken by 552 // recents or default handler. 553 mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, everythingElse, 554 otherStartT, finishTransaction, finishCB, this); 555 } else { 556 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just " 557 + "forward animation to Pip-Handler."); 558 // This happens if the pip-ing activity is in a multi-activity task (and thus a 559 // new pip task is spawned). In this case, we don't actually exit split so we can 560 // just let pip transition handle the animation verbatim. 561 mixed.mInFlightSubAnimations = 1; 562 mPipHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction, 563 finishCB); 564 } 565 return true; 566 } 567 unlinkMissingParents(TransitionInfo from)568 private void unlinkMissingParents(TransitionInfo from) { 569 for (int i = 0; i < from.getChanges().size(); ++i) { 570 final TransitionInfo.Change chg = from.getChanges().get(i); 571 if (chg.getParent() == null) continue; 572 if (from.getChange(chg.getParent()) == null) { 573 from.getChanges().get(i).setParent(null); 574 } 575 } 576 } 577 isWithinTask(TransitionInfo info, TransitionInfo.Change chg)578 private boolean isWithinTask(TransitionInfo info, TransitionInfo.Change chg) { 579 TransitionInfo.Change curr = chg; 580 while (curr != null) { 581 if (curr.getTaskInfo() != null) return true; 582 if (curr.getParent() == null) break; 583 curr = info.getChange(curr.getParent()); 584 } 585 return false; 586 } 587 588 /** 589 * This is intended to be called by SplitCoordinator as a helper to mix a split handling 590 * transition with an entering-pip change. The use-case for this is when an auto-pip change 591 * gets collected into the transition which has already claimed by 592 * StageCoordinator.handleRequest. This happens when launching a fullscreen app while having an 593 * auto-pip activity in the foreground split pair. 594 */ 595 // TODO(b/287704263): Remove when split/mixed are reversed. animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCallback)596 public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info, 597 SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, 598 Transitions.TransitionFinishCallback finishCallback) { 599 final MixedTransition mixed = new MixedTransition( 600 MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition); 601 mActiveTransitions.add(mixed); 602 return animateEnterPipFromSplit(mixed, info, startT, finishT, finishCallback); 603 } 604 605 /** 606 * This is intended to be called by SplitCoordinator as a helper to mix an already-pending 607 * split transition with a display-change. The use-case for this is when a display 608 * change/rotation gets collected into a split-screen enter/exit transition which has already 609 * been claimed by StageCoordinator.handleRequest. This happens during launcher tests. 610 */ animatePendingSplitWithDisplayChange(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull Transitions.TransitionFinishCallback finishCallback)611 public boolean animatePendingSplitWithDisplayChange(@NonNull IBinder transition, 612 @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, 613 @NonNull SurfaceControl.Transaction finishT, 614 @NonNull Transitions.TransitionFinishCallback finishCallback) { 615 final TransitionInfo everythingElse = subCopy(info, info.getType(), true /* withChanges */); 616 final TransitionInfo displayPart = subCopy(info, TRANSIT_CHANGE, false /* withChanges */); 617 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 618 TransitionInfo.Change change = info.getChanges().get(i); 619 if (isWithinTask(info, change)) continue; 620 displayPart.addChange(change); 621 everythingElse.getChanges().remove(i); 622 } 623 if (displayPart.getChanges().isEmpty()) return false; 624 unlinkMissingParents(everythingElse); 625 final MixedTransition mixed = new MixedTransition( 626 MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition); 627 mActiveTransitions.add(mixed); 628 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change " 629 + "and split change."); 630 // We need to split the transition into 2 parts: the split part and the display part. 631 mixed.mInFlightSubAnimations = 2; 632 633 Transitions.TransitionFinishCallback finishCB = (wct) -> { 634 --mixed.mInFlightSubAnimations; 635 mixed.joinFinishArgs(wct); 636 if (mixed.mInFlightSubAnimations > 0) return; 637 mActiveTransitions.remove(mixed); 638 finishCallback.onTransitionFinished(mixed.mFinishWCT); 639 }; 640 641 // Dispatch the display change. This will most-likely be taken by the default handler. 642 // Do this first since the first handler used will apply the startT; the display change 643 // needs to take a screenshot before that happens so we need it to be the first handler. 644 mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, displayPart, 645 startT, finishT, finishCB, mSplitHandler); 646 647 // Note: at this point, startT has probably already been applied, so we are basically 648 // giving splitHandler an empty startT. This is currently OK because display-change will 649 // grab a screenshot and paste it on top anyways. 650 mSplitHandler.startPendingAnimation(transition, everythingElse, startT, finishT, finishCB); 651 return true; 652 } 653 animateRecentsDuringSplit(@onNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)654 private boolean animateRecentsDuringSplit(@NonNull final MixedTransition mixed, 655 @NonNull TransitionInfo info, 656 @NonNull SurfaceControl.Transaction startTransaction, 657 @NonNull SurfaceControl.Transaction finishTransaction, 658 @NonNull Transitions.TransitionFinishCallback finishCallback) { 659 // Split-screen is only interested in the recents transition finishing (and merging), so 660 // just wrap finish and start recents animation directly. 661 Transitions.TransitionFinishCallback finishCB = (wct) -> { 662 mixed.mInFlightSubAnimations = 0; 663 mActiveTransitions.remove(mixed); 664 // If pair-to-pair switching, the post-recents clean-up isn't needed. 665 wct = wct != null ? wct : new WindowContainerTransaction(); 666 if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) { 667 mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction); 668 } else { 669 // notify pair-to-pair recents animation finish 670 mSplitHandler.onRecentsPairToPairAnimationFinish(wct); 671 } 672 mSplitHandler.onTransitionAnimationComplete(); 673 finishCallback.onTransitionFinished(wct); 674 }; 675 mixed.mInFlightSubAnimations = 1; 676 mSplitHandler.onRecentsInSplitAnimationStart(info); 677 final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info, 678 startTransaction, finishTransaction, finishCB); 679 if (!handled) { 680 mSplitHandler.onRecentsInSplitAnimationCanceled(); 681 mActiveTransitions.remove(mixed); 682 } 683 return handled; 684 } 685 animateKeyguard(@onNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)686 private boolean animateKeyguard(@NonNull final MixedTransition mixed, 687 @NonNull TransitionInfo info, 688 @NonNull SurfaceControl.Transaction startTransaction, 689 @NonNull SurfaceControl.Transaction finishTransaction, 690 @NonNull Transitions.TransitionFinishCallback finishCallback) { 691 final Transitions.TransitionFinishCallback finishCB = (wct) -> { 692 mixed.mInFlightSubAnimations--; 693 if (mixed.mInFlightSubAnimations == 0) { 694 mActiveTransitions.remove(mixed); 695 finishCallback.onTransitionFinished(wct); 696 } 697 }; 698 mixed.mInFlightSubAnimations++; 699 // Sync pip state. 700 if (mPipHandler != null) { 701 mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); 702 } 703 if (!mKeyguardHandler.startAnimation( 704 mixed.mTransition, info, startTransaction, finishTransaction, finishCB)) { 705 mixed.mInFlightSubAnimations--; 706 return false; 707 } 708 return true; 709 } 710 animateRecentsDuringDesktop(@onNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)711 private boolean animateRecentsDuringDesktop(@NonNull final MixedTransition mixed, 712 @NonNull TransitionInfo info, 713 @NonNull SurfaceControl.Transaction startTransaction, 714 @NonNull SurfaceControl.Transaction finishTransaction, 715 @NonNull Transitions.TransitionFinishCallback finishCallback) { 716 boolean consumed = mRecentsHandler.startAnimation( 717 mixed.mTransition, info, startTransaction, finishTransaction, finishCallback); 718 if (!consumed) { 719 return false; 720 } 721 //Sync desktop mode state (proto 1) 722 if (mDesktopModeController != null) { 723 mDesktopModeController.syncSurfaceState(info, finishTransaction); 724 return true; 725 } 726 //Sync desktop mode state (proto 2) 727 if (mDesktopTasksController != null) { 728 mDesktopTasksController.syncSurfaceState(info, finishTransaction); 729 return true; 730 } 731 732 return false; 733 } 734 animateUnfold(@onNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)735 private boolean animateUnfold(@NonNull final MixedTransition mixed, 736 @NonNull TransitionInfo info, 737 @NonNull SurfaceControl.Transaction startTransaction, 738 @NonNull SurfaceControl.Transaction finishTransaction, 739 @NonNull Transitions.TransitionFinishCallback finishCallback) { 740 final Transitions.TransitionFinishCallback finishCB = (wct) -> { 741 mixed.mInFlightSubAnimations--; 742 if (mixed.mInFlightSubAnimations > 0) return; 743 mActiveTransitions.remove(mixed); 744 finishCallback.onTransitionFinished(wct); 745 }; 746 mixed.mInFlightSubAnimations = 1; 747 // Sync pip state. 748 if (mPipHandler != null) { 749 mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); 750 } 751 if (mSplitHandler != null && mSplitHandler.isSplitActive()) { 752 mSplitHandler.updateSurfaces(startTransaction); 753 } 754 return mUnfoldHandler.startAnimation( 755 mixed.mTransition, info, startTransaction, finishTransaction, finishCB); 756 } 757 758 /** Use to when split use intent to enter, check if this enter transition should be mixed or 759 * not.*/ shouldSplitEnterMixed(PendingIntent intent)760 public boolean shouldSplitEnterMixed(PendingIntent intent) { 761 // Check if this intent package is same as pip one or not, if true we want let the pip 762 // task enter split. 763 if (mPipHandler != null) { 764 return mPipHandler.isInPipPackage(SplitScreenUtils.getPackageName(intent.getIntent())); 765 } 766 return false; 767 } 768 769 /** @return whether the transition-request represents a pip-entry. */ requestHasPipEnter(TransitionRequestInfo request)770 public boolean requestHasPipEnter(TransitionRequestInfo request) { 771 return mPipHandler.requestHasPipEnter(request); 772 } 773 774 /** Whether a particular change is a window that is entering pip. */ 775 // TODO(b/287704263): Remove when split/mixed are reversed. isEnteringPip(TransitionInfo.Change change, @WindowManager.TransitionType int transitType)776 public boolean isEnteringPip(TransitionInfo.Change change, 777 @WindowManager.TransitionType int transitType) { 778 return mPipHandler.isEnteringPip(change, transitType); 779 } 780 781 @Override mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)782 public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 783 @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, 784 @NonNull Transitions.TransitionFinishCallback finishCallback) { 785 for (int i = 0; i < mActiveTransitions.size(); ++i) { 786 if (mActiveTransitions.get(i).mTransition != mergeTarget) continue; 787 MixedTransition mixed = mActiveTransitions.get(i); 788 if (mixed.mInFlightSubAnimations <= 0) { 789 // Already done, so no need to end it. 790 return; 791 } 792 if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { 793 // queue since no actual animation. 794 } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { 795 if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) { 796 boolean ended = mSplitHandler.end(); 797 // If split couldn't end (because it is remote), then don't end everything else 798 // since we have to play out the animation anyways. 799 if (!ended) return; 800 mPipHandler.end(); 801 if (mixed.mLeftoversHandler != null) { 802 mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, 803 finishCallback); 804 } 805 } else { 806 mPipHandler.end(); 807 } 808 } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { 809 mPipHandler.end(); 810 if (mixed.mLeftoversHandler != null) { 811 mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, 812 finishCallback); 813 } 814 } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { 815 if (mSplitHandler.isPendingEnter(transition)) { 816 // Recents -> enter-split means that we are switching from one pair to 817 // another pair. 818 mixed.mAnimType = MixedTransition.ANIM_TYPE_PAIR_TO_PAIR; 819 } 820 mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, 821 finishCallback); 822 } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) { 823 mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); 824 } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) { 825 mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, 826 finishCallback); 827 } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) { 828 mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); 829 } else { 830 throw new IllegalStateException("Playing a mixed transition with unknown type? " 831 + mixed.mType); 832 } 833 } 834 } 835 836 @Override onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)837 public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, 838 @Nullable SurfaceControl.Transaction finishT) { 839 MixedTransition mixed = null; 840 for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { 841 if (mActiveTransitions.get(i).mTransition != transition) continue; 842 mixed = mActiveTransitions.remove(i); 843 break; 844 } 845 if (mixed == null) return; 846 if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { 847 mPipHandler.onTransitionConsumed(transition, aborted, finishT); 848 } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { 849 mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); 850 } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { 851 mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); 852 } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) { 853 mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT); 854 } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) { 855 mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); 856 } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) { 857 mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT); 858 } 859 if (mixed.mHasRequestToRemote) { 860 mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT); 861 } 862 } 863 } 864