1 /* 2 * Copyright (C) 2023 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.pip2.phone; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 20 import static android.view.WindowManager.TRANSIT_CLOSE; 21 import static android.view.WindowManager.TRANSIT_OPEN; 22 import static android.view.WindowManager.TRANSIT_PIP; 23 import static android.view.WindowManager.TRANSIT_TO_BACK; 24 import static android.view.WindowManager.TRANSIT_TO_FRONT; 25 26 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; 27 import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP; 28 29 import android.animation.Animator; 30 import android.animation.AnimatorListenerAdapter; 31 import android.animation.ValueAnimator; 32 import android.annotation.NonNull; 33 import android.app.ActivityManager; 34 import android.app.PictureInPictureParams; 35 import android.content.Context; 36 import android.graphics.Rect; 37 import android.os.Bundle; 38 import android.os.IBinder; 39 import android.view.SurfaceControl; 40 import android.window.TransitionInfo; 41 import android.window.TransitionRequestInfo; 42 import android.window.WindowContainerToken; 43 import android.window.WindowContainerTransaction; 44 45 import androidx.annotation.Nullable; 46 47 import com.android.internal.util.Preconditions; 48 import com.android.wm.shell.ShellTaskOrganizer; 49 import com.android.wm.shell.common.pip.PipBoundsAlgorithm; 50 import com.android.wm.shell.common.pip.PipBoundsState; 51 import com.android.wm.shell.common.pip.PipMenuController; 52 import com.android.wm.shell.common.pip.PipUtils; 53 import com.android.wm.shell.pip.PipContentOverlay; 54 import com.android.wm.shell.pip.PipTransitionController; 55 import com.android.wm.shell.pip2.animation.PipAlphaAnimator; 56 import com.android.wm.shell.sysui.ShellInit; 57 import com.android.wm.shell.transition.Transitions; 58 59 /** 60 * Implementation of transitions for PiP on phone. 61 */ 62 public class PipTransition extends PipTransitionController implements 63 PipTransitionState.PipTransitionStateChangedListener { 64 private static final String TAG = PipTransition.class.getSimpleName(); 65 66 // Used when for ENTERING_PIP state update. 67 private static final String PIP_TASK_TOKEN = "pip_task_token"; 68 private static final String PIP_TASK_LEASH = "pip_task_leash"; 69 70 // Used for PiP CHANGING_BOUNDS state update. 71 static final String PIP_START_TX = "pip_start_tx"; 72 static final String PIP_FINISH_TX = "pip_finish_tx"; 73 static final String PIP_DESTINATION_BOUNDS = "pip_dest_bounds"; 74 75 /** 76 * The fixed start delay in ms when fading out the content overlay from bounds animation. 77 * The fadeout animation is guaranteed to start after the client has drawn under the new config. 78 */ 79 private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 400; 80 81 // 82 // Dependencies 83 // 84 85 private final Context mContext; 86 private final PipScheduler mPipScheduler; 87 private final PipTransitionState mPipTransitionState; 88 89 // 90 // Transition tokens 91 // 92 93 @Nullable 94 private IBinder mEnterTransition; 95 @Nullable 96 private IBinder mExitViaExpandTransition; 97 @Nullable 98 private IBinder mResizeTransition; 99 100 // 101 // Internal state and relevant cached info 102 // 103 104 @Nullable 105 private WindowContainerToken mPipTaskToken; 106 @Nullable 107 private SurfaceControl mPipLeash; 108 @Nullable 109 private Transitions.TransitionFinishCallback mFinishCallback; 110 PipTransition( Context context, @NonNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, PipBoundsState pipBoundsState, PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm, PipScheduler pipScheduler, PipTransitionState pipTransitionState)111 public PipTransition( 112 Context context, 113 @NonNull ShellInit shellInit, 114 @NonNull ShellTaskOrganizer shellTaskOrganizer, 115 @NonNull Transitions transitions, 116 PipBoundsState pipBoundsState, 117 PipMenuController pipMenuController, 118 PipBoundsAlgorithm pipBoundsAlgorithm, 119 PipScheduler pipScheduler, 120 PipTransitionState pipTransitionState) { 121 super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController, 122 pipBoundsAlgorithm); 123 124 mContext = context; 125 mPipScheduler = pipScheduler; 126 mPipScheduler.setPipTransitionController(this); 127 mPipTransitionState = pipTransitionState; 128 mPipTransitionState.addPipTransitionStateChangedListener(this); 129 } 130 131 @Override onInit()132 protected void onInit() { 133 if (PipUtils.isPip2ExperimentEnabled()) { 134 mTransitions.addHandler(this); 135 } 136 } 137 138 // 139 // Transition collection stage lifecycle hooks 140 // 141 142 @Override startExitTransition(int type, WindowContainerTransaction out, @Nullable Rect destinationBounds)143 public void startExitTransition(int type, WindowContainerTransaction out, 144 @Nullable Rect destinationBounds) { 145 if (out == null) { 146 return; 147 } 148 IBinder transition = mTransitions.startTransition(type, out, this); 149 if (type == TRANSIT_EXIT_PIP) { 150 mExitViaExpandTransition = transition; 151 } 152 } 153 154 @Override startResizeTransition(WindowContainerTransaction wct)155 public void startResizeTransition(WindowContainerTransaction wct) { 156 if (wct == null) { 157 return; 158 } 159 mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this); 160 } 161 162 @Nullable 163 @Override handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)164 public WindowContainerTransaction handleRequest(@NonNull IBinder transition, 165 @NonNull TransitionRequestInfo request) { 166 if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) { 167 mEnterTransition = transition; 168 return getEnterPipTransaction(transition, request); 169 } 170 return null; 171 } 172 173 @Override augmentRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWct)174 public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request, 175 @NonNull WindowContainerTransaction outWct) { 176 if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) { 177 outWct.merge(getEnterPipTransaction(transition, request), true /* transfer */); 178 mEnterTransition = transition; 179 } 180 } 181 182 // 183 // Transition playing stage lifecycle hooks 184 // 185 186 @Override mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)187 public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 188 @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, 189 @NonNull Transitions.TransitionFinishCallback finishCallback) {} 190 191 @Override onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)192 public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, 193 @Nullable SurfaceControl.Transaction finishT) {} 194 195 @Override startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)196 public boolean startAnimation(@NonNull IBinder transition, 197 @NonNull TransitionInfo info, 198 @NonNull SurfaceControl.Transaction startTransaction, 199 @NonNull SurfaceControl.Transaction finishTransaction, 200 @NonNull Transitions.TransitionFinishCallback finishCallback) { 201 if (transition == mEnterTransition || info.getType() == TRANSIT_PIP) { 202 mEnterTransition = null; 203 // If we are in swipe PiP to Home transition we are ENTERING_PIP as a jumpcut transition 204 // is being carried out. 205 TransitionInfo.Change pipChange = getPipChange(info); 206 207 // If there is no PiP change, exit this transition handler and potentially try others. 208 if (pipChange == null) return false; 209 210 Bundle extra = new Bundle(); 211 extra.putParcelable(PIP_TASK_TOKEN, pipChange.getContainer()); 212 extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash()); 213 mPipTransitionState.setState(PipTransitionState.ENTERING_PIP, extra); 214 215 if (mPipTransitionState.isInSwipePipToHomeTransition()) { 216 // If this is the second transition as a part of swipe PiP to home cuj, 217 // handle this transition as a special case with no-op animation. 218 return handleSwipePipToHomeTransition(info, startTransaction, finishTransaction, 219 finishCallback); 220 } 221 if (isLegacyEnter(info)) { 222 // If this is a legacy-enter-pip (auto-enter is off and PiP activity went to pause), 223 // then we should run an ALPHA type (cross-fade) animation. 224 return startAlphaTypeEnterAnimation(info, startTransaction, finishTransaction, 225 finishCallback); 226 } 227 return startBoundsTypeEnterAnimation(info, startTransaction, finishTransaction, 228 finishCallback); 229 } else if (transition == mExitViaExpandTransition) { 230 mExitViaExpandTransition = null; 231 mPipTransitionState.setState(PipTransitionState.EXITING_PIP); 232 return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback); 233 } else if (transition == mResizeTransition) { 234 mResizeTransition = null; 235 return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback); 236 } 237 238 if (isRemovePipTransition(info)) { 239 return removePipImmediately(info, startTransaction, finishTransaction, finishCallback); 240 } 241 return false; 242 } 243 244 // 245 // Animation schedulers and entry points 246 // 247 startResizeAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)248 private boolean startResizeAnimation(@NonNull TransitionInfo info, 249 @NonNull SurfaceControl.Transaction startTransaction, 250 @NonNull SurfaceControl.Transaction finishTransaction, 251 @NonNull Transitions.TransitionFinishCallback finishCallback) { 252 TransitionInfo.Change pipChange = getPipChange(info); 253 if (pipChange == null) { 254 return false; 255 } 256 SurfaceControl pipLeash = pipChange.getLeash(); 257 258 // Even though the final bounds and crop are applied with finishTransaction since 259 // this is a visible change, we still need to handle the app draw coming in. Snapshot 260 // covering app draw during collection will be removed by startTransaction. So we make 261 // the crop equal to the final bounds and then let the current 262 // animator scale the leash back to starting bounds. 263 // Note: animator is responsible for applying the startTx but NOT finishTx. 264 startTransaction.setWindowCrop(pipLeash, pipChange.getEndAbsBounds().width(), 265 pipChange.getEndAbsBounds().height()); 266 267 // TODO: b/275910498 Couple this routine with a new implementation of the PiP animator. 268 // Classes interested in continuing the animation would subscribe to this state update 269 // getting info such as endBounds, startTx, and finishTx as an extra Bundle once 270 // animators are in place. Once done state needs to be updated to CHANGED_PIP_BOUNDS. 271 Bundle extra = new Bundle(); 272 extra.putParcelable(PIP_START_TX, startTransaction); 273 extra.putParcelable(PIP_FINISH_TX, finishTransaction); 274 extra.putParcelable(PIP_DESTINATION_BOUNDS, pipChange.getEndAbsBounds()); 275 276 mFinishCallback = finishCallback; 277 mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS, extra); 278 return true; 279 } 280 handleSwipePipToHomeTransition(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)281 private boolean handleSwipePipToHomeTransition(@NonNull TransitionInfo info, 282 @NonNull SurfaceControl.Transaction startTransaction, 283 @NonNull SurfaceControl.Transaction finishTransaction, 284 @NonNull Transitions.TransitionFinishCallback finishCallback) { 285 TransitionInfo.Change pipChange = getPipChange(info); 286 if (pipChange == null) { 287 return false; 288 } 289 WindowContainerToken pipTaskToken = pipChange.getContainer(); 290 SurfaceControl pipLeash = pipChange.getLeash(); 291 292 if (pipTaskToken == null || pipLeash == null) { 293 return false; 294 } 295 296 SurfaceControl overlayLeash = mPipTransitionState.getSwipePipToHomeOverlay(); 297 PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams; 298 299 Rect appBounds = mPipTransitionState.getSwipePipToHomeAppBounds(); 300 Rect destinationBounds = pipChange.getEndAbsBounds(); 301 302 float aspectRatio = pipChange.getTaskInfo().pictureInPictureParams.getAspectRatioFloat(); 303 304 // We fake the source rect hint when the one prvided by the app is invalid for 305 // the animation with an app icon overlay. 306 Rect animationSrcRectHint = overlayLeash == null ? params.getSourceRectHint() 307 : PipUtils.getEnterPipWithOverlaySrcRectHint(appBounds, aspectRatio); 308 309 WindowContainerTransaction finishWct = new WindowContainerTransaction(); 310 SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 311 312 final float scale = (float) destinationBounds.width() / animationSrcRectHint.width(); 313 startTransaction.setWindowCrop(pipLeash, animationSrcRectHint); 314 startTransaction.setPosition(pipLeash, 315 destinationBounds.left - animationSrcRectHint.left * scale, 316 destinationBounds.top - animationSrcRectHint.top * scale); 317 startTransaction.setScale(pipLeash, scale, scale); 318 319 if (overlayLeash != null) { 320 final int overlaySize = PipContentOverlay.PipAppIconOverlay.getOverlaySize( 321 mPipTransitionState.getSwipePipToHomeAppBounds(), destinationBounds); 322 323 // Overlay needs to be adjusted once a new draw comes in resetting surface transform. 324 tx.setScale(overlayLeash, 1f, 1f); 325 tx.setPosition(overlayLeash, (destinationBounds.width() - overlaySize) / 2f, 326 (destinationBounds.height() - overlaySize) / 2f); 327 } 328 startTransaction.apply(); 329 330 tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(), 331 this::onClientDrawAtTransitionEnd); 332 finishWct.setBoundsChangeTransaction(pipTaskToken, tx); 333 334 // Note that finishWct should be free of any actual WM state changes; we are using 335 // it for syncing with the client draw after delayed configuration changes are dispatched. 336 finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct); 337 return true; 338 } 339 startOverlayFadeoutAnimation()340 private void startOverlayFadeoutAnimation() { 341 ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f); 342 animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS); 343 animator.addListener(new AnimatorListenerAdapter() { 344 @Override 345 public void onAnimationEnd(Animator animation) { 346 super.onAnimationEnd(animation); 347 SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 348 tx.remove(mPipTransitionState.getSwipePipToHomeOverlay()); 349 tx.apply(); 350 351 // We have fully completed enter-PiP animation after the overlay is gone. 352 mPipTransitionState.setState(PipTransitionState.ENTERED_PIP); 353 } 354 }); 355 animator.addUpdateListener(animation -> { 356 float alpha = (float) animation.getAnimatedValue(); 357 SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 358 tx.setAlpha(mPipTransitionState.getSwipePipToHomeOverlay(), alpha).apply(); 359 }); 360 animator.start(); 361 } 362 startBoundsTypeEnterAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)363 private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info, 364 @NonNull SurfaceControl.Transaction startTransaction, 365 @NonNull SurfaceControl.Transaction finishTransaction, 366 @NonNull Transitions.TransitionFinishCallback finishCallback) { 367 TransitionInfo.Change pipChange = getPipChange(info); 368 if (pipChange == null) { 369 return false; 370 } 371 // cache the PiP task token and leash 372 WindowContainerToken pipTaskToken = pipChange.getContainer(); 373 374 startTransaction.apply(); 375 // TODO: b/275910498 Use a new implementation of the PiP animator here. 376 finishCallback.onTransitionFinished(null); 377 return true; 378 } 379 startAlphaTypeEnterAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)380 private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info, 381 @NonNull SurfaceControl.Transaction startTransaction, 382 @NonNull SurfaceControl.Transaction finishTransaction, 383 @NonNull Transitions.TransitionFinishCallback finishCallback) { 384 TransitionInfo.Change pipChange = getPipChange(info); 385 if (pipChange == null) { 386 return false; 387 } 388 389 Rect destinationBounds = pipChange.getEndAbsBounds(); 390 SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; 391 Preconditions.checkNotNull(pipLeash, "Leash is null for alpha transition."); 392 393 // Start transition with 0 alpha at the entry bounds. 394 startTransaction.setPosition(pipLeash, destinationBounds.left, destinationBounds.top) 395 .setWindowCrop(pipLeash, destinationBounds.width(), destinationBounds.height()) 396 .setAlpha(pipLeash, 0f); 397 398 PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipLeash, startTransaction, 399 PipAlphaAnimator.FADE_IN); 400 animator.setAnimationEndCallback(() -> { 401 finishCallback.onTransitionFinished(null); 402 // This should update the pip transition state accordingly after we stop playing. 403 onClientDrawAtTransitionEnd(); 404 }); 405 406 animator.start(); 407 return true; 408 } 409 startExpandAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)410 private boolean startExpandAnimation(@NonNull TransitionInfo info, 411 @NonNull SurfaceControl.Transaction startTransaction, 412 @NonNull SurfaceControl.Transaction finishTransaction, 413 @NonNull Transitions.TransitionFinishCallback finishCallback) { 414 startTransaction.apply(); 415 // TODO: b/275910498 Use a new implementation of the PiP animator here. 416 finishCallback.onTransitionFinished(null); 417 mPipTransitionState.setState(PipTransitionState.EXITED_PIP); 418 return true; 419 } 420 removePipImmediately(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)421 private boolean removePipImmediately(@NonNull TransitionInfo info, 422 @NonNull SurfaceControl.Transaction startTransaction, 423 @NonNull SurfaceControl.Transaction finishTransaction, 424 @NonNull Transitions.TransitionFinishCallback finishCallback) { 425 startTransaction.apply(); 426 finishCallback.onTransitionFinished(null); 427 mPipTransitionState.setState(PipTransitionState.EXITED_PIP); 428 return true; 429 } 430 431 // 432 // Various helpers to resolve transition requests and infos 433 // 434 435 @Nullable getPipChange(TransitionInfo info)436 private TransitionInfo.Change getPipChange(TransitionInfo info) { 437 for (TransitionInfo.Change change : info.getChanges()) { 438 if (change.getTaskInfo() != null 439 && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) { 440 return change; 441 } 442 } 443 return null; 444 } 445 getEnterPipTransaction(@onNull IBinder transition, @NonNull TransitionRequestInfo request)446 private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition, 447 @NonNull TransitionRequestInfo request) { 448 // cache the original task token to check for multi-activity case later 449 final ActivityManager.RunningTaskInfo pipTask = request.getPipTask(); 450 PictureInPictureParams pipParams = pipTask.pictureInPictureParams; 451 mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo, 452 pipParams, mPipBoundsAlgorithm); 453 454 // calculate the entry bounds and notify core to move task to pinned with final bounds 455 final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); 456 mPipBoundsState.setBounds(entryBounds); 457 458 WindowContainerTransaction wct = new WindowContainerTransaction(); 459 wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds); 460 wct.deferConfigToTransitionEnd(pipTask.token); 461 return wct; 462 } 463 isAutoEnterInButtonNavigation(@onNull TransitionRequestInfo requestInfo)464 private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) { 465 final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask(); 466 if (pipTask == null) { 467 return false; 468 } 469 if (pipTask.pictureInPictureParams == null) { 470 return false; 471 } 472 473 // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type 474 // implies that we are entering PiP in button navigation mode. This is guaranteed by 475 // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav. 476 return requestInfo.getType() == TRANSIT_OPEN 477 && pipTask.pictureInPictureParams.isAutoEnterEnabled(); 478 } 479 isEnterPictureInPictureModeRequest(@onNull TransitionRequestInfo requestInfo)480 private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) { 481 return requestInfo.getType() == TRANSIT_PIP; 482 } 483 isLegacyEnter(@onNull TransitionInfo info)484 private boolean isLegacyEnter(@NonNull TransitionInfo info) { 485 TransitionInfo.Change pipChange = getPipChange(info); 486 // If the only change in the changes list is a opening type PiP task, 487 // then this is legacy-enter PiP. 488 return pipChange != null && info.getChanges().size() == 1 489 && (pipChange.getMode() == TRANSIT_TO_FRONT || pipChange.getMode() == TRANSIT_OPEN); 490 } 491 isRemovePipTransition(@onNull TransitionInfo info)492 private boolean isRemovePipTransition(@NonNull TransitionInfo info) { 493 if (mPipTransitionState.mPipTaskToken == null) { 494 // PiP removal makes sense if enter-PiP has cached a valid pinned task token. 495 return false; 496 } 497 TransitionInfo.Change pipChange = info.getChange(mPipTransitionState.mPipTaskToken); 498 if (pipChange == null) { 499 // Search for the PiP change by token since the windowing mode might be FULLSCREEN now. 500 return false; 501 } 502 503 boolean isPipMovedToBack = info.getType() == TRANSIT_TO_BACK 504 && pipChange.getMode() == TRANSIT_TO_BACK; 505 boolean isPipClosed = info.getType() == TRANSIT_CLOSE 506 && pipChange.getMode() == TRANSIT_CLOSE; 507 // PiP is being removed if the pinned task is either moved to back or closed. 508 return isPipMovedToBack || isPipClosed; 509 } 510 511 // 512 // Miscellaneous callbacks and listeners 513 // 514 onClientDrawAtTransitionEnd()515 private void onClientDrawAtTransitionEnd() { 516 if (mPipTransitionState.getSwipePipToHomeOverlay() != null) { 517 startOverlayFadeoutAnimation(); 518 } else if (mPipTransitionState.getState() == PipTransitionState.ENTERING_PIP) { 519 // If we were entering PiP (i.e. playing the animation) with a valid srcRectHint, 520 // and then we get a signal on client finishing its draw after the transition 521 // has ended, then we have fully entered PiP. 522 mPipTransitionState.setState(PipTransitionState.ENTERED_PIP); 523 } 524 } 525 526 @Override finishTransition(@ullable SurfaceControl.Transaction tx)527 public void finishTransition(@Nullable SurfaceControl.Transaction tx) { 528 WindowContainerTransaction wct = null; 529 if (tx != null && mPipTransitionState.mPipTaskToken != null) { 530 // Outside callers can only provide a transaction to be applied with the final draw. 531 // So no actual WM changes can be applied for this transition after this point. 532 wct = new WindowContainerTransaction(); 533 wct.setBoundsChangeTransaction(mPipTransitionState.mPipTaskToken, tx); 534 } 535 if (mFinishCallback != null) { 536 mFinishCallback.onTransitionFinished(wct); 537 } 538 } 539 540 @Override onPipTransitionStateChanged(@ipTransitionState.TransitionState int oldState, @PipTransitionState.TransitionState int newState, @Nullable Bundle extra)541 public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, 542 @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) { 543 switch (newState) { 544 case PipTransitionState.ENTERING_PIP: 545 Preconditions.checkState(extra != null, 546 "No extra bundle for " + mPipTransitionState); 547 548 mPipTransitionState.mPipTaskToken = extra.getParcelable( 549 PIP_TASK_TOKEN, WindowContainerToken.class); 550 mPipTransitionState.mPinnedTaskLeash = extra.getParcelable( 551 PIP_TASK_LEASH, SurfaceControl.class); 552 boolean hasValidTokenAndLeash = mPipTransitionState.mPipTaskToken != null 553 && mPipTransitionState.mPinnedTaskLeash != null; 554 555 Preconditions.checkState(hasValidTokenAndLeash, 556 "Unexpected bundle for " + mPipTransitionState); 557 break; 558 case PipTransitionState.EXITED_PIP: 559 mPipTransitionState.mPipTaskToken = null; 560 mPipTransitionState.mPinnedTaskLeash = null; 561 break; 562 } 563 } 564 } 565