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 import static android.view.WindowManager.TRANSIT_TO_FRONT; 24 25 import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; 26 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; 27 import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString; 28 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS; 29 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP; 30 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE; 31 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN; 32 33 import android.animation.Animator; 34 import android.animation.AnimatorListenerAdapter; 35 import android.animation.ValueAnimator; 36 import android.annotation.NonNull; 37 import android.annotation.Nullable; 38 import android.graphics.Rect; 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 import android.window.WindowContainerTransactionCallback; 47 48 import com.android.internal.protolog.common.ProtoLog; 49 import com.android.wm.shell.common.TransactionPool; 50 import com.android.wm.shell.common.split.SplitDecorManager; 51 import com.android.wm.shell.protolog.ShellProtoLogGroup; 52 import com.android.wm.shell.transition.OneShotRemoteHandler; 53 import com.android.wm.shell.transition.Transitions; 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 DismissTransition mPendingDismiss = null; 66 TransitSession mPendingEnter = null; 67 TransitSession mPendingRecent = null; 68 TransitSession mPendingResize = null; 69 70 private IBinder mAnimatingTransition = null; 71 OneShotRemoteHandler mPendingRemoteHandler = null; 72 private OneShotRemoteHandler mActiveRemoteHandler = null; 73 74 private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish; 75 76 /** Keeps track of currently running animations */ 77 private final ArrayList<Animator> mAnimations = new ArrayList<>(); 78 private final StageCoordinator mStageCoordinator; 79 80 private Transitions.TransitionFinishCallback mFinishCallback = null; 81 private SurfaceControl.Transaction mFinishTransaction; 82 SplitScreenTransitions(@onNull TransactionPool pool, @NonNull Transitions transitions, @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator)83 SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions, 84 @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) { 85 mTransactionPool = pool; 86 mTransitions = transitions; 87 mOnFinish = onFinishCallback; 88 mStageCoordinator = stageCoordinator; 89 } 90 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)91 void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 92 @NonNull SurfaceControl.Transaction startTransaction, 93 @NonNull SurfaceControl.Transaction finishTransaction, 94 @NonNull Transitions.TransitionFinishCallback finishCallback, 95 @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, 96 @NonNull WindowContainerToken topRoot) { 97 mFinishCallback = finishCallback; 98 mAnimatingTransition = transition; 99 mFinishTransaction = finishTransaction; 100 if (mPendingRemoteHandler != null) { 101 mPendingRemoteHandler.startAnimation(transition, info, startTransaction, 102 finishTransaction, mRemoteFinishCB); 103 mActiveRemoteHandler = mPendingRemoteHandler; 104 mPendingRemoteHandler = null; 105 return; 106 } 107 playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot, topRoot); 108 } 109 playInternalAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot)110 private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 111 @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot, 112 @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) { 113 final TransitSession pendingTransition = getPendingTransition(transition); 114 if (pendingTransition != null && pendingTransition.mCanceled) { 115 // The pending transition was canceled, so skip playing animation. 116 t.apply(); 117 onFinish(null /* wct */, null /* wctCB */); 118 return; 119 } 120 121 // Play some place-holder fade animations 122 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 123 final TransitionInfo.Change change = info.getChanges().get(i); 124 final SurfaceControl leash = change.getLeash(); 125 final int mode = info.getChanges().get(i).getMode(); 126 127 if (mode == TRANSIT_CHANGE) { 128 if (change.getParent() != null) { 129 // This is probably reparented, so we want the parent to be immediately visible 130 final TransitionInfo.Change parentChange = info.getChange(change.getParent()); 131 t.show(parentChange.getLeash()); 132 t.setAlpha(parentChange.getLeash(), 1.f); 133 // and then animate this layer outside the parent (since, for example, this is 134 // the home task animating from fullscreen to part-screen). 135 t.reparent(leash, info.getRootLeash()); 136 t.setLayer(leash, info.getChanges().size() - i); 137 // build the finish reparent/reposition 138 mFinishTransaction.reparent(leash, parentChange.getLeash()); 139 mFinishTransaction.setPosition(leash, 140 change.getEndRelOffset().x, change.getEndRelOffset().y); 141 } 142 // TODO(shell-transitions): screenshot here 143 final Rect startBounds = new Rect(change.getStartAbsBounds()); 144 final Rect endBounds = new Rect(change.getEndAbsBounds()); 145 startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); 146 endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); 147 startExampleResizeAnimation(leash, startBounds, endBounds); 148 } 149 boolean isRootOrSplitSideRoot = change.getParent() == null 150 || topRoot.equals(change.getParent()); 151 // For enter or exit, we only want to animate the side roots but not the top-root. 152 if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer())) { 153 continue; 154 } 155 156 if (isPendingEnter(transition) && (mainRoot.equals(change.getContainer()) 157 || sideRoot.equals(change.getContainer()))) { 158 t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top); 159 t.setWindowCrop(leash, change.getEndAbsBounds().width(), 160 change.getEndAbsBounds().height()); 161 } 162 boolean isOpening = isOpeningTransition(info); 163 if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { 164 // fade in 165 startExampleAnimation(leash, true /* show */); 166 } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) { 167 // fade out 168 if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) { 169 // Dismissing via snap-to-top/bottom means that the dismissed task is already 170 // not-visible (usually cropped to oblivion) so immediately set its alpha to 0 171 // and don't animate it so it doesn't pop-in when reparented. 172 t.setAlpha(leash, 0.f); 173 } else { 174 startExampleAnimation(leash, false /* show */); 175 } 176 } 177 } 178 t.apply(); 179 onFinish(null /* wct */, null /* wctCB */); 180 } 181 applyResizeTransition(@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)182 void applyResizeTransition(@NonNull IBinder transition, @NonNull TransitionInfo info, 183 @NonNull SurfaceControl.Transaction startTransaction, 184 @NonNull SurfaceControl.Transaction finishTransaction, 185 @NonNull Transitions.TransitionFinishCallback finishCallback, 186 @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, 187 @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) { 188 mFinishCallback = finishCallback; 189 mAnimatingTransition = transition; 190 mFinishTransaction = finishTransaction; 191 192 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 193 final TransitionInfo.Change change = info.getChanges().get(i); 194 if (mainRoot.equals(change.getContainer()) || sideRoot.equals(change.getContainer())) { 195 final SurfaceControl leash = change.getLeash(); 196 startTransaction.setPosition(leash, change.getEndAbsBounds().left, 197 change.getEndAbsBounds().top); 198 startTransaction.setWindowCrop(leash, change.getEndAbsBounds().width(), 199 change.getEndAbsBounds().height()); 200 201 SplitDecorManager decor = mainRoot.equals(change.getContainer()) 202 ? mainDecor : sideDecor; 203 ValueAnimator va = new ValueAnimator(); 204 mAnimations.add(va); 205 decor.setScreenshotIfNeeded(change.getSnapshot(), startTransaction); 206 decor.onResized(startTransaction, () -> { 207 mTransitions.getMainExecutor().execute(() -> { 208 mAnimations.remove(va); 209 onFinish(null /* wct */, null /* wctCB */); 210 }); 211 }); 212 } 213 } 214 215 startTransaction.apply(); 216 onFinish(null /* wct */, null /* wctCB */); 217 } 218 isPendingTransition(IBinder transition)219 boolean isPendingTransition(IBinder transition) { 220 return getPendingTransition(transition) != null; 221 } 222 isPendingEnter(IBinder transition)223 boolean isPendingEnter(IBinder transition) { 224 return mPendingEnter != null && mPendingEnter.mTransition == transition; 225 } 226 isPendingRecent(IBinder transition)227 boolean isPendingRecent(IBinder transition) { 228 return mPendingRecent != null && mPendingRecent.mTransition == transition; 229 } 230 isPendingDismiss(IBinder transition)231 boolean isPendingDismiss(IBinder transition) { 232 return mPendingDismiss != null && mPendingDismiss.mTransition == transition; 233 } 234 isPendingResize(IBinder transition)235 boolean isPendingResize(IBinder transition) { 236 return mPendingResize != null && mPendingResize.mTransition == transition; 237 } 238 239 @Nullable getPendingTransition(IBinder transition)240 private TransitSession getPendingTransition(IBinder transition) { 241 if (isPendingEnter(transition)) { 242 return mPendingEnter; 243 } else if (isPendingRecent(transition)) { 244 return mPendingRecent; 245 } else if (isPendingDismiss(transition)) { 246 return mPendingDismiss; 247 } else if (isPendingResize(transition)) { 248 return mPendingResize; 249 } 250 251 return null; 252 } 253 254 255 /** Starts a transition to enter split with a remote transition animator. */ startEnterTransition( @indowManager.TransitionType int transitType, WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, Transitions.TransitionHandler handler, @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishedCallback)256 IBinder startEnterTransition( 257 @WindowManager.TransitionType int transitType, 258 WindowContainerTransaction wct, 259 @Nullable RemoteTransition remoteTransition, 260 Transitions.TransitionHandler handler, 261 @Nullable TransitionConsumedCallback consumedCallback, 262 @Nullable TransitionFinishedCallback finishedCallback) { 263 final IBinder transition = mTransitions.startTransition(transitType, wct, handler); 264 setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback); 265 return transition; 266 } 267 268 /** Sets a transition to enter split. */ setEnterTransition(@onNull IBinder transition, @Nullable RemoteTransition remoteTransition, @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishedCallback)269 void setEnterTransition(@NonNull IBinder transition, 270 @Nullable RemoteTransition remoteTransition, 271 @Nullable TransitionConsumedCallback consumedCallback, 272 @Nullable TransitionFinishedCallback finishedCallback) { 273 mPendingEnter = new TransitSession(transition, consumedCallback, finishedCallback); 274 275 if (remoteTransition != null) { 276 // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) 277 mPendingRemoteHandler = new OneShotRemoteHandler( 278 mTransitions.getMainExecutor(), remoteTransition); 279 mPendingRemoteHandler.setTransition(transition); 280 } 281 282 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " 283 + " deduced Enter split screen"); 284 } 285 286 /** Starts a transition to dismiss split. */ startDismissTransition(WindowContainerTransaction wct, Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop, @SplitScreenController.ExitReason int reason)287 IBinder startDismissTransition(WindowContainerTransaction wct, 288 Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop, 289 @SplitScreenController.ExitReason int reason) { 290 final int type = reason == EXIT_REASON_DRAG_DIVIDER 291 ? TRANSIT_SPLIT_DISMISS_SNAP : TRANSIT_SPLIT_DISMISS; 292 IBinder transition = mTransitions.startTransition(type, wct, handler); 293 setDismissTransition(transition, dismissTop, reason); 294 return transition; 295 } 296 297 /** Sets a transition to dismiss split. */ setDismissTransition(@onNull IBinder transition, @SplitScreen.StageType int dismissTop, @SplitScreenController.ExitReason int reason)298 void setDismissTransition(@NonNull IBinder transition, @SplitScreen.StageType int dismissTop, 299 @SplitScreenController.ExitReason int reason) { 300 mPendingDismiss = new DismissTransition(transition, reason, dismissTop); 301 302 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " 303 + " deduced Dismiss due to %s. toTop=%s", 304 exitReasonToString(reason), stageTypeToString(dismissTop)); 305 } 306 startResizeTransition(WindowContainerTransaction wct, Transitions.TransitionHandler handler, @Nullable TransitionFinishedCallback finishCallback)307 IBinder startResizeTransition(WindowContainerTransaction wct, 308 Transitions.TransitionHandler handler, 309 @Nullable TransitionFinishedCallback finishCallback) { 310 IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler); 311 setResizeTransition(transition, finishCallback); 312 return transition; 313 } 314 setResizeTransition(@onNull IBinder transition, @Nullable TransitionFinishedCallback finishCallback)315 void setResizeTransition(@NonNull IBinder transition, 316 @Nullable TransitionFinishedCallback finishCallback) { 317 mPendingResize = new TransitSession(transition, null /* consumedCb */, finishCallback); 318 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " 319 + " deduced Resize split screen"); 320 } 321 setRecentTransition(@onNull IBinder transition, @Nullable RemoteTransition remoteTransition, @Nullable TransitionFinishedCallback finishCallback)322 void setRecentTransition(@NonNull IBinder transition, 323 @Nullable RemoteTransition remoteTransition, 324 @Nullable TransitionFinishedCallback finishCallback) { 325 mPendingRecent = new TransitSession(transition, null /* consumedCb */, finishCallback); 326 327 if (remoteTransition != null) { 328 // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) 329 mPendingRemoteHandler = new OneShotRemoteHandler( 330 mTransitions.getMainExecutor(), remoteTransition); 331 mPendingRemoteHandler.setTransition(transition); 332 } 333 334 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " 335 + " deduced Enter recent panel"); 336 } 337 mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback)338 void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, 339 IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) { 340 if (mergeTarget != mAnimatingTransition) return; 341 342 if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) { 343 // Since there's an entering transition merged, recent transition no longer 344 // need to handle entering split screen after the transition finished. 345 mPendingRecent.setFinishedCallback(null); 346 } 347 348 if (mActiveRemoteHandler != null) { 349 mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); 350 } else { 351 for (int i = mAnimations.size() - 1; i >= 0; --i) { 352 final Animator anim = mAnimations.get(i); 353 mTransitions.getAnimExecutor().execute(anim::end); 354 } 355 } 356 } 357 end()358 boolean end() { 359 // If It's remote, there's nothing we can do right now. 360 if (mActiveRemoteHandler != null) return false; 361 for (int i = mAnimations.size() - 1; i >= 0; --i) { 362 final Animator anim = mAnimations.get(i); 363 mTransitions.getAnimExecutor().execute(anim::end); 364 } 365 return true; 366 } 367 onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)368 void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, 369 @Nullable SurfaceControl.Transaction finishT) { 370 if (isPendingEnter(transition)) { 371 if (!aborted) { 372 // An entering transition got merged, appends the rest operations to finish entering 373 // split screen. 374 mStageCoordinator.finishEnterSplitScreen(finishT); 375 mPendingRemoteHandler = null; 376 } 377 378 mPendingEnter.onConsumed(aborted); 379 mPendingEnter = null; 380 mPendingRemoteHandler = null; 381 } else if (isPendingDismiss(transition)) { 382 mPendingDismiss.onConsumed(aborted); 383 mPendingDismiss = null; 384 } else if (isPendingRecent(transition)) { 385 mPendingRecent.onConsumed(aborted); 386 mPendingRecent = null; 387 mPendingRemoteHandler = null; 388 } else if (isPendingResize(transition)) { 389 mPendingResize.onConsumed(aborted); 390 mPendingResize = null; 391 } 392 } 393 onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB)394 void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) { 395 if (!mAnimations.isEmpty()) return; 396 397 if (wct == null) wct = new WindowContainerTransaction(); 398 if (isPendingEnter(mAnimatingTransition)) { 399 mPendingEnter.onFinished(wct, mFinishTransaction); 400 mPendingEnter = null; 401 } else if (isPendingRecent(mAnimatingTransition)) { 402 mPendingRecent.onFinished(wct, mFinishTransaction); 403 mPendingRecent = null; 404 } else if (isPendingDismiss(mAnimatingTransition)) { 405 mPendingDismiss.onFinished(wct, mFinishTransaction); 406 mPendingDismiss = null; 407 } else if (isPendingResize(mAnimatingTransition)) { 408 mPendingResize.onFinished(wct, mFinishTransaction); 409 mPendingResize = null; 410 } 411 412 mPendingRemoteHandler = null; 413 mActiveRemoteHandler = null; 414 mAnimatingTransition = null; 415 416 mOnFinish.run(); 417 if (mFinishCallback != null) { 418 mFinishCallback.onTransitionFinished(wct /* wct */, wctCB /* wctCB */); 419 mFinishCallback = null; 420 } 421 } 422 423 // TODO(shell-transitions): real animations startExampleAnimation(@onNull SurfaceControl leash, boolean show)424 private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) { 425 final float end = show ? 1.f : 0.f; 426 final float start = 1.f - end; 427 final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); 428 final ValueAnimator va = ValueAnimator.ofFloat(start, end); 429 va.setDuration(500); 430 va.addUpdateListener(animation -> { 431 float fraction = animation.getAnimatedFraction(); 432 transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction); 433 transaction.apply(); 434 }); 435 final Runnable finisher = () -> { 436 transaction.setAlpha(leash, end); 437 transaction.apply(); 438 mTransactionPool.release(transaction); 439 mTransitions.getMainExecutor().execute(() -> { 440 mAnimations.remove(va); 441 onFinish(null /* wct */, null /* wctCB */); 442 }); 443 }; 444 va.addListener(new AnimatorListenerAdapter() { 445 @Override 446 public void onAnimationEnd(Animator animation) { 447 finisher.run(); 448 } 449 450 @Override 451 public void onAnimationCancel(Animator animation) { 452 finisher.run(); 453 } 454 }); 455 mAnimations.add(va); 456 mTransitions.getAnimExecutor().execute(va::start); 457 } 458 459 // TODO(shell-transitions): real animations startExampleResizeAnimation(@onNull SurfaceControl leash, @NonNull Rect startBounds, @NonNull Rect endBounds)460 private void startExampleResizeAnimation(@NonNull SurfaceControl leash, 461 @NonNull Rect startBounds, @NonNull Rect endBounds) { 462 final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); 463 final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f); 464 va.setDuration(500); 465 va.addUpdateListener(animation -> { 466 float fraction = animation.getAnimatedFraction(); 467 transaction.setWindowCrop(leash, 468 (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction), 469 (int) (startBounds.height() * (1.f - fraction) 470 + endBounds.height() * fraction)); 471 transaction.setPosition(leash, 472 startBounds.left * (1.f - fraction) + endBounds.left * fraction, 473 startBounds.top * (1.f - fraction) + endBounds.top * fraction); 474 transaction.apply(); 475 }); 476 final Runnable finisher = () -> { 477 transaction.setWindowCrop(leash, 0, 0); 478 transaction.setPosition(leash, endBounds.left, endBounds.top); 479 transaction.apply(); 480 mTransactionPool.release(transaction); 481 mTransitions.getMainExecutor().execute(() -> { 482 mAnimations.remove(va); 483 onFinish(null /* wct */, null /* wctCB */); 484 }); 485 }; 486 va.addListener(new AnimatorListenerAdapter() { 487 @Override 488 public void onAnimationEnd(Animator animation) { 489 finisher.run(); 490 } 491 492 @Override 493 public void onAnimationCancel(Animator animation) { 494 finisher.run(); 495 } 496 }); 497 mAnimations.add(va); 498 mTransitions.getAnimExecutor().execute(va::start); 499 } 500 isOpeningTransition(TransitionInfo info)501 private boolean isOpeningTransition(TransitionInfo info) { 502 return Transitions.isOpeningType(info.getType()) 503 || info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE 504 || info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN; 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 static class TransitSession { 519 final IBinder mTransition; 520 TransitionConsumedCallback mConsumedCallback; 521 TransitionFinishedCallback mFinishedCallback; 522 523 /** Whether the transition was canceled. */ 524 boolean mCanceled; 525 TransitSession(IBinder transition, @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishedCallback)526 TransitSession(IBinder transition, 527 @Nullable TransitionConsumedCallback consumedCallback, 528 @Nullable TransitionFinishedCallback finishedCallback) { 529 mTransition = transition; 530 mConsumedCallback = consumedCallback; 531 mFinishedCallback = finishedCallback; 532 533 } 534 535 /** Sets transition consumed callback. */ setConsumedCallback(@ullable TransitionConsumedCallback callback)536 void setConsumedCallback(@Nullable TransitionConsumedCallback callback) { 537 mConsumedCallback = callback; 538 } 539 540 /** Sets transition finished callback. */ setFinishedCallback(@ullable TransitionFinishedCallback callback)541 void setFinishedCallback(@Nullable TransitionFinishedCallback callback) { 542 mFinishedCallback = callback; 543 } 544 545 /** 546 * Cancels the transition. This should be called before playing animation. A canceled 547 * transition will skip playing animation. 548 * 549 * @param finishedCb new finish callback to override. 550 */ cancel(@ullable TransitionFinishedCallback finishedCb)551 void cancel(@Nullable TransitionFinishedCallback finishedCb) { 552 mCanceled = true; 553 setFinishedCallback(finishedCb); 554 } 555 onConsumed(boolean aborted)556 void onConsumed(boolean aborted) { 557 if (mConsumedCallback != null) { 558 mConsumedCallback.onConsumed(aborted); 559 } 560 } 561 onFinished(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT)562 void onFinished(WindowContainerTransaction finishWct, 563 SurfaceControl.Transaction finishT) { 564 if (mFinishedCallback != null) { 565 mFinishedCallback.onFinished(finishWct, finishT); 566 } 567 } 568 } 569 570 /** Bundled information of dismiss transition. */ 571 static class DismissTransition extends TransitSession { 572 final int mReason; 573 final @SplitScreen.StageType int mDismissTop; 574 DismissTransition(IBinder transition, int reason, int dismissTop)575 DismissTransition(IBinder transition, int reason, int dismissTop) { 576 super(transition, null /* consumedCallback */, null /* finishedCallback */); 577 this.mReason = reason; 578 this.mDismissTop = dismissTop; 579 } 580 } 581 } 582