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