1 /* 2 * Copyright (C) 2021 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.splitscreen; 18 19 import static android.view.WindowManager.TRANSIT_CHANGE; 20 import static android.view.WindowManager.TRANSIT_CLOSE; 21 import static android.view.WindowManager.TRANSIT_OPEN; 22 import static android.view.WindowManager.TRANSIT_TO_BACK; 23 24 import static com.android.wm.shell.Flags.enableFlexibleSplit; 25 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; 26 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS; 27 import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN; 28 import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_OUT; 29 import static com.android.wm.shell.shared.split.SplitScreenConstants.FADE_DURATION; 30 import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; 31 import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; 32 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; 33 import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString; 34 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS; 35 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP; 36 37 import android.animation.Animator; 38 import android.animation.AnimatorListenerAdapter; 39 import android.animation.ValueAnimator; 40 import android.annotation.NonNull; 41 import android.annotation.Nullable; 42 import android.os.IBinder; 43 import android.view.SurfaceControl; 44 import android.view.WindowManager; 45 import android.window.RemoteTransition; 46 import android.window.TransitionInfo; 47 import android.window.WindowContainerToken; 48 import android.window.WindowContainerTransaction; 49 50 import com.android.internal.protolog.ProtoLog; 51 import com.android.wm.shell.common.split.SplitDecorManager; 52 import com.android.wm.shell.protolog.ShellProtoLogGroup; 53 import com.android.wm.shell.shared.TransactionPool; 54 import com.android.wm.shell.shared.TransitionUtil; 55 import com.android.wm.shell.shared.split.SplitScreenConstants; 56 import com.android.wm.shell.transition.OneShotRemoteHandler; 57 import com.android.wm.shell.transition.Transitions; 58 59 import java.util.ArrayList; 60 import java.util.List; 61 import java.util.Map; 62 import java.util.Set; 63 import java.util.concurrent.Executor; 64 65 /** Manages transition animations for split-screen. */ 66 class SplitScreenTransitions { 67 private static final String TAG = "SplitScreenTransitions"; 68 69 private final TransactionPool mTransactionPool; 70 private final Transitions mTransitions; 71 private final Runnable mOnFinish; 72 73 DismissSession mPendingDismiss = null; 74 EnterSession mPendingEnter = null; 75 TransitSession mPendingResize = null; 76 TransitSession mPendingRemotePassthrough = null; 77 78 private IBinder mAnimatingTransition = null; 79 private OneShotRemoteHandler mActiveRemoteHandler = null; 80 81 private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish; 82 83 /** Keeps track of currently running animations */ 84 private final ArrayList<Animator> mAnimations = new ArrayList<>(); 85 private final StageCoordinator mStageCoordinator; 86 87 private Transitions.TransitionFinishCallback mFinishCallback = null; 88 private SurfaceControl.Transaction mFinishTransaction; 89 private SplitScreen.SplitInvocationListener mSplitInvocationListener; 90 private Executor mSplitInvocationListenerExecutor; 91 SplitScreenTransitions(@onNull TransactionPool pool, @NonNull Transitions transitions, @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator)92 SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions, 93 @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) { 94 mTransactionPool = pool; 95 mTransitions = transitions; 96 mOnFinish = onFinishCallback; 97 mStageCoordinator = stageCoordinator; 98 } 99 initTransition(@onNull IBinder transition, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)100 private void initTransition(@NonNull IBinder transition, 101 @NonNull SurfaceControl.Transaction finishTransaction, 102 @NonNull Transitions.TransitionFinishCallback finishCallback) { 103 mAnimatingTransition = transition; 104 mFinishTransaction = finishTransaction; 105 mFinishCallback = finishCallback; 106 } 107 108 /** Play animation for enter transition or dismiss transition. */ playAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot)109 void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 110 @NonNull SurfaceControl.Transaction startTransaction, 111 @NonNull SurfaceControl.Transaction finishTransaction, 112 @NonNull Transitions.TransitionFinishCallback finishCallback, 113 @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, 114 @NonNull WindowContainerToken topRoot) { 115 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playAnimation: transition=%d", info.getDebugId()); 116 initTransition(transition, finishTransaction, finishCallback); 117 118 final TransitSession pendingTransition = getPendingTransition(transition); 119 if (pendingTransition != null) { 120 if (pendingTransition.mCanceled) { 121 // The pending transition was canceled, so skip playing animation. 122 startTransaction.apply(); 123 onFinish(null /* wct */); 124 return; 125 } 126 127 if (pendingTransition.mRemoteHandler != null) { 128 pendingTransition.mRemoteHandler.startAnimation(transition, info, startTransaction, 129 finishTransaction, mRemoteFinishCB); 130 mActiveRemoteHandler = pendingTransition.mRemoteHandler; 131 return; 132 } 133 } 134 135 playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot, topRoot); 136 } 137 138 /** Internal function of playAnimation. */ playInternalAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot)139 private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 140 @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot, 141 @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) { 142 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playInternalAnimation: transition=%d", 143 info.getDebugId()); 144 // Play some place-holder fade animations 145 final boolean isEnter = isPendingEnter(transition); 146 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 147 final TransitionInfo.Change change = info.getChanges().get(i); 148 final SurfaceControl leash = change.getLeash(); 149 final int mode = info.getChanges().get(i).getMode(); 150 151 final int rootIdx = TransitionUtil.rootIndexFor(change, info); 152 if (mode == TRANSIT_CHANGE) { 153 if (change.getParent() != null) { 154 // This is probably reparented, so we want the parent to be immediately visible 155 final TransitionInfo.Change parentChange = info.getChange(change.getParent()); 156 t.show(parentChange.getLeash()); 157 t.setAlpha(parentChange.getLeash(), 1.f); 158 // and then animate this layer outside the parent (since, for example, this is 159 // the home task animating from fullscreen to part-screen). 160 t.reparent(parentChange.getLeash(), info.getRoot(rootIdx).getLeash()); 161 t.setLayer(parentChange.getLeash(), info.getChanges().size() - i); 162 // build the finish reparent/reposition 163 mFinishTransaction.reparent(leash, parentChange.getLeash()); 164 mFinishTransaction.setPosition(leash, 165 change.getEndRelOffset().x, change.getEndRelOffset().y); 166 } 167 } 168 169 final boolean isTopRoot = topRoot.equals(change.getContainer()); 170 final boolean isMainRoot = mainRoot.equals(change.getContainer()); 171 final boolean isSideRoot = sideRoot.equals(change.getContainer()); 172 final boolean isDivider = change.getFlags() == FLAG_IS_DIVIDER_BAR; 173 final boolean isMainChild = mainRoot.equals(change.getParent()); 174 final boolean isSideChild = sideRoot.equals(change.getParent()); 175 if (isEnter && (isMainChild || isSideChild)) { 176 // Reset child tasks bounds on finish. 177 mFinishTransaction.setPosition(leash, 178 change.getEndRelOffset().x, change.getEndRelOffset().y); 179 mFinishTransaction.setCrop(leash, null); 180 } else if (isTopRoot) { 181 // Ensure top root is visible at start. 182 t.setAlpha(leash, 1.f); 183 t.show(leash); 184 } else if (isEnter && isMainRoot || isSideRoot) { 185 t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top); 186 t.setWindowCrop(leash, change.getEndAbsBounds().width(), 187 change.getEndAbsBounds().height()); 188 } else if (isDivider) { 189 t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top); 190 t.setLayer(leash, Integer.MAX_VALUE); 191 t.show(leash); 192 } 193 194 // We want to use child tasks to animate so ignore split root container and non task 195 // except divider change. 196 if (isTopRoot || isMainRoot || isSideRoot 197 || (change.getTaskInfo() == null && !isDivider)) { 198 continue; 199 } 200 if (isEnter && mPendingEnter.mResizeAnim) { 201 // We will run animation in next transition so skip anim here 202 continue; 203 } else if (isPendingDismiss(transition) 204 && mPendingDismiss.mReason == EXIT_REASON_DRAG_DIVIDER) { 205 // TODO(b/280020345): need to refine animation for this but just skip anim now. 206 continue; 207 } 208 209 // Because cross fade might be looked more flicker during animation 210 // (surface become black in middle of animation), we only do fade-out 211 // and show opening surface directly. 212 boolean isOpening = TransitionUtil.isOpeningType(info.getType()); 213 if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) { 214 // fade out 215 startFadeAnimation(leash, false /* show */); 216 } else if (mode == TRANSIT_CHANGE && change.getSnapshot() != null) { 217 t.reparent(change.getSnapshot(), info.getRoot(rootIdx).getLeash()); 218 // Ensure snapshot it on the top of all transition surfaces 219 t.setLayer(change.getSnapshot(), info.getChanges().size() + 1); 220 t.setPosition(change.getSnapshot(), change.getStartAbsBounds().left, 221 change.getStartAbsBounds().top); 222 t.show(change.getSnapshot()); 223 startFadeAnimation(change.getSnapshot(), false /* show */); 224 } 225 } 226 t.apply(); 227 onFinish(null /* wct */); 228 } 229 230 /** Play animation for drag divider dismiss transition. */ playDragDismissAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull WindowContainerToken toTopRoot, @NonNull SplitDecorManager toTopDecor, @NonNull WindowContainerToken topRoot)231 void playDragDismissAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 232 @NonNull SurfaceControl.Transaction startTransaction, 233 @NonNull SurfaceControl.Transaction finishTransaction, 234 @NonNull Transitions.TransitionFinishCallback finishCallback, 235 @NonNull WindowContainerToken toTopRoot, @NonNull SplitDecorManager toTopDecor, 236 @NonNull WindowContainerToken topRoot) { 237 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playDragDismissAnimation: transition=%d", 238 info.getDebugId()); 239 initTransition(transition, finishTransaction, finishCallback); 240 241 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 242 final TransitionInfo.Change change = info.getChanges().get(i); 243 final SurfaceControl leash = change.getLeash(); 244 245 if (toTopRoot.equals(change.getContainer())) { 246 startTransaction.setAlpha(leash, 1.f); 247 startTransaction.show(leash); 248 249 ValueAnimator va = new ValueAnimator(); 250 mAnimations.add(va); 251 252 toTopDecor.onResized(startTransaction, animated -> { 253 mAnimations.remove(va); 254 if (animated) { 255 mTransitions.getMainExecutor().execute(() -> { 256 onFinish(null /* wct */); 257 }); 258 } 259 }); 260 } else if (topRoot.equals(change.getContainer())) { 261 // Ensure it on top of all changes in transition. 262 startTransaction.setLayer(leash, Integer.MAX_VALUE); 263 startTransaction.setAlpha(leash, 1.f); 264 startTransaction.show(leash); 265 } 266 } 267 startTransaction.apply(); 268 onFinish(null /* wct */); 269 } 270 271 /** Play animation for resize transition. */ playResizeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull Map<WindowContainerToken, SplitDecorManager> rootDecorMap)272 void playResizeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 273 @NonNull SurfaceControl.Transaction startTransaction, 274 @NonNull SurfaceControl.Transaction finishTransaction, 275 @NonNull Transitions.TransitionFinishCallback finishCallback, 276 @NonNull Map<WindowContainerToken, SplitDecorManager> rootDecorMap) { 277 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playResizeAnimation: transition=%d", info.getDebugId()); 278 initTransition(transition, finishTransaction, finishCallback); 279 280 Set<WindowContainerToken> rootDecorKeys = rootDecorMap.keySet(); 281 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 282 final TransitionInfo.Change change = info.getChanges().get(i); 283 if (rootDecorKeys.contains(change.getContainer())) { 284 final SurfaceControl leash = change.getLeash(); 285 startTransaction.setPosition(leash, change.getEndAbsBounds().left, 286 change.getEndAbsBounds().top); 287 startTransaction.setWindowCrop(leash, change.getEndAbsBounds().width(), 288 change.getEndAbsBounds().height()); 289 290 SplitDecorManager decor = rootDecorMap.get(change.getContainer()); 291 292 // This is to ensure onFinished be called after all animations ended. 293 ValueAnimator va = new ValueAnimator(); 294 mAnimations.add(va); 295 296 decor.setScreenshotIfNeeded(change.getSnapshot(), startTransaction); 297 decor.onResized(startTransaction, animated -> { 298 mAnimations.remove(va); 299 if (animated) { 300 mTransitions.getMainExecutor().execute(() -> { 301 onFinish(null /* wct */); 302 }); 303 } 304 }); 305 } 306 } 307 308 startTransaction.apply(); 309 onFinish(null /* wct */); 310 } 311 isPendingTransition(IBinder transition)312 boolean isPendingTransition(IBinder transition) { 313 return getPendingTransition(transition) != null; 314 } 315 isPendingEnter(IBinder transition)316 boolean isPendingEnter(IBinder transition) { 317 return mPendingEnter != null && mPendingEnter.mTransition == transition; 318 } 319 isPendingDismiss(IBinder transition)320 boolean isPendingDismiss(IBinder transition) { 321 return mPendingDismiss != null && mPendingDismiss.mTransition == transition; 322 } 323 isPendingResize(IBinder transition)324 boolean isPendingResize(IBinder transition) { 325 return mPendingResize != null && mPendingResize.mTransition == transition; 326 } 327 isPendingPassThrough(IBinder transition)328 boolean isPendingPassThrough(IBinder transition) { 329 return mPendingRemotePassthrough != null && 330 mPendingRemotePassthrough.mTransition == transition; 331 } 332 333 @Nullable getPendingTransition(IBinder transition)334 private TransitSession getPendingTransition(IBinder transition) { 335 if (isPendingEnter(transition)) { 336 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved enter transition"); 337 return mPendingEnter; 338 } else if (isPendingDismiss(transition)) { 339 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved dismiss transition"); 340 return mPendingDismiss; 341 } else if (isPendingResize(transition)) { 342 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved resize transition"); 343 return mPendingResize; 344 } else if (isPendingPassThrough(transition)) { 345 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved passThrough transition"); 346 return mPendingRemotePassthrough; 347 } 348 return null; 349 } 350 startFullscreenTransition(WindowContainerTransaction wct, @Nullable RemoteTransition handler)351 void startFullscreenTransition(WindowContainerTransaction wct, 352 @Nullable RemoteTransition handler) { 353 OneShotRemoteHandler fullscreenHandler = 354 new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler); 355 fullscreenHandler.setTransition(mTransitions 356 .startTransition(TRANSIT_OPEN, wct, fullscreenHandler)); 357 } 358 359 360 /** Starts a transition to enter split with a remote transition animator. */ startEnterTransition( @indowManager.TransitionType int transitType, WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, Transitions.TransitionHandler handler, int extraTransitType, boolean resizeAnim, @SplitScreenConstants.PersistentSnapPosition int snapPosition)361 IBinder startEnterTransition( 362 @WindowManager.TransitionType int transitType, 363 WindowContainerTransaction wct, 364 @Nullable RemoteTransition remoteTransition, 365 Transitions.TransitionHandler handler, 366 int extraTransitType, boolean resizeAnim, 367 @SplitScreenConstants.PersistentSnapPosition int snapPosition) { 368 if (mPendingEnter != null) { 369 ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " 370 + " skip to start enter split transition since it already exist. "); 371 return null; 372 } 373 if (mSplitInvocationListenerExecutor != null && mSplitInvocationListener != null) { 374 mSplitInvocationListenerExecutor.execute(() -> mSplitInvocationListener 375 .onSplitAnimationInvoked(true /*animationRunning*/)); 376 } 377 final IBinder transition = mTransitions.startTransition(transitType, wct, handler); 378 setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim, 379 snapPosition); 380 return transition; 381 } 382 383 /** Sets a transition to enter split. */ setEnterTransition(@onNull IBinder transition, @Nullable RemoteTransition remoteTransition, int extraTransitType, boolean resizeAnim, int snapPosition)384 void setEnterTransition(@NonNull IBinder transition, 385 @Nullable RemoteTransition remoteTransition, 386 int extraTransitType, boolean resizeAnim, 387 int snapPosition) { 388 mPendingEnter = new EnterSession( 389 transition, remoteTransition, extraTransitType, resizeAnim, snapPosition); 390 391 ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " 392 + " deduced Enter split screen"); 393 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setEnterTransition: transitType=%d resize=%b", 394 extraTransitType, resizeAnim); 395 } 396 397 /** Sets a transition to enter split. */ setRemotePassThroughTransition(@onNull IBinder transition, @Nullable RemoteTransition remoteTransition)398 void setRemotePassThroughTransition(@NonNull IBinder transition, 399 @Nullable RemoteTransition remoteTransition) { 400 mPendingRemotePassthrough = new TransitSession( 401 transition, null, null, 402 remoteTransition, Transitions.TRANSIT_SPLIT_PASSTHROUGH); 403 404 ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " 405 + " deduced remote passthrough split screen"); 406 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setRemotePassThrough: transitType=%d remote=%s", 407 Transitions.TRANSIT_SPLIT_PASSTHROUGH, remoteTransition); 408 } 409 410 /** Starts a transition to dismiss split. */ startDismissTransition(WindowContainerTransaction wct, Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop, @SplitScreenController.ExitReason int reason)411 IBinder startDismissTransition(WindowContainerTransaction wct, 412 Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop, 413 @SplitScreenController.ExitReason int reason) { 414 if (mPendingDismiss != null) { 415 ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " 416 + " skip to start dismiss split transition since it already exist. reason to " 417 + " dismiss = %s", exitReasonToString(reason)); 418 return null; 419 } 420 final int type = reason == EXIT_REASON_DRAG_DIVIDER 421 ? TRANSIT_SPLIT_DISMISS_SNAP : TRANSIT_SPLIT_DISMISS; 422 IBinder transition = mTransitions.startTransition(type, wct, handler); 423 setDismissTransition(transition, dismissTop, reason); 424 return transition; 425 } 426 427 /** Sets a transition to dismiss split. */ setDismissTransition(@onNull IBinder transition, @SplitScreen.StageType int dismissTop, @SplitScreenController.ExitReason int reason)428 void setDismissTransition(@NonNull IBinder transition, @SplitScreen.StageType int dismissTop, 429 @SplitScreenController.ExitReason int reason) { 430 mPendingDismiss = new DismissSession(transition, reason, dismissTop); 431 432 ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " 433 + " deduced Dismiss due to %s. toTop=%s", 434 exitReasonToString(reason), stageTypeToString(dismissTop)); 435 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setDismissTransition: reason=%s dismissTop=%s", 436 exitReasonToString(reason), stageTypeToString(dismissTop)); 437 } 438 startResizeTransition(WindowContainerTransaction wct, Transitions.TransitionHandler handler, @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishCallback, @Nullable SplitDecorManager mainDecor, @Nullable SplitDecorManager sideDecor, @Nullable List<SplitDecorManager> decorManagers)439 IBinder startResizeTransition(WindowContainerTransaction wct, 440 Transitions.TransitionHandler handler, 441 @Nullable TransitionConsumedCallback consumedCallback, 442 @Nullable TransitionFinishedCallback finishCallback, 443 @Nullable SplitDecorManager mainDecor, @Nullable SplitDecorManager sideDecor, 444 @Nullable List<SplitDecorManager> decorManagers) { 445 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 446 " splitTransition deduced Resize split screen."); 447 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setResizeTransition: hasPendingResize=%b", 448 mPendingResize != null); 449 if (mPendingResize != null) { 450 mPendingResize.cancel(null); 451 if (enableFlexibleSplit()) { 452 for (SplitDecorManager stage : decorManagers) { 453 stage.cancelRunningAnimations(); 454 } 455 } else { 456 mainDecor.cancelRunningAnimations(); 457 sideDecor.cancelRunningAnimations(); 458 } 459 mAnimations.clear(); 460 onFinish(null /* wct */); 461 } 462 463 IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler); 464 mPendingResize = new TransitSession(transition, consumedCallback, finishCallback); 465 return transition; 466 } 467 mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback)468 void mergeAnimation(IBinder transition, TransitionInfo info, 469 SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, 470 IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) { 471 if (mergeTarget != mAnimatingTransition) return; 472 473 if (mActiveRemoteHandler != null) { 474 mActiveRemoteHandler.mergeAnimation(transition, info, startT, 475 finishT, mergeTarget, finishCallback); 476 } else { 477 for (int i = mAnimations.size() - 1; i >= 0; --i) { 478 final Animator anim = mAnimations.get(i); 479 mTransitions.getAnimExecutor().execute(anim::end); 480 } 481 } 482 } 483 end()484 boolean end() { 485 // If It's remote, there's nothing we can do right now. 486 if (mActiveRemoteHandler != null) return false; 487 for (int i = mAnimations.size() - 1; i >= 0; --i) { 488 final Animator anim = mAnimations.get(i); 489 mTransitions.getAnimExecutor().execute(anim::end); 490 } 491 return true; 492 } 493 onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)494 void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, 495 @Nullable SurfaceControl.Transaction finishT) { 496 if (isPendingEnter(transition)) { 497 if (!aborted) { 498 // An entering transition got merged, appends the rest operations to finish entering 499 // split screen. 500 mStageCoordinator.finishEnterSplitScreen(finishT); 501 } 502 503 mPendingEnter.onConsumed(aborted); 504 mPendingEnter = null; 505 mStageCoordinator.notifySplitAnimationFinished(); 506 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for enter transition"); 507 } else if (isPendingDismiss(transition)) { 508 mPendingDismiss.onConsumed(aborted); 509 mPendingDismiss = null; 510 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for dismiss transition"); 511 } else if (isPendingResize(transition)) { 512 mPendingResize.onConsumed(aborted); 513 mPendingResize = null; 514 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for resize transition"); 515 } else if (isPendingPassThrough(transition)) { 516 mPendingRemotePassthrough.onConsumed(aborted); 517 mPendingRemotePassthrough.mRemoteHandler.onTransitionConsumed(transition, aborted, 518 finishT); 519 mPendingRemotePassthrough = null; 520 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for passThrough transition"); 521 } 522 523 if (mActiveRemoteHandler != null) { 524 mActiveRemoteHandler.onTransitionConsumed(transition, aborted, finishT); 525 } 526 } 527 onFinish(WindowContainerTransaction wct)528 void onFinish(WindowContainerTransaction wct) { 529 if (!mAnimations.isEmpty()) return; 530 531 if (wct == null) wct = new WindowContainerTransaction(); 532 if (isPendingEnter(mAnimatingTransition)) { 533 mPendingEnter.onFinished(wct, mFinishTransaction); 534 mPendingEnter = null; 535 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for enter transition"); 536 } else if (isPendingDismiss(mAnimatingTransition)) { 537 mPendingDismiss.onFinished(wct, mFinishTransaction); 538 mPendingDismiss = null; 539 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for dismiss transition"); 540 } else if (isPendingResize(mAnimatingTransition)) { 541 mPendingResize.onFinished(wct, mFinishTransaction); 542 mPendingResize = null; 543 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for resize transition"); 544 } else if (isPendingPassThrough(mAnimatingTransition)) { 545 mPendingRemotePassthrough.onFinished(wct, mFinishTransaction); 546 mPendingRemotePassthrough = null; 547 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for passThrough transition"); 548 } 549 550 mActiveRemoteHandler = null; 551 mAnimatingTransition = null; 552 553 mOnFinish.run(); 554 if (mFinishCallback != null) { 555 Transitions.TransitionFinishCallback currentFinishCallback = mFinishCallback; 556 mFinishCallback = null; 557 currentFinishCallback.onTransitionFinished(wct /* wct */); 558 } 559 } 560 startFadeAnimation(@onNull SurfaceControl leash, boolean show)561 private void startFadeAnimation(@NonNull SurfaceControl leash, boolean show) { 562 final float end = show ? 1.f : 0.f; 563 final float start = 1.f - end; 564 final ValueAnimator va = ValueAnimator.ofFloat(start, end); 565 va.setDuration(FADE_DURATION); 566 va.setInterpolator(show ? ALPHA_IN : ALPHA_OUT); 567 va.addUpdateListener(animation -> { 568 float fraction = animation.getAnimatedFraction(); 569 final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); 570 transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction); 571 transaction.apply(); 572 mTransactionPool.release(transaction); 573 }); 574 va.addListener(new AnimatorListenerAdapter() { 575 @Override 576 public void onAnimationEnd(Animator animation) { 577 final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); 578 transaction.setAlpha(leash, end); 579 transaction.apply(); 580 mTransactionPool.release(transaction); 581 mTransitions.getMainExecutor().execute(() -> { 582 mAnimations.remove(va); 583 onFinish(null /* wct */); 584 }); 585 } 586 }); 587 mAnimations.add(va); 588 mTransitions.getAnimExecutor().execute(va::start); 589 } 590 registerSplitAnimListener(@onNull SplitScreen.SplitInvocationListener listener, @NonNull Executor executor)591 public void registerSplitAnimListener(@NonNull SplitScreen.SplitInvocationListener listener, 592 @NonNull Executor executor) { 593 mSplitInvocationListener = listener; 594 mSplitInvocationListenerExecutor = executor; 595 } 596 597 /** Calls when the transition got consumed. */ 598 interface TransitionConsumedCallback { onConsumed(boolean aborted)599 void onConsumed(boolean aborted); 600 } 601 602 /** Calls when the transition finished. */ 603 interface TransitionFinishedCallback { onFinished(WindowContainerTransaction wct, SurfaceControl.Transaction t)604 void onFinished(WindowContainerTransaction wct, SurfaceControl.Transaction t); 605 } 606 607 /** Session for a transition and its clean-up callback. */ 608 class TransitSession { 609 final IBinder mTransition; 610 TransitionConsumedCallback mConsumedCallback; 611 TransitionFinishedCallback mFinishedCallback; 612 OneShotRemoteHandler mRemoteHandler; 613 614 /** Whether the transition was canceled. */ 615 boolean mCanceled; 616 617 /** A note for extra transit type, to help indicate custom transition. */ 618 final int mExtraTransitType; 619 TransitSession(IBinder transition, @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishedCallback)620 TransitSession(IBinder transition, 621 @Nullable TransitionConsumedCallback consumedCallback, 622 @Nullable TransitionFinishedCallback finishedCallback) { 623 this(transition, consumedCallback, finishedCallback, null /* remoteTransition */, 0); 624 } 625 TransitSession(IBinder transition, @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishedCallback, @Nullable RemoteTransition remoteTransition, int extraTransitType)626 TransitSession(IBinder transition, 627 @Nullable TransitionConsumedCallback consumedCallback, 628 @Nullable TransitionFinishedCallback finishedCallback, 629 @Nullable RemoteTransition remoteTransition, int extraTransitType) { 630 mTransition = transition; 631 mConsumedCallback = consumedCallback; 632 mFinishedCallback = finishedCallback; 633 634 if (remoteTransition != null) { 635 // Wrapping the remote transition for ease-of-use. (OneShot handles all the binder 636 // linking/death stuff) 637 mRemoteHandler = new OneShotRemoteHandler( 638 mTransitions.getMainExecutor(), remoteTransition); 639 mRemoteHandler.setTransition(transition); 640 } 641 mExtraTransitType = extraTransitType; 642 } 643 644 /** Sets transition consumed callback. */ setConsumedCallback(@ullable TransitionConsumedCallback callback)645 void setConsumedCallback(@Nullable TransitionConsumedCallback callback) { 646 mConsumedCallback = callback; 647 } 648 649 /** Sets transition finished callback. */ setFinishedCallback(@ullable TransitionFinishedCallback callback)650 void setFinishedCallback(@Nullable TransitionFinishedCallback callback) { 651 mFinishedCallback = callback; 652 } 653 654 /** 655 * Cancels the transition. This should be called before playing animation. A canceled 656 * transition will skip playing animation. 657 * 658 * @param finishedCb new finish callback to override. 659 */ cancel(@ullable TransitionFinishedCallback finishedCb)660 void cancel(@Nullable TransitionFinishedCallback finishedCb) { 661 mCanceled = true; 662 setFinishedCallback(finishedCb); 663 } 664 onConsumed(boolean aborted)665 void onConsumed(boolean aborted) { 666 if (mConsumedCallback != null) { 667 mConsumedCallback.onConsumed(aborted); 668 } 669 } 670 onFinished(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT)671 void onFinished(WindowContainerTransaction finishWct, 672 SurfaceControl.Transaction finishT) { 673 if (mFinishedCallback != null) { 674 mFinishedCallback.onFinished(finishWct, finishT); 675 } 676 } 677 } 678 679 /** Bundled information of enter transition. */ 680 class EnterSession extends TransitSession { 681 final boolean mResizeAnim; 682 /** The starting snap position we'll enter into with this transition. */ 683 final @SplitScreenConstants.PersistentSnapPosition int mEnteringPosition; 684 EnterSession(IBinder transition, @Nullable RemoteTransition remoteTransition, int extraTransitType, boolean resizeAnim, int snapPosition)685 EnterSession(IBinder transition, 686 @Nullable RemoteTransition remoteTransition, 687 int extraTransitType, boolean resizeAnim, int snapPosition) { 688 super(transition, null /* consumedCallback */, null /* finishedCallback */, 689 remoteTransition, extraTransitType); 690 this.mResizeAnim = resizeAnim; 691 this.mEnteringPosition = snapPosition; 692 } 693 } 694 695 /** Bundled information of dismiss transition. */ 696 class DismissSession extends TransitSession { 697 final int mReason; 698 final @SplitScreen.StageType int mDismissTop; 699 DismissSession(IBinder transition, int reason, int dismissTop)700 DismissSession(IBinder transition, int reason, int dismissTop) { 701 super(transition, null /* consumedCallback */, null /* finishedCallback */); 702 this.mReason = reason; 703 this.mDismissTop = dismissTop; 704 } 705 } 706 } 707