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