1 /* 2 * Copyright (C) 2025 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.bubbles; 18 19 import static android.app.ActivityTaskManager.INVALID_TASK_ID; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 22 import static android.view.View.INVISIBLE; 23 import static android.view.WindowManager.TRANSIT_CHANGE; 24 import static android.view.WindowManager.TRANSIT_TO_FRONT; 25 26 import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE; 27 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.app.ActivityManager; 31 import android.app.TaskInfo; 32 import android.content.Context; 33 import android.graphics.Point; 34 import android.graphics.PointF; 35 import android.graphics.Rect; 36 import android.os.IBinder; 37 import android.util.Slog; 38 import android.view.SurfaceControl; 39 import android.view.SurfaceView; 40 import android.view.View; 41 import android.window.TransitionInfo; 42 import android.window.TransitionRequestInfo; 43 import android.window.WindowContainerToken; 44 import android.window.WindowContainerTransaction; 45 46 import androidx.core.animation.Animator; 47 import androidx.core.animation.Animator.AnimatorUpdateListener; 48 import androidx.core.animation.AnimatorListenerAdapter; 49 import androidx.core.animation.ValueAnimator; 50 51 import com.android.internal.annotations.VisibleForTesting; 52 import com.android.launcher3.icons.BubbleIconFactory; 53 import com.android.wm.shell.ShellTaskOrganizer; 54 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; 55 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; 56 import com.android.wm.shell.taskview.TaskView; 57 import com.android.wm.shell.taskview.TaskViewRepository; 58 import com.android.wm.shell.taskview.TaskViewTaskController; 59 import com.android.wm.shell.taskview.TaskViewTransitions; 60 import com.android.wm.shell.transition.Transitions; 61 62 import java.util.concurrent.Executor; 63 64 /** 65 * Implements transition coordination for bubble operations. 66 */ 67 public class BubbleTransitions { 68 private static final String TAG = "BubbleTransitions"; 69 70 /** 71 * Multiplier used to convert a view elevation to an "equivalent" shadow-radius. This is the 72 * same multiple used by skia and surface-outsets in WMS. 73 */ 74 private static final float ELEVATION_TO_RADIUS = 2; 75 76 @NonNull final Transitions mTransitions; 77 @NonNull final ShellTaskOrganizer mTaskOrganizer; 78 @NonNull final TaskViewRepository mRepository; 79 @NonNull final Executor mMainExecutor; 80 @NonNull final BubbleData mBubbleData; 81 @NonNull final TaskViewTransitions mTaskViewTransitions; 82 @NonNull final Context mContext; 83 BubbleTransitions(@onNull Transitions transitions, @NonNull ShellTaskOrganizer organizer, @NonNull TaskViewRepository repository, @NonNull BubbleData bubbleData, @NonNull TaskViewTransitions taskViewTransitions, Context context)84 BubbleTransitions(@NonNull Transitions transitions, @NonNull ShellTaskOrganizer organizer, 85 @NonNull TaskViewRepository repository, @NonNull BubbleData bubbleData, 86 @NonNull TaskViewTransitions taskViewTransitions, Context context) { 87 mTransitions = transitions; 88 mTaskOrganizer = organizer; 89 mRepository = repository; 90 mMainExecutor = transitions.getMainExecutor(); 91 mBubbleData = bubbleData; 92 mTaskViewTransitions = taskViewTransitions; 93 mContext = context; 94 } 95 96 /** 97 * Starts a convert-to-bubble transition. 98 * 99 * @see ConvertToBubble 100 */ startConvertToBubble(Bubble bubble, TaskInfo taskInfo, BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory, BubblePositioner positioner, BubbleStackView stackView, BubbleBarLayerView layerView, BubbleIconFactory iconFactory, DragData dragData, boolean inflateSync)101 public BubbleTransition startConvertToBubble(Bubble bubble, TaskInfo taskInfo, 102 BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory, 103 BubblePositioner positioner, BubbleStackView stackView, 104 BubbleBarLayerView layerView, BubbleIconFactory iconFactory, 105 DragData dragData, boolean inflateSync) { 106 return new ConvertToBubble(bubble, taskInfo, mContext, 107 expandedViewManager, factory, positioner, stackView, layerView, iconFactory, 108 dragData, inflateSync); 109 } 110 111 /** 112 * Starts a convert-from-bubble transition. 113 * 114 * @see ConvertFromBubble 115 */ startConvertFromBubble(Bubble bubble, TaskInfo taskInfo)116 public BubbleTransition startConvertFromBubble(Bubble bubble, 117 TaskInfo taskInfo) { 118 ConvertFromBubble convert = new ConvertFromBubble(bubble, taskInfo); 119 return convert; 120 } 121 122 /** Starts a transition that converts a dragged bubble icon to a full screen task. */ startDraggedBubbleIconToFullscreen(Bubble bubble, Point dropLocation)123 public BubbleTransition startDraggedBubbleIconToFullscreen(Bubble bubble, Point dropLocation) { 124 return new DraggedBubbleIconToFullscreen(bubble, dropLocation); 125 } 126 127 /** 128 * Plucks the task-surface out of an ancestor view while making the view invisible. This helper 129 * attempts to do this seamlessly (ie. view becomes invisible in sync with task reparent). 130 */ pluck(SurfaceControl taskLeash, View fromView, SurfaceControl dest, float destX, float destY, float cornerRadius, SurfaceControl.Transaction t, Runnable onPlucked)131 private void pluck(SurfaceControl taskLeash, View fromView, SurfaceControl dest, 132 float destX, float destY, float cornerRadius, SurfaceControl.Transaction t, 133 Runnable onPlucked) { 134 SurfaceControl.Transaction pluckT = new SurfaceControl.Transaction(); 135 pluckT.reparent(taskLeash, dest); 136 t.reparent(taskLeash, dest); 137 pluckT.setPosition(taskLeash, destX, destY); 138 t.setPosition(taskLeash, destX, destY); 139 pluckT.show(taskLeash); 140 pluckT.setAlpha(taskLeash, 1.f); 141 float shadowRadius = fromView.getElevation() * ELEVATION_TO_RADIUS; 142 pluckT.setShadowRadius(taskLeash, shadowRadius); 143 pluckT.setCornerRadius(taskLeash, cornerRadius); 144 t.setShadowRadius(taskLeash, shadowRadius); 145 t.setCornerRadius(taskLeash, cornerRadius); 146 147 // Need to remove the taskview AFTER applying the startTransaction because it isn't 148 // synchronized. 149 pluckT.addTransactionCommittedListener(mMainExecutor, onPlucked::run); 150 fromView.getViewRootImpl().applyTransactionOnDraw(pluckT); 151 fromView.setVisibility(INVISIBLE); 152 } 153 154 /** 155 * Interface to a bubble-specific transition. Bubble transitions have a multi-step lifecycle 156 * in order to coordinate with the bubble view logic. These steps are communicated on this 157 * interface. 158 */ 159 interface BubbleTransition { surfaceCreated()160 default void surfaceCreated() {} continueExpand()161 default void continueExpand() {} skip()162 void skip(); continueCollapse()163 default void continueCollapse() {} 164 } 165 166 /** 167 * Information about the task when it is being dragged to a bubble 168 */ 169 public static class DragData { 170 private final WindowContainerTransaction mPendingWct; 171 private final boolean mReleasedOnLeft; 172 private final float mTaskScale; 173 private final float mCornerRadius; 174 private final PointF mDragPosition; 175 176 /** 177 * @param releasedOnLeft true if the bubble was released in the left drop target 178 * @param taskScale the scale of the task when it was dragged to bubble 179 * @param cornerRadius the corner radius of the task when it was dragged to bubble 180 * @param dragPosition the position of the task when it was dragged to bubble 181 * @param wct pending operations to be applied when finishing the drag 182 */ DragData(boolean releasedOnLeft, float taskScale, float cornerRadius, @Nullable PointF dragPosition, @Nullable WindowContainerTransaction wct)183 public DragData(boolean releasedOnLeft, float taskScale, float cornerRadius, 184 @Nullable PointF dragPosition, @Nullable WindowContainerTransaction wct) { 185 mPendingWct = wct; 186 mReleasedOnLeft = releasedOnLeft; 187 mTaskScale = taskScale; 188 mCornerRadius = cornerRadius; 189 mDragPosition = dragPosition != null ? dragPosition : new PointF(0, 0); 190 } 191 192 /** 193 * @return pending operations to be applied when finishing the drag 194 */ 195 @Nullable getPendingWct()196 public WindowContainerTransaction getPendingWct() { 197 return mPendingWct; 198 } 199 200 /** 201 * @return true if the bubble was released in the left drop target 202 */ isReleasedOnLeft()203 public boolean isReleasedOnLeft() { 204 return mReleasedOnLeft; 205 } 206 207 /** 208 * @return the scale of the task when it was dragged to bubble 209 */ getTaskScale()210 public float getTaskScale() { 211 return mTaskScale; 212 } 213 214 /** 215 * @return the corner radius of the task when it was dragged to bubble 216 */ getCornerRadius()217 public float getCornerRadius() { 218 return mCornerRadius; 219 } 220 221 /** 222 * @return position of the task when it was dragged to bubble 223 */ getDragPosition()224 public PointF getDragPosition() { 225 return mDragPosition; 226 } 227 } 228 229 /** 230 * BubbleTransition that coordinates the process of a non-bubble task becoming a bubble. The 231 * steps are as follows: 232 * 233 * 1. Start inflating the bubble view 234 * 2. Once inflated (but not-yet visible), tell WM to do the shell-transition. 235 * 3. When the transition becomes ready, notify Launcher in parallel 236 * 4. Wait for surface to be created 237 * 5. Once surface is ready, animate the task to a bubble 238 * 239 * While the animation is pending, we keep a reference to the pending transition in the bubble. 240 * This allows us to check in other parts of the code that this bubble will be shown via the 241 * transition animation. 242 * 243 * startAnimation, continueExpand and surfaceCreated are set-up to happen in either order, 244 * to support UX/timing adjustments. 245 */ 246 @VisibleForTesting 247 class ConvertToBubble implements Transitions.TransitionHandler, BubbleTransition { 248 final BubbleBarLayerView mLayerView; 249 Bubble mBubble; 250 @Nullable DragData mDragData; 251 IBinder mTransition; 252 Transitions.TransitionFinishCallback mFinishCb; 253 WindowContainerTransaction mFinishWct = null; 254 final Rect mStartBounds = new Rect(); 255 SurfaceControl mSnapshot = null; 256 TaskInfo mTaskInfo; 257 BubbleViewProvider mPriorBubble = null; 258 259 private final TransitionProgress mTransitionProgress = new TransitionProgress(); 260 private SurfaceControl.Transaction mFinishT; 261 private SurfaceControl mTaskLeash; 262 ConvertToBubble(Bubble bubble, TaskInfo taskInfo, Context context, BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory, BubblePositioner positioner, BubbleStackView stackView, BubbleBarLayerView layerView, BubbleIconFactory iconFactory, @Nullable DragData dragData, boolean inflateSync)263 ConvertToBubble(Bubble bubble, TaskInfo taskInfo, Context context, 264 BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory, 265 BubblePositioner positioner, BubbleStackView stackView, 266 BubbleBarLayerView layerView, BubbleIconFactory iconFactory, 267 @Nullable DragData dragData, boolean inflateSync) { 268 mBubble = bubble; 269 mTaskInfo = taskInfo; 270 mLayerView = layerView; 271 mDragData = dragData; 272 mBubble.setInflateSynchronously(inflateSync); 273 mBubble.setPreparingTransition(this); 274 mBubble.inflate( 275 this::onInflated, 276 context, 277 expandedViewManager, 278 factory, 279 positioner, 280 stackView, 281 layerView, 282 iconFactory, 283 false /* skipInflation */); 284 } 285 286 @VisibleForTesting onInflated(Bubble b)287 void onInflated(Bubble b) { 288 if (b != mBubble) { 289 throw new IllegalArgumentException("inflate callback doesn't match bubble"); 290 } 291 final Rect launchBounds = new Rect(); 292 mLayerView.getExpandedViewRestBounds(launchBounds); 293 WindowContainerTransaction wct = new WindowContainerTransaction(); 294 if (mDragData != null && mDragData.getPendingWct() != null) { 295 wct.merge(mDragData.getPendingWct(), true); 296 } 297 if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) { 298 if (mTaskInfo.getParentTaskId() != INVALID_TASK_ID) { 299 wct.reparent(mTaskInfo.token, null, true); 300 } 301 } 302 303 wct.setAlwaysOnTop(mTaskInfo.token, true); 304 wct.setWindowingMode(mTaskInfo.token, WINDOWING_MODE_MULTI_WINDOW); 305 wct.setBounds(mTaskInfo.token, launchBounds); 306 307 final TaskView tv = b.getTaskView(); 308 tv.setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT); 309 final TaskViewRepository.TaskViewState state = mRepository.byTaskView( 310 tv.getController()); 311 if (state != null) { 312 state.mVisible = true; 313 } 314 mTaskViewTransitions.enqueueExternal(tv.getController(), () -> { 315 mTransition = mTransitions.startTransition(TRANSIT_CONVERT_TO_BUBBLE, wct, this); 316 return mTransition; 317 }); 318 } 319 320 @Override skip()321 public void skip() { 322 mBubble.setPreparingTransition(null); 323 mFinishCb.onTransitionFinished(mFinishWct); 324 mFinishCb = null; 325 } 326 327 @Override handleRequest(@onNull IBinder transition, @Nullable TransitionRequestInfo request)328 public WindowContainerTransaction handleRequest(@NonNull IBinder transition, 329 @Nullable TransitionRequestInfo request) { 330 return null; 331 } 332 333 @Override mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)334 public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 335 @NonNull SurfaceControl.Transaction startT, 336 @NonNull SurfaceControl.Transaction finishT, 337 @NonNull IBinder mergeTarget, 338 @NonNull Transitions.TransitionFinishCallback finishCallback) { 339 } 340 341 @Override onTransitionConsumed(@onNull IBinder transition, boolean aborted, @NonNull SurfaceControl.Transaction finishTransaction)342 public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, 343 @NonNull SurfaceControl.Transaction finishTransaction) { 344 if (!aborted) return; 345 mTransition = null; 346 mTaskViewTransitions.onExternalDone(transition); 347 } 348 349 @Override startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)350 public boolean startAnimation(@NonNull IBinder transition, 351 @NonNull TransitionInfo info, 352 @NonNull SurfaceControl.Transaction startTransaction, 353 @NonNull SurfaceControl.Transaction finishTransaction, 354 @NonNull Transitions.TransitionFinishCallback finishCallback) { 355 if (mTransition != transition) return false; 356 boolean found = false; 357 for (int i = 0; i < info.getChanges().size(); ++i) { 358 final TransitionInfo.Change chg = info.getChanges().get(i); 359 if (chg.getTaskInfo() == null) continue; 360 if (chg.getMode() != TRANSIT_CHANGE && chg.getMode() != TRANSIT_TO_FRONT) continue; 361 if (!mTaskInfo.token.equals(chg.getTaskInfo().token)) continue; 362 mStartBounds.set(chg.getStartAbsBounds()); 363 // Converting a task into taskview, so treat as "new" 364 mFinishWct = new WindowContainerTransaction(); 365 mTaskInfo = chg.getTaskInfo(); 366 mFinishT = finishTransaction; 367 mTaskLeash = chg.getLeash(); 368 found = true; 369 mSnapshot = chg.getSnapshot(); 370 break; 371 } 372 if (!found) { 373 Slog.w(TAG, "Expected a TaskView conversion in this transition but didn't get " 374 + "one, cleaning up the task view"); 375 mBubble.getTaskView().getController().setTaskNotFound(); 376 mTaskViewTransitions.onExternalDone(transition); 377 return false; 378 } 379 mFinishCb = finishCallback; 380 381 if (mDragData != null) { 382 mStartBounds.offsetTo((int) mDragData.getDragPosition().x, 383 (int) mDragData.getDragPosition().y); 384 startTransaction.setScale(mSnapshot, mDragData.getTaskScale(), 385 mDragData.getTaskScale()); 386 startTransaction.setCornerRadius(mSnapshot, mDragData.getCornerRadius()); 387 } 388 389 // Now update state (and talk to launcher) in parallel with snapshot stuff 390 mBubbleData.notificationEntryUpdated(mBubble, /* suppressFlyout= */ true, 391 /* showInShade= */ false); 392 393 final int left = mStartBounds.left - info.getRoot(0).getOffset().x; 394 final int top = mStartBounds.top - info.getRoot(0).getOffset().y; 395 startTransaction.setPosition(mTaskLeash, left, top); 396 startTransaction.show(mSnapshot); 397 // Move snapshot to root so that it remains visible while task is moved to taskview 398 startTransaction.reparent(mSnapshot, info.getRoot(0).getLeash()); 399 startTransaction.setPosition(mSnapshot, left, top); 400 startTransaction.setLayer(mSnapshot, Integer.MAX_VALUE); 401 402 startTransaction.apply(); 403 404 mTaskViewTransitions.onExternalDone(transition); 405 mTransitionProgress.setTransitionReady(); 406 startExpandAnim(); 407 return true; 408 } 409 startExpandAnim()410 private void startExpandAnim() { 411 final boolean animate = mLayerView.canExpandView(mBubble); 412 if (animate) { 413 mPriorBubble = mLayerView.prepareConvertedView(mBubble); 414 } 415 if (mPriorBubble != null) { 416 // TODO: an animation. For now though, just remove it. 417 final BubbleBarExpandedView priorView = mPriorBubble.getBubbleBarExpandedView(); 418 mLayerView.removeView(priorView); 419 mPriorBubble = null; 420 } 421 if (!animate || mTransitionProgress.isReadyToAnimate()) { 422 playAnimation(animate); 423 } 424 } 425 426 @Override continueExpand()427 public void continueExpand() { 428 mTransitionProgress.setReadyToExpand(); 429 } 430 431 @Override surfaceCreated()432 public void surfaceCreated() { 433 mTransitionProgress.setSurfaceReady(); 434 mMainExecutor.execute(() -> { 435 final TaskViewTaskController tvc = mBubble.getTaskView().getController(); 436 final TaskViewRepository.TaskViewState state = mRepository.byTaskView(tvc); 437 if (state == null) return; 438 state.mVisible = true; 439 if (mTransitionProgress.isReadyToAnimate()) { 440 playAnimation(true /* animate */); 441 } 442 }); 443 } 444 playAnimation(boolean animate)445 private void playAnimation(boolean animate) { 446 final TaskViewTaskController tv = mBubble.getTaskView().getController(); 447 final SurfaceControl.Transaction startT = new SurfaceControl.Transaction(); 448 // Set task position to 0,0 as it will be placed inside the TaskView 449 startT.setPosition(mTaskLeash, 0, 0); 450 mTaskViewTransitions.prepareOpenAnimation(tv, true /* new */, startT, mFinishT, 451 (ActivityManager.RunningTaskInfo) mTaskInfo, mTaskLeash, mFinishWct); 452 453 if (mFinishWct.isEmpty()) { 454 mFinishWct = null; 455 } 456 457 if (animate) { 458 float startScale = mDragData != null ? mDragData.getTaskScale() : 1f; 459 mLayerView.animateConvert(startT, mStartBounds, startScale, mSnapshot, mTaskLeash, 460 () -> { 461 mFinishCb.onTransitionFinished(mFinishWct); 462 mFinishCb = null; 463 }); 464 } else { 465 startT.apply(); 466 mFinishCb.onTransitionFinished(mFinishWct); 467 mFinishCb = null; 468 } 469 } 470 471 /** 472 * Keeps track of internal state of different steps of this BubbleTransition. 473 */ 474 private class TransitionProgress { 475 private boolean mTransitionReady; 476 private boolean mReadyToExpand; 477 private boolean mSurfaceReady; 478 setTransitionReady()479 void setTransitionReady() { 480 mTransitionReady = true; 481 onUpdate(); 482 } 483 setReadyToExpand()484 void setReadyToExpand() { 485 mReadyToExpand = true; 486 onUpdate(); 487 } 488 setSurfaceReady()489 void setSurfaceReady() { 490 mSurfaceReady = true; 491 onUpdate(); 492 } 493 isReadyToAnimate()494 boolean isReadyToAnimate() { 495 // Animation only depends on transition and surface state 496 return mTransitionReady && mSurfaceReady; 497 } 498 onUpdate()499 private void onUpdate() { 500 if (mTransitionReady && mReadyToExpand && mSurfaceReady) { 501 // Clear the transition from bubble when all the steps are ready 502 mBubble.setPreparingTransition(null); 503 } 504 } 505 } 506 } 507 508 /** 509 * BubbleTransition that coordinates the setup for moving a task out of a bubble. The actual 510 * animation is owned by the "receiver" of the task; however, because Bubbles uses TaskView, 511 * we need to do some extra coordination work to get the task surface out of the view 512 * "seamlessly". 513 * 514 * The process here looks like: 515 * 1. Send transition to WM for leaving bubbles mode 516 * 2. in startAnimation, set-up a "pluck" operation to pull the task surface out of taskview 517 * 3. Once "plucked", remove the view (calls continueCollapse when surfaces can be cleaned-up) 518 * 4. Then re-dispatch the transition animation so that the "receiver" can animate it. 519 * 520 * So, constructor -> startAnimation -> continueCollapse -> re-dispatch. 521 */ 522 @VisibleForTesting 523 class ConvertFromBubble implements Transitions.TransitionHandler, BubbleTransition { 524 @NonNull final Bubble mBubble; 525 IBinder mTransition; 526 TaskInfo mTaskInfo; 527 SurfaceControl mTaskLeash; 528 SurfaceControl mRootLeash; 529 ConvertFromBubble(@onNull Bubble bubble, TaskInfo taskInfo)530 ConvertFromBubble(@NonNull Bubble bubble, TaskInfo taskInfo) { 531 mBubble = bubble; 532 mTaskInfo = taskInfo; 533 534 mBubble.setPreparingTransition(this); 535 WindowContainerTransaction wct = new WindowContainerTransaction(); 536 WindowContainerToken token = mTaskInfo.getToken(); 537 wct.setWindowingMode(token, WINDOWING_MODE_UNDEFINED); 538 wct.setAlwaysOnTop(token, false); 539 mTaskOrganizer.setInterceptBackPressedOnTaskRoot(token, false); 540 mTaskViewTransitions.enqueueExternal( 541 mBubble.getTaskView().getController(), 542 () -> { 543 mTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this); 544 return mTransition; 545 }); 546 } 547 548 @Override skip()549 public void skip() { 550 mBubble.setPreparingTransition(null); 551 final TaskViewTaskController tv = 552 mBubble.getTaskView().getController(); 553 tv.notifyTaskRemovalStarted(tv.getTaskInfo()); 554 mTaskLeash = null; 555 } 556 557 @Override handleRequest(@onNull IBinder transition, @android.annotation.Nullable TransitionRequestInfo request)558 public WindowContainerTransaction handleRequest(@NonNull IBinder transition, 559 @android.annotation.Nullable TransitionRequestInfo request) { 560 return null; 561 } 562 563 @Override mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)564 public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 565 @NonNull SurfaceControl.Transaction startT, 566 @NonNull SurfaceControl.Transaction finishT, 567 @NonNull IBinder mergeTarget, 568 @NonNull Transitions.TransitionFinishCallback finishCallback) { 569 } 570 571 @Override onTransitionConsumed(@onNull IBinder transition, boolean aborted, @NonNull SurfaceControl.Transaction finishTransaction)572 public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, 573 @NonNull SurfaceControl.Transaction finishTransaction) { 574 if (!aborted) return; 575 mTransition = null; 576 skip(); 577 mTaskViewTransitions.onExternalDone(transition); 578 } 579 580 @Override startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)581 public boolean startAnimation(@NonNull IBinder transition, 582 @NonNull TransitionInfo info, 583 @NonNull SurfaceControl.Transaction startTransaction, 584 @NonNull SurfaceControl.Transaction finishTransaction, 585 @NonNull Transitions.TransitionFinishCallback finishCallback) { 586 if (mTransition != transition) return false; 587 588 final TaskViewTaskController tv = 589 mBubble.getTaskView().getController(); 590 if (tv == null) { 591 mTaskViewTransitions.onExternalDone(transition); 592 return false; 593 } 594 595 TransitionInfo.Change taskChg = null; 596 597 boolean found = false; 598 for (int i = 0; i < info.getChanges().size(); ++i) { 599 final TransitionInfo.Change chg = info.getChanges().get(i); 600 if (chg.getTaskInfo() == null) continue; 601 if (chg.getMode() != TRANSIT_CHANGE) continue; 602 if (!mTaskInfo.token.equals(chg.getTaskInfo().token)) continue; 603 found = true; 604 mRepository.remove(tv); 605 taskChg = chg; 606 break; 607 } 608 609 if (!found) { 610 Slog.w(TAG, "Expected a TaskView conversion in this transition but didn't get " 611 + "one, cleaning up the task view"); 612 tv.setTaskNotFound(); 613 skip(); 614 mTaskViewTransitions.onExternalDone(transition); 615 return false; 616 } 617 618 mTaskLeash = taskChg.getLeash(); 619 mRootLeash = info.getRoot(0).getLeash(); 620 621 SurfaceControl dest = getExpandedView(mBubble).getViewRootImpl().getSurfaceControl(); 622 final Runnable onPlucked = () -> { 623 // Need to remove the taskview AFTER applying the startTransaction because 624 // it isn't synchronized. 625 tv.notifyTaskRemovalStarted(tv.getTaskInfo()); 626 // Unset after removeView so it can be used to pick a different animation. 627 mBubble.setPreparingTransition(null); 628 mBubbleData.setExpanded(false /* expanded */); 629 }; 630 if (dest != null) { 631 pluck(mTaskLeash, getExpandedView(mBubble), dest, 632 taskChg.getStartAbsBounds().left - info.getRoot(0).getOffset().x, 633 taskChg.getStartAbsBounds().top - info.getRoot(0).getOffset().y, 634 getCornerRadius(mBubble), startTransaction, 635 onPlucked); 636 getExpandedView(mBubble).post(() -> mTransitions.dispatchTransition( 637 mTransition, info, startTransaction, finishTransaction, finishCallback, 638 null)); 639 } else { 640 onPlucked.run(); 641 mTransitions.dispatchTransition(mTransition, info, startTransaction, 642 finishTransaction, finishCallback, null); 643 } 644 645 mTaskViewTransitions.onExternalDone(transition); 646 return true; 647 } 648 649 @Override continueCollapse()650 public void continueCollapse() { 651 mBubble.cleanupTaskView(); 652 if (mTaskLeash == null || !mTaskLeash.isValid() || !mRootLeash.isValid()) return; 653 SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 654 t.reparent(mTaskLeash, mRootLeash); 655 t.apply(); 656 } 657 getExpandedView(@onNull Bubble bubble)658 private View getExpandedView(@NonNull Bubble bubble) { 659 if (bubble.getBubbleBarExpandedView() != null) { 660 return bubble.getBubbleBarExpandedView(); 661 } 662 return bubble.getExpandedView(); 663 } 664 getCornerRadius(@onNull Bubble bubble)665 private float getCornerRadius(@NonNull Bubble bubble) { 666 if (bubble.getBubbleBarExpandedView() != null) { 667 return bubble.getBubbleBarExpandedView().getCornerRadius(); 668 } 669 return bubble.getExpandedView().getCornerRadius(); 670 } 671 } 672 673 /** 674 * A transition that converts a dragged bubble icon to a full screen window. 675 * 676 * <p>This transition assumes that the bubble is invisible so it is simply sent to front. 677 */ 678 class DraggedBubbleIconToFullscreen implements Transitions.TransitionHandler, BubbleTransition { 679 680 IBinder mTransition; 681 final Bubble mBubble; 682 final Point mDropLocation; 683 final TransactionProvider mTransactionProvider; 684 DraggedBubbleIconToFullscreen(Bubble bubble, Point dropLocation)685 DraggedBubbleIconToFullscreen(Bubble bubble, Point dropLocation) { 686 this(bubble, dropLocation, SurfaceControl.Transaction::new); 687 } 688 689 @VisibleForTesting DraggedBubbleIconToFullscreen(Bubble bubble, Point dropLocation, TransactionProvider transactionProvider)690 DraggedBubbleIconToFullscreen(Bubble bubble, Point dropLocation, 691 TransactionProvider transactionProvider) { 692 mBubble = bubble; 693 mDropLocation = dropLocation; 694 mTransactionProvider = transactionProvider; 695 bubble.setPreparingTransition(this); 696 WindowContainerToken token = bubble.getTaskView().getTaskInfo().getToken(); 697 WindowContainerTransaction wct = new WindowContainerTransaction(); 698 wct.setAlwaysOnTop(token, false); 699 wct.setWindowingMode(token, WINDOWING_MODE_UNDEFINED); 700 wct.reorder(token, /* onTop= */ true); 701 wct.setHidden(token, false); 702 mTaskOrganizer.setInterceptBackPressedOnTaskRoot(token, false); 703 mTaskViewTransitions.enqueueExternal(bubble.getTaskView().getController(), () -> { 704 mTransition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct, this); 705 return mTransition; 706 }); 707 } 708 709 @Override skip()710 public void skip() { 711 mBubble.setPreparingTransition(null); 712 } 713 714 @Override startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)715 public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 716 @NonNull SurfaceControl.Transaction startTransaction, 717 @NonNull SurfaceControl.Transaction finishTransaction, 718 @NonNull Transitions.TransitionFinishCallback finishCallback) { 719 if (mTransition != transition) { 720 return false; 721 } 722 723 final TaskViewTaskController taskViewTaskController = 724 mBubble.getTaskView().getController(); 725 if (taskViewTaskController == null) { 726 mTaskViewTransitions.onExternalDone(transition); 727 finishCallback.onTransitionFinished(null); 728 return true; 729 } 730 731 TransitionInfo.Change change = findTransitionChange(info); 732 if (change == null) { 733 Slog.w(TAG, "Expected a TaskView transition to front but didn't find " 734 + "one, cleaning up the task view"); 735 taskViewTaskController.setTaskNotFound(); 736 mTaskViewTransitions.onExternalDone(transition); 737 finishCallback.onTransitionFinished(null); 738 return true; 739 } 740 mRepository.remove(taskViewTaskController); 741 742 final SurfaceControl taskLeash = change.getLeash(); 743 // set the initial position of the task with 0 scale 744 startTransaction.setPosition(taskLeash, mDropLocation.x, mDropLocation.y); 745 startTransaction.setScale(taskLeash, 0, 0); 746 startTransaction.apply(); 747 748 final SurfaceControl.Transaction animT = mTransactionProvider.get(); 749 ValueAnimator animator = ValueAnimator.ofFloat(0, 1); 750 animator.setDuration(250); 751 animator.addUpdateListener(new AnimatorUpdateListener() { 752 @Override 753 public void onAnimationUpdate(@NonNull Animator animation) { 754 float progress = animator.getAnimatedFraction(); 755 float x = mDropLocation.x * (1 - progress); 756 float y = mDropLocation.y * (1 - progress); 757 animT.setPosition(taskLeash, x, y); 758 animT.setScale(taskLeash, progress, progress); 759 animT.apply(); 760 } 761 }); 762 animator.addListener(new AnimatorListenerAdapter() { 763 @Override 764 public void onAnimationEnd(@NonNull Animator animation) { 765 animT.close(); 766 finishCallback.onTransitionFinished(null); 767 } 768 }); 769 animator.start(); 770 taskViewTaskController.notifyTaskRemovalStarted(mBubble.getTaskView().getTaskInfo()); 771 mTaskViewTransitions.onExternalDone(transition); 772 return true; 773 } 774 findTransitionChange(TransitionInfo info)775 private TransitionInfo.Change findTransitionChange(TransitionInfo info) { 776 TransitionInfo.Change result = null; 777 WindowContainerToken token = mBubble.getTaskView().getTaskInfo().getToken(); 778 for (int i = 0; i < info.getChanges().size(); ++i) { 779 final TransitionInfo.Change change = info.getChanges().get(i); 780 if (change.getTaskInfo() == null) { 781 continue; 782 } 783 if (change.getMode() != TRANSIT_TO_FRONT) { 784 continue; 785 } 786 if (!token.equals(change.getTaskInfo().token)) { 787 continue; 788 } 789 result = change; 790 break; 791 } 792 return result; 793 } 794 795 @Override mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)796 public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 797 @NonNull SurfaceControl.Transaction startTransaction, 798 @NonNull SurfaceControl.Transaction finishTransaction, 799 @NonNull IBinder mergeTarget, 800 @NonNull Transitions.TransitionFinishCallback finishCallback) { 801 } 802 803 @Override handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)804 public WindowContainerTransaction handleRequest(@NonNull IBinder transition, 805 @NonNull TransitionRequestInfo request) { 806 return null; 807 } 808 809 @Override onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishTransaction)810 public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, 811 @Nullable SurfaceControl.Transaction finishTransaction) { 812 if (!aborted) { 813 return; 814 } 815 mTransition = null; 816 mTaskViewTransitions.onExternalDone(transition); 817 } 818 } 819 820 interface TransactionProvider { get()821 SurfaceControl.Transaction get(); 822 } 823 } 824