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