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 package com.android.launcher3.taskbar.bubbles; 17 18 import static android.view.View.INVISIBLE; 19 import static android.view.View.VISIBLE; 20 21 import static com.android.launcher3.Utilities.mapRange; 22 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT; 23 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT; 24 25 import android.animation.Animator; 26 import android.animation.AnimatorSet; 27 import android.content.Intent; 28 import android.content.pm.ShortcutInfo; 29 import android.content.res.Resources; 30 import android.graphics.Point; 31 import android.graphics.PointF; 32 import android.graphics.Rect; 33 import android.util.DisplayMetrics; 34 import android.util.Log; 35 import android.util.TypedValue; 36 import android.view.MotionEvent; 37 import android.view.View; 38 import android.widget.FrameLayout; 39 40 import androidx.annotation.NonNull; 41 import androidx.annotation.Nullable; 42 43 import com.android.app.animation.Interpolators; 44 import com.android.launcher3.AbstractFloatingView; 45 import com.android.launcher3.DeviceProfile; 46 import com.android.launcher3.R; 47 import com.android.launcher3.anim.AnimatedFloat; 48 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; 49 import com.android.launcher3.dragndrop.DragController; 50 import com.android.launcher3.model.data.ItemInfo; 51 import com.android.launcher3.model.data.WorkspaceItemInfo; 52 import com.android.launcher3.taskbar.TaskbarActivityContext; 53 import com.android.launcher3.taskbar.TaskbarControllers; 54 import com.android.launcher3.taskbar.TaskbarInsetsController; 55 import com.android.launcher3.taskbar.TaskbarSharedState; 56 import com.android.launcher3.taskbar.TaskbarStashController; 57 import com.android.launcher3.taskbar.bubbles.BubbleBarLocationDropTarget.BubbleBarDragListener; 58 import com.android.launcher3.taskbar.bubbles.animation.BubbleBarViewAnimator; 59 import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutController; 60 import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutPositioner; 61 import com.android.launcher3.taskbar.bubbles.flyout.FlyoutCallbacks; 62 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController; 63 import com.android.launcher3.util.DisplayController; 64 import com.android.launcher3.util.MultiPropertyFactory; 65 import com.android.launcher3.util.MultiValueAlpha; 66 import com.android.quickstep.SystemUiProxy; 67 import com.android.wm.shell.Flags; 68 import com.android.wm.shell.shared.bubbles.BubbleBarLocation; 69 import com.android.wm.shell.shared.bubbles.DeviceConfig; 70 71 import java.io.PrintWriter; 72 import java.util.List; 73 import java.util.Objects; 74 import java.util.function.Consumer; 75 76 /** 77 * Controller for {@link BubbleBarView}. Manages the visibility of the bubble bar as well as 78 * responding to changes in bubble state provided by BubbleBarController. 79 */ 80 public class BubbleBarViewController { 81 82 private static final String TAG = "BubbleBarViewController"; 83 private static final float APP_ICON_SMALL_DP = 44f; 84 private static final float APP_ICON_MEDIUM_DP = 48f; 85 private static final float APP_ICON_LARGE_DP = 52f; 86 /** The dot size is defined as a percentage of the icon size. */ 87 private static final float DOT_TO_BUBBLE_SIZE_RATIO = 0.228f; 88 public static final int TASKBAR_FADE_IN_DURATION_MS = 150; 89 public static final int TASKBAR_FADE_IN_DELAY_MS = 50; 90 public static final int TASKBAR_FADE_OUT_DURATION_MS = 100; 91 private final SystemUiProxy mSystemUiProxy; 92 private final TaskbarActivityContext mActivity; 93 private final BubbleBarView mBarView; 94 private int mIconSize; 95 private int mBubbleBarPadding; 96 private final int mDragElevation; 97 98 // Initialized in init. 99 private BubbleStashController mBubbleStashController; 100 private BubbleBarController mBubbleBarController; 101 private BubbleDragController mBubbleDragController; 102 private TaskbarStashController mTaskbarStashController; 103 private TaskbarInsetsController mTaskbarInsetsController; 104 private TaskbarViewPropertiesProvider mTaskbarViewPropertiesProvider; 105 private View.OnClickListener mBubbleClickListener; 106 private BubbleView.Controller mBubbleViewController; 107 private BubbleBarOverflow mOverflowBubble; 108 109 // These are exposed to {@link BubbleStashController} to animate for stashing/un-stashing 110 private final MultiValueAlpha mBubbleBarAlpha; 111 private final AnimatedFloat mBubbleBarBubbleAlpha = new AnimatedFloat(this::updateBubbleAlpha); 112 private final AnimatedFloat mBubbleBarBackgroundAlpha = new AnimatedFloat( 113 this::updateBackgroundAlpha); 114 private final AnimatedFloat mBubbleBarScaleX = new AnimatedFloat(this::updateScaleX); 115 private final AnimatedFloat mBubbleBarScaleY = new AnimatedFloat(this::updateScaleY); 116 private final AnimatedFloat mBubbleBarBackgroundScaleX = new AnimatedFloat( 117 this::updateBackgroundScaleX); 118 private final AnimatedFloat mBubbleBarBackgroundScaleY = new AnimatedFloat( 119 this::updateBackgroundScaleY); 120 private final AnimatedFloat mBubbleBarTranslationY = new AnimatedFloat( 121 this::updateTranslationY); 122 private final AnimatedFloat mBubbleOffsetY = new AnimatedFloat( 123 this::updateBubbleOffsetY); 124 private final AnimatedFloat mBubbleBarPinning = new AnimatedFloat(pinningProgress -> { 125 updateTranslationY(); 126 setBubbleBarScaleAndPadding(pinningProgress); 127 }); 128 private final BubbleBarDragListener mDragListener = new BubbleBarDragListener() { 129 130 @Override 131 public void getBubbleBarLocationHitRect(@NonNull BubbleBarLocation bubbleBarLocation, 132 Rect outRect) { 133 Point screenSize = DisplayController.INSTANCE.get(mActivity).getInfo().currentSize; 134 outRect.top = screenSize.y - mBubbleBarDropTargetSize; 135 outRect.bottom = screenSize.y; 136 if (bubbleBarLocation.isOnLeft(mBarView.isLayoutRtl())) { 137 outRect.left = 0; 138 outRect.right = mBubbleBarDropTargetSize; 139 } else { 140 outRect.left = screenSize.x - mBubbleBarDropTargetSize; 141 outRect.right = screenSize.x; 142 } 143 } 144 145 @Override 146 public void onLauncherItemDroppedOverBubbleBarDragZone(@NonNull BubbleBarLocation location, 147 @NonNull ItemInfo itemInfo) { 148 AbstractFloatingView.closeAllOpenViews(mActivity); 149 if (itemInfo instanceof WorkspaceItemInfo) { 150 ShortcutInfo shortcutInfo = ((WorkspaceItemInfo) itemInfo).getDeepShortcutInfo(); 151 if (shortcutInfo != null) { 152 mSystemUiProxy.showShortcutBubble(shortcutInfo, location); 153 return; 154 } 155 } 156 Intent itemIntent = itemInfo.getIntent(); 157 if (itemIntent != null && itemIntent.getComponent() != null) { 158 itemIntent.setPackage(itemIntent.getComponent().getPackageName()); 159 mSystemUiProxy.showAppBubble(itemIntent, itemInfo.user, location); 160 } 161 } 162 163 @Override 164 public void onLauncherItemDraggedOutsideBubbleBarDropZone() { 165 onItemDraggedOutsideBubbleBarDropZone(); 166 mSystemUiProxy.showBubbleDropTarget(/* show = */ false); 167 } 168 169 @Override 170 public void onLauncherItemDraggedOverBubbleBarDragZone( 171 @NonNull BubbleBarLocation location) { 172 onDragItemOverBubbleBarDragZone(location); 173 mSystemUiProxy.showBubbleDropTarget(/* show = */ true, location); 174 } 175 176 @NonNull 177 @Override 178 public View getDropView() { 179 return mBarView; 180 } 181 }; 182 183 // Modified when swipe up is happening on the bubble bar or task bar. 184 private float mBubbleBarSwipeUpTranslationY; 185 // Modified when bubble bar is springing back into the stash handle. 186 private float mBubbleBarStashTranslationY; 187 // Minimum distance between the BubbleBar and the taskbar 188 private final int mBubbleBarTaskbarMinDistance; 189 // Whether the bar is hidden for a sysui state. 190 private boolean mHiddenForSysui; 191 // Whether the bar is hidden because there are no bubbles. 192 private boolean mHiddenForNoBubbles = true; 193 // Whether the bar is hidden when stashed 194 private boolean mHiddenForStashed; 195 private boolean mShouldShowEducation; 196 public boolean mOverflowAdded; 197 private boolean mWasStashedBeforeEnteringBubbleDragZone = false; 198 199 /** This field is used solely to track the bubble bar location prior to the start of the drag */ 200 private @Nullable BubbleBarLocation mBubbleBarDragLocation; 201 202 private BubbleBarViewAnimator mBubbleBarViewAnimator; 203 private final FrameLayout mBubbleBarContainer; 204 private BubbleBarFlyoutController mBubbleBarFlyoutController; 205 private BubbleBarPinController mBubbleBarPinController; 206 private TaskbarSharedState mTaskbarSharedState; 207 private final BubbleBarLocationDropTarget mBubbleBarLeftDropTarget; 208 private final BubbleBarLocationDropTarget mBubbleBarRightDropTarget; 209 private final TimeSource mTimeSource = System::currentTimeMillis; 210 private final int mTaskbarTranslationDelta; 211 private final int mBubbleBarDropTargetSize; 212 213 @Nullable 214 private BubbleBarBoundsChangeListener mBoundsChangeListener; 215 BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView, FrameLayout bubbleBarContainer)216 public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView, 217 FrameLayout bubbleBarContainer) { 218 mActivity = activity; 219 mBarView = barView; 220 mBubbleBarContainer = bubbleBarContainer; 221 mSystemUiProxy = SystemUiProxy.INSTANCE.get(mActivity); 222 mBubbleBarAlpha = new MultiValueAlpha(mBarView, 1 /* num alpha channels */); 223 Resources res = activity.getResources(); 224 mIconSize = res.getDimensionPixelSize(R.dimen.bubblebar_icon_size); 225 mBubbleBarTaskbarMinDistance = res.getDimensionPixelSize( 226 R.dimen.bubblebar_transient_taskbar_min_distance); 227 mDragElevation = res.getDimensionPixelSize(R.dimen.dragged_bubble_elevation); 228 mTaskbarTranslationDelta = getBubbleBarTranslationDeltaForTaskbar(activity); 229 if (DeviceConfig.isSmallTablet(mActivity)) { 230 mBubbleBarDropTargetSize = res.getDimensionPixelSize(R.dimen.drag_zone_bubble_fold); 231 } else { 232 mBubbleBarDropTargetSize = res.getDimensionPixelSize(R.dimen.drag_zone_bubble_tablet); 233 } 234 mBubbleBarLeftDropTarget = new BubbleBarLocationDropTarget(BubbleBarLocation.LEFT, 235 mDragListener); 236 mBubbleBarRightDropTarget = new BubbleBarLocationDropTarget(BubbleBarLocation.RIGHT, 237 mDragListener); 238 } 239 240 /** Initializes controller. */ init(TaskbarControllers controllers, BubbleControllers bubbleControllers, TaskbarViewPropertiesProvider taskbarViewPropertiesProvider)241 public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers, 242 TaskbarViewPropertiesProvider taskbarViewPropertiesProvider) { 243 mTaskbarSharedState = controllers.getSharedState(); 244 mBubbleStashController = bubbleControllers.bubbleStashController; 245 mBubbleBarController = bubbleControllers.bubbleBarController; 246 mBubbleDragController = bubbleControllers.bubbleDragController; 247 mBubbleBarPinController = bubbleControllers.bubbleBarPinController; 248 mTaskbarStashController = controllers.taskbarStashController; 249 mTaskbarInsetsController = controllers.taskbarInsetsController; 250 mBubbleBarFlyoutController = new BubbleBarFlyoutController( 251 mBubbleBarContainer, createFlyoutPositioner(), createFlyoutCallbacks()); 252 mBubbleBarViewAnimator = new BubbleBarViewAnimator( 253 mBarView, mBubbleStashController, mBubbleBarFlyoutController, 254 createBubbleBarParentViewController(), mBubbleBarController::showExpandedView, 255 () -> setHiddenForBubbles(false)); 256 mTaskbarViewPropertiesProvider = taskbarViewPropertiesProvider; 257 onBubbleBarConfigurationChanged(/* animate= */ false); 258 mActivity.addOnDeviceProfileChangeListener( 259 dp -> onBubbleBarConfigurationChanged(/* animate= */ true)); 260 mBubbleBarScaleY.updateValue(1f); 261 mBubbleClickListener = v -> onBubbleClicked((BubbleView) v); 262 mBubbleDragController.setupBubbleBarView(mBarView); 263 mOverflowBubble = bubbleControllers.bubbleCreator.createOverflow(mBarView); 264 if (!Flags.enableOptionalBubbleOverflow()) { 265 showOverflow(true); 266 } 267 if (!mBubbleStashController.isTransientTaskBar()) { 268 // TODO(b/380274085) for transient taskbar mode, the click is also handled by the input 269 // consumer. This check can be removed once b/380274085 is fixed. 270 mBarView.setOnClickListener(v -> setExpanded(!mBarView.isExpanded())); 271 } 272 mBarView.addOnLayoutChangeListener( 273 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { 274 mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged(); 275 if (mBoundsChangeListener != null) { 276 mBoundsChangeListener.onBoundsChanged(); 277 } 278 }); 279 float pinningValue = mActivity.isTransientTaskbar() 280 ? PINNING_TRANSIENT 281 : PINNING_PERSISTENT; 282 mBubbleBarPinning.updateValue(pinningValue); 283 mBarView.setController(new BubbleBarView.Controller() { 284 @Override 285 public float getBubbleBarTranslationY() { 286 return mBubbleStashController.getBubbleBarTranslationY(); 287 } 288 289 @Override 290 public void onBubbleBarTouched() { 291 if (isAnimatingNewBubble()) { 292 interruptAnimationForTouch(); 293 } 294 } 295 296 @Override 297 public void expandBubbleBar() { 298 BubbleBarViewController.this.setExpanded( 299 /* isExpanded= */ true, /* maybeShowEdu*/ true); 300 } 301 302 @Override 303 public void dismissBubbleBar() { 304 onDismissAllBubbles(); 305 } 306 307 @Override 308 public void updateBubbleBarLocation(BubbleBarLocation location, 309 @BubbleBarLocation.UpdateSource int source) { 310 mBubbleBarController.updateBubbleBarLocation(location, source); 311 } 312 313 @Override 314 public void setIsDragging(boolean dragging) { 315 mBubbleBarContainer.setElevation(dragging ? mDragElevation : 0); 316 } 317 }); 318 319 mBubbleViewController = new BubbleView.Controller() { 320 @Override 321 public BubbleBarLocation getBubbleBarLocation() { 322 return BubbleBarViewController.this.getBubbleBarLocation(); 323 } 324 325 @Override 326 public void dismiss(BubbleView bubble) { 327 if (bubble.getBubble() != null) { 328 notifySysUiBubbleDismissed(bubble.getBubble()); 329 } 330 onBubbleDismissed(bubble); 331 } 332 333 @Override 334 public void collapse() { 335 collapseBubbleBar(); 336 } 337 338 @Override 339 public void updateBubbleBarLocation(BubbleBarLocation location, 340 @BubbleBarLocation.UpdateSource int source) { 341 mBubbleBarController.updateBubbleBarLocation(location, source); 342 } 343 }; 344 } 345 346 /** Adds bubble bar locations drop zones to the drag controller. */ addBubbleBarDropTargets(DragController<?> dragController)347 public void addBubbleBarDropTargets(DragController<?> dragController) { 348 dragController.addDropTarget(mBubbleBarLeftDropTarget); 349 dragController.addDropTarget(mBubbleBarRightDropTarget); 350 } 351 352 /** Removes bubble bar locations drop zones to the drag controller. */ removeBubbleBarDropTargets(DragController<?> dragController)353 public void removeBubbleBarDropTargets(DragController<?> dragController) { 354 dragController.removeDropTarget(mBubbleBarLeftDropTarget); 355 dragController.removeDropTarget(mBubbleBarRightDropTarget); 356 } 357 358 /** Returns animated float property responsible for pinning transition animation. */ getBubbleBarPinning()359 public AnimatedFloat getBubbleBarPinning() { 360 return mBubbleBarPinning; 361 } 362 createFlyoutPositioner()363 private BubbleBarFlyoutPositioner createFlyoutPositioner() { 364 return new BubbleBarFlyoutPositioner() { 365 366 @Override 367 public boolean isOnLeft() { 368 boolean shouldRevertLocation = 369 mBarView.isShowingDropTarget() && isLocationUpdatedForDropTarget(); 370 boolean isOnLeft = mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl()); 371 return shouldRevertLocation != isOnLeft; 372 } 373 374 @Override 375 public float getTargetTy() { 376 return mBarView.getTranslationY() - mBarView.getHeight(); 377 } 378 379 @Override 380 @NonNull 381 public PointF getDistanceToCollapsedPosition() { 382 // the flyout animates from the selected bubble dot. calculate the distance it needs 383 // to translate itself to its starting position. 384 PointF distanceToDotCenter = mBarView.getSelectedBubbleDotDistanceFromTopLeft(); 385 386 // if we're gravitating left, return the distance between the top left corner of the 387 // bubble bar and the bottom left corner of the dot. 388 // if we're gravitating right, return the distance between the top right corner of 389 // the bubble bar and the bottom right corner of the dot. 390 float distanceX = isOnLeft() 391 ? distanceToDotCenter.x - getCollapsedSize() / 2 392 : mBarView.getWidth() - distanceToDotCenter.x - getCollapsedSize() / 2; 393 float distanceY = distanceToDotCenter.y + getCollapsedSize() / 2; 394 return new PointF(distanceX, distanceY); 395 } 396 397 @Override 398 public float getCollapsedSize() { 399 return mIconSize * DOT_TO_BUBBLE_SIZE_RATIO; 400 } 401 402 @Override 403 public int getCollapsedColor() { 404 return mBarView.getSelectedBubbleDotColor(); 405 } 406 407 @Override 408 public float getCollapsedElevation() { 409 return mBarView.getBubbleElevation(); 410 } 411 412 @Override 413 public float getDistanceToRevealTriangle() { 414 return getDistanceToCollapsedPosition().y - mBarView.getPointerSize(); 415 } 416 }; 417 } 418 419 private FlyoutCallbacks createFlyoutCallbacks() { 420 return new FlyoutCallbacks() { 421 @Override 422 public void flyoutClicked() { 423 interruptAnimationForTouch(); 424 setExpanded(/* isExpanded= */ true, /* maybeShowEdu*/ true); 425 } 426 }; 427 } 428 429 private BubbleBarParentViewHeightUpdateNotifier createBubbleBarParentViewController() { 430 return new BubbleBarParentViewHeightUpdateNotifier() { 431 @Override 432 public void updateTopBoundary() { 433 mActivity.setTaskbarWindowForAnimatingBubble(); 434 } 435 }; 436 } 437 438 private void onBubbleClicked(BubbleView bubbleView) { 439 if (mBubbleBarPinning.isAnimating()) return; 440 bubbleView.markSeen(); 441 BubbleBarItem bubble = bubbleView.getBubble(); 442 if (bubble == null) { 443 Log.e(TAG, "bubble click listener, bubble was null"); 444 } 445 446 final String currentlySelected = mBubbleBarController.getSelectedBubbleKey(); 447 if (mBarView.isExpanded() && Objects.equals(bubble.getKey(), currentlySelected)) { 448 // Tapping the currently selected bubble while expanded collapses the view. 449 collapseBubbleBar(); 450 } else { 451 mBubbleBarController.showAndSelectBubble(bubble); 452 } 453 } 454 455 /** Interrupts the running animation for a touch event on the bubble bar or flyout. */ 456 private void interruptAnimationForTouch() { 457 mBubbleBarViewAnimator.interruptForTouch(); 458 mBubbleStashController.onNewBubbleAnimationInterrupted(false, mBarView.getTranslationY()); 459 } 460 461 private void collapseBubbleBar() { 462 setExpanded(false); 463 mBubbleStashController.stashBubbleBar(); 464 } 465 466 /** Notifies that the stash state is changing. */ 467 public void onStashStateChanging() { 468 if (isAnimatingNewBubble()) { 469 mBubbleBarViewAnimator.onStashStateChangingWhileAnimating(); 470 } 471 } 472 473 /** Shows the education view if it was previously requested. */ 474 private boolean maybeShowEduView() { 475 if (mShouldShowEducation) { 476 mShouldShowEducation = false; 477 // Get the bubble bar bounds on screen 478 Rect bounds = new Rect(); 479 mBarView.getBoundsOnScreen(bounds); 480 // Calculate user education reference position in Screen coordinates 481 Point position = new Point(bounds.centerX(), bounds.top); 482 // Show user education relative to the reference point 483 mSystemUiProxy.showUserEducation(position); 484 return true; 485 } 486 return false; 487 } 488 489 /** Notifies that the IME became visible. */ 490 public void onImeVisible() { 491 if (isAnimatingNewBubble()) { 492 mBubbleBarViewAnimator.interruptForIme(); 493 } 494 } 495 496 // 497 // The below animators are exposed to BubbleStashController so it can manage the stashing 498 // animation. 499 // 500 501 public MultiPropertyFactory<View> getBubbleBarAlpha() { 502 return mBubbleBarAlpha; 503 } 504 505 public AnimatedFloat getBubbleBarBubbleAlpha() { 506 return mBubbleBarBubbleAlpha; 507 } 508 509 public AnimatedFloat getBubbleBarBackgroundAlpha() { 510 return mBubbleBarBackgroundAlpha; 511 } 512 513 public AnimatedFloat getBubbleBarScaleX() { 514 return mBubbleBarScaleX; 515 } 516 517 public AnimatedFloat getBubbleBarScaleY() { 518 return mBubbleBarScaleY; 519 } 520 521 public AnimatedFloat getBubbleBarBackgroundScaleX() { 522 return mBubbleBarBackgroundScaleX; 523 } 524 525 public AnimatedFloat getBubbleBarBackgroundScaleY() { 526 return mBubbleBarBackgroundScaleY; 527 } 528 529 public AnimatedFloat getBubbleBarTranslationY() { 530 return mBubbleBarTranslationY; 531 } 532 533 public AnimatedFloat getBubbleOffsetY() { 534 return mBubbleOffsetY; 535 } 536 537 public float getBubbleBarCollapsedWidth() { 538 return mBarView.collapsedWidth(); 539 } 540 541 public float getBubbleBarCollapsedHeight() { 542 return mBarView.getBubbleBarCollapsedHeight(); 543 } 544 545 /** Returns the bubble bar arrow height.*/ 546 public float getBubbleBarArrowHeight() { 547 return mBarView.getArrowHeight(); 548 } 549 550 /** 551 * @see BubbleBarView#getRelativePivotX() 552 */ 553 public float getBubbleBarRelativePivotX() { 554 return mBarView.getRelativePivotX(); 555 } 556 557 /** 558 * @see BubbleBarView#getRelativePivotY() 559 */ 560 public float getBubbleBarRelativePivotY() { 561 return mBarView.getRelativePivotY(); 562 } 563 564 /** 565 * @see BubbleBarView#setRelativePivot(float, float) 566 */ 567 public void setBubbleBarRelativePivot(float x, float y) { 568 mBarView.setRelativePivot(x, y); 569 } 570 571 /** 572 * Whether the bubble bar is visible or not. 573 */ 574 public boolean isBubbleBarVisible() { 575 return mBarView.getVisibility() == VISIBLE; 576 } 577 578 /** Whether the bubble bar has bubbles. */ 579 public boolean hasBubbles() { 580 return mBarView.getBubbleChildCount() > 0; 581 } 582 583 /** 584 * @return current {@link BubbleBarLocation} 585 */ 586 public BubbleBarLocation getBubbleBarLocation() { 587 return mBarView.getBubbleBarLocation(); 588 } 589 590 /** 591 * @return the max collapsed width for the bubble bar. 592 */ 593 public float getCollapsedWidthWithMaxVisibleBubbles() { 594 return mBarView.getCollapsedWidthWithMaxVisibleBubbles(); 595 } 596 597 /** 598 * @return {@code true} if bubble bar is on the left edge of the screen, {@code false} if on 599 * the right 600 */ 601 public boolean isBubbleBarOnLeft() { 602 return mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl()); 603 } 604 605 /** 606 * Update bar {@link BubbleBarLocation} 607 */ 608 public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { 609 mBarView.setBubbleBarLocation(bubbleBarLocation); 610 } 611 612 /** 613 * Animate bubble bar to the given location. The location change is transient. It does not 614 * update the state of the bubble bar. 615 * To update bubble bar pinned location, use {@link #setBubbleBarLocation(BubbleBarLocation)}. 616 */ 617 public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { 618 mBarView.animateToBubbleBarLocation(bubbleBarLocation); 619 } 620 621 /** Return animator for animating bubble bar in. */ 622 public Animator animateBubbleBarLocationIn(BubbleBarLocation fromLocation, 623 BubbleBarLocation toLocation) { 624 return mBarView.animateToBubbleBarLocationIn(fromLocation, toLocation); 625 } 626 627 /** Return animator for animating bubble bar out. */ 628 public Animator animateBubbleBarLocationOut(BubbleBarLocation toLocation) { 629 return mBarView.animateToBubbleBarLocationOut(toLocation); 630 } 631 632 /** Returns whether the Bubble Bar is currently displaying a drop target. */ 633 public boolean isShowingDropTarget() { 634 return mBarView.isShowingDropTarget(); 635 } 636 637 /** 638 * Notifies the controller that a drag event is over the Bubble Bar drop zone. The controller 639 * will display the appropriate drop target and enter drop target mode. The controller will also 640 * update the return value of {@link #isLocationUpdatedForDropTarget()} to true if location was 641 * updated. 642 */ 643 public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation bubbleBarLocation) { 644 mBubbleBarDragLocation = bubbleBarLocation; 645 mBarView.showDropTarget(/* isDropTarget = */ true); 646 mWasStashedBeforeEnteringBubbleDragZone = hasBubbles() 647 && mBubbleStashController.isStashed(); 648 if (mWasStashedBeforeEnteringBubbleDragZone) { 649 // bubble bar is stashed - un-stash at drag location 650 mBubbleStashController.showBubbleBarAtLocation( 651 /* fromLocation = */ getBubbleBarLocation(), 652 /* toLocation = */ mBubbleBarDragLocation 653 ); 654 } else if (hasBubbles()) { 655 if (isLocationUpdatedForDropTarget()) { 656 // bubble bar has bubbles and location is changed - animate bar to the opposite side 657 animateBubbleBarLocation(bubbleBarLocation); 658 } 659 } else { 660 // bubble bar has no bubbles flow just show the empty drop target 661 mBubbleBarPinController.showDropTarget(bubbleBarLocation); 662 } 663 } 664 665 /** 666 * Returns {@code true} if location was updated after most recent 667 * {@link #onDragItemOverBubbleBarDragZone}}. 668 */ 669 public boolean isLocationUpdatedForDropTarget() { 670 if (mBubbleBarDragLocation == null) { 671 return false; 672 } 673 boolean isRtl = mBarView.isLayoutRtl(); 674 return getBubbleBarLocation().isOnLeft(isRtl) 675 != mBubbleBarDragLocation.isOnLeft(isRtl); 676 } 677 678 /** 679 * Notifies the controller that the drag event is outside the Bubble Bar drop zone. 680 * This will hide the drop target zone if there are no bubbles or return the 681 * Bubble Bar to its original location. The controller will also exit drop target 682 * mode and reset the value returned from {@link #isLocationUpdatedForDropTarget()} to false. 683 */ 684 public void onItemDraggedOutsideBubbleBarDropZone() { 685 if (!isShowingDropTarget()) { 686 return; 687 } 688 if (mWasStashedBeforeEnteringBubbleDragZone && mBubbleBarDragLocation != null) { 689 // bubble bar was stashed - stash at original location 690 mBubbleStashController.stashBubbleBarToLocation( 691 /* fromLocation = */ mBubbleBarDragLocation, 692 /* toLocation = */ getBubbleBarLocation() 693 ); 694 } else if (hasBubbles()) { 695 if (isLocationUpdatedForDropTarget()) { 696 // bubble bar has bubbles and location was changed - return to the original 697 // location 698 animateBubbleBarLocation(getBubbleBarLocation()); 699 } 700 } 701 onItemDragCompleted(); 702 } 703 704 /** 705 * Notifies the controller that the drag has completed over the Bubble Bar drop zone. 706 * The controller will hide the drop target if there are no bubbles and exit drop target mode. 707 */ 708 public void onItemDragCompleted() { 709 mBarView.showDropTarget(/* isDropTarget = */ false); 710 mBubbleBarPinController.hideDropTarget(); 711 mWasStashedBeforeEnteringBubbleDragZone = false; 712 mBubbleBarDragLocation = null; 713 } 714 715 /** 716 * The bounds of the bubble bar. 717 */ 718 public Rect getBubbleBarBounds() { 719 return mBarView.getBubbleBarBounds(); 720 } 721 722 /** Returns the bounds of the flyout view if it exists, or {@code null} otherwise. */ 723 @Nullable 724 public Rect getFlyoutBounds() { 725 return mBubbleBarFlyoutController.getFlyoutBounds(); 726 } 727 728 /** Checks that bubble bar is visible and that the motion event is within bounds. */ 729 public boolean isEventOverBubbleBar(MotionEvent event) { 730 if (!isBubbleBarVisible()) return false; 731 final Rect bounds = getBubbleBarBounds(); 732 final int bubbleBarTopOnScreen = mBarView.getRestingTopPositionOnScreen(); 733 final float x = event.getX(); 734 return event.getRawY() >= bubbleBarTopOnScreen && x >= bounds.left && x <= bounds.right; 735 } 736 737 /** Whether a new bubble is animating. */ 738 public boolean isAnimatingNewBubble() { 739 return mBubbleBarViewAnimator != null && mBubbleBarViewAnimator.isAnimating(); 740 } 741 742 public boolean isNewBubbleAnimationRunningOrPending() { 743 return mBubbleBarViewAnimator != null && mBubbleBarViewAnimator.hasAnimation(); 744 } 745 746 /** The horizontal margin of the bubble bar from the edge of the screen. */ 747 public int getHorizontalMargin() { 748 return mBarView.getHorizontalMargin(); 749 } 750 751 /** 752 * When the bubble bar is not stashed, it can be collapsed (the icons are in a stack) or 753 * expanded (the icons are in a row). This indicates whether the bubble bar is expanded. 754 */ 755 public boolean isExpanded() { 756 return mBarView.isExpanded(); 757 } 758 759 /** 760 * Whether the motion event is within the bounds of the bubble bar. 761 */ 762 public boolean isEventOverAnyItem(MotionEvent ev) { 763 return mBarView.isEventOverAnyItem(ev); 764 } 765 766 // 767 // Visibility of the bubble bar 768 // 769 770 /** 771 * Returns whether the bubble bar is hidden because there are no bubbles. 772 */ 773 public boolean isHiddenForNoBubbles() { 774 return mHiddenForNoBubbles; 775 } 776 777 /** Returns maximum height of the bubble bar with the flyout view. */ 778 public int getBubbleBarWithFlyoutMaximumHeight() { 779 if (!hasBubbles() && !isAnimatingNewBubble()) return 0; 780 int bubbleBarTopOnHome = (int) (mBubbleStashController.getBubbleBarVerticalCenterForHome() 781 + mBarView.getBubbleBarCollapsedHeight() / 2 + mBarView.getArrowHeight()); 782 if (isAnimatingNewBubble()) { 783 if (mTaskbarStashController.isInApp() && mBubbleStashController.getHasHandleView()) { 784 // when animating a bubble in an app, the bubble bar will be higher than its 785 // position on home 786 float bubbleBarTopDistanceFromBottom = 787 -mBubbleStashController.getBubbleBarTranslationYForTaskbar() 788 + mBarView.getHeight(); 789 return (int) bubbleBarTopDistanceFromBottom 790 + mBubbleBarFlyoutController.getMaximumFlyoutHeight(); 791 } 792 return bubbleBarTopOnHome + mBubbleBarFlyoutController.getMaximumFlyoutHeight(); 793 } else { 794 return bubbleBarTopOnHome; 795 } 796 } 797 798 /** 799 * Sets whether the bubble bar should be hidden because there are no bubbles. 800 */ 801 public void setHiddenForBubbles(boolean hidden) { 802 if (mHiddenForNoBubbles != hidden) { 803 mHiddenForNoBubbles = hidden; 804 if (hidden) { 805 mBarView.dismiss(() -> { 806 updateVisibilityForStateChange(); 807 mBarView.setExpanded(false); 808 adjustTaskbarAndHotseatToBubbleBarState(/* isBubbleBarExpanded= */ false); 809 mActivity.bubbleBarVisibilityChanged(/* isVisible= */ false); 810 }); 811 } else { 812 updateVisibilityForStateChange(); 813 mActivity.bubbleBarVisibilityChanged(/* isVisible= */ true); 814 } 815 } 816 } 817 818 /** Sets a callback that updates the selected bubble after the bubble bar collapses. */ 819 public void setUpdateSelectedBubbleAfterCollapse( 820 Consumer<String> updateSelectedBubbleAfterCollapse) { 821 mBarView.setUpdateSelectedBubbleAfterCollapse(updateSelectedBubbleAfterCollapse); 822 } 823 824 /** Returns whether the bubble bar should be hidden because of the current sysui state. */ 825 boolean isHiddenForSysui() { 826 return mHiddenForSysui; 827 } 828 829 /** 830 * Sets whether the bubble bar should be hidden due to SysUI state (e.g. on lockscreen). 831 */ 832 public void setHiddenForSysui(boolean hidden) { 833 if (mHiddenForSysui != hidden) { 834 mHiddenForSysui = hidden; 835 updateVisibilityForStateChange(); 836 } 837 } 838 839 /** Sets whether the bubble bar should be hidden due to stashed state */ 840 public void setHiddenForStashed(boolean hidden) { 841 if (mHiddenForStashed != hidden) { 842 mHiddenForStashed = hidden; 843 updateVisibilityForStateChange(); 844 } 845 } 846 847 private void updateVisibilityForStateChange() { 848 boolean hiddenForStashedAndNotAnimating = 849 mHiddenForStashed && !mBubbleBarViewAnimator.isAnimating(); 850 if (mHiddenForSysui || mHiddenForNoBubbles || hiddenForStashedAndNotAnimating) { 851 //TODO(b/404870188) this visibility change cause search view drag misbehavior 852 mBarView.setVisibility(INVISIBLE); 853 } else { 854 mBarView.setVisibility(VISIBLE); 855 } 856 } 857 858 /** 859 * Returns the translation X of the transient taskbar according to the bubble bar location 860 * regardless of the current taskbar mode. 861 */ 862 public int getTransientTaskbarTranslationXForBubbleBar(BubbleBarLocation location) { 863 int taskbarShift = 0; 864 if (!isBubbleBarVisible() || mTaskbarViewPropertiesProvider == null) return taskbarShift; 865 Rect taskbarViewBounds = mTaskbarViewPropertiesProvider.getTaskbarViewBounds(); 866 if (taskbarViewBounds.isEmpty()) return taskbarShift; 867 int actualDistance = 868 getDistanceBetweenTransientTaskbarAndBubbleBar(location, taskbarViewBounds); 869 if (actualDistance < mBubbleBarTaskbarMinDistance) { 870 taskbarShift = mBubbleBarTaskbarMinDistance - actualDistance; 871 if (!location.isOnLeft(mBarView.isLayoutRtl())) { 872 taskbarShift = -taskbarShift; 873 } 874 } 875 return taskbarShift; 876 } 877 878 private int getDistanceBetweenTransientTaskbarAndBubbleBar(BubbleBarLocation location, 879 Rect taskbarViewBounds) { 880 Resources res = mActivity.getResources(); 881 DeviceProfile transientDp = mActivity.getTransientTaskbarDeviceProfile(); 882 int transientIconSize = getBubbleBarIconSizeFromDeviceProfile(res, transientDp); 883 int transientPadding = getBubbleBarPaddingFromDeviceProfile(res, transientDp); 884 int transientWidthWithMargin = (int) (mBarView.getCollapsedWidthForIconSizeAndPadding( 885 transientIconSize, transientPadding) + mBarView.getHorizontalMargin()); 886 int distance; 887 if (location.isOnLeft(mBarView.isLayoutRtl())) { 888 distance = taskbarViewBounds.left - transientWidthWithMargin; 889 } else { 890 int displayWidth = res.getDisplayMetrics().widthPixels; 891 int bubbleBarLeft = displayWidth - transientWidthWithMargin; 892 distance = bubbleBarLeft - taskbarViewBounds.right; 893 } 894 return distance; 895 } 896 897 // 898 // Modifying view related properties. 899 // 900 901 /** Notifies controller of configuration change, so bubble bar can be adjusted */ 902 public void onBubbleBarConfigurationChanged(boolean animate) { 903 int newIconSize; 904 int newPadding; 905 Resources res = mActivity.getResources(); 906 if (mBubbleStashController.isBubblesShowingOnHome() 907 || mBubbleStashController.isTransientTaskBar()) { 908 newIconSize = getBubbleBarIconSizeFromDeviceProfile(res); 909 newPadding = getBubbleBarPaddingFromDeviceProfile(res); 910 } else { 911 // the bubble bar is shown inside the persistent task bar, use preset sizes 912 newIconSize = res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_persistent_taskbar); 913 newPadding = res.getDimensionPixelSize( 914 R.dimen.bubblebar_icon_spacing_persistent_taskbar); 915 } 916 updateBubbleBarIconSizeAndPadding(newIconSize, newPadding, animate); 917 } 918 919 private int getBubbleBarIconSizeFromDeviceProfile(Resources res) { 920 return getBubbleBarIconSizeFromDeviceProfile(res, mActivity.getDeviceProfile()); 921 } 922 923 private int getBubbleBarIconSizeFromDeviceProfile(Resources res, DeviceProfile deviceProfile) { 924 DisplayMetrics dm = res.getDisplayMetrics(); 925 float smallIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 926 APP_ICON_SMALL_DP, dm); 927 float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 928 APP_ICON_MEDIUM_DP, dm); 929 float smallMediumThreshold = (smallIconSize + mediumIconSize) / 2f; 930 int taskbarIconSize = deviceProfile.taskbarIconSize; 931 return taskbarIconSize <= smallMediumThreshold 932 ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_small) : 933 res.getDimensionPixelSize(R.dimen.bubblebar_icon_size); 934 935 } 936 937 private int getBubbleBarPaddingFromDeviceProfile(Resources res) { 938 return getBubbleBarPaddingFromDeviceProfile(res, mActivity.getDeviceProfile()); 939 } 940 941 private int getBubbleBarPaddingFromDeviceProfile(Resources res, DeviceProfile deviceProfile) { 942 DisplayMetrics dm = res.getDisplayMetrics(); 943 float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 944 APP_ICON_MEDIUM_DP, dm); 945 float largeIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 946 APP_ICON_LARGE_DP, dm); 947 float mediumLargeThreshold = (mediumIconSize + largeIconSize) / 2f; 948 return deviceProfile.taskbarIconSize >= mediumLargeThreshold 949 ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing_large) : 950 res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing); 951 } 952 953 private void updateBubbleBarIconSizeAndPadding(int iconSize, int padding, boolean animate) { 954 if (mIconSize == iconSize && mBubbleBarPadding == padding) return; 955 mIconSize = iconSize; 956 mBubbleBarPadding = padding; 957 if (animate) { 958 mBarView.animateBubbleBarIconSize(iconSize, padding); 959 } else { 960 mBarView.setIconSizeAndPadding(iconSize, padding); 961 } 962 } 963 964 /** 965 * Sets the translation of the bubble bar during the swipe up gesture. 966 */ 967 public void setTranslationYForSwipe(float transY) { 968 mBubbleBarSwipeUpTranslationY = transY; 969 updateTranslationY(); 970 } 971 972 /** 973 * Sets the translation of the bubble bar during the stash animation. 974 */ 975 public void setTranslationYForStash(float transY) { 976 mBubbleBarStashTranslationY = transY; 977 updateTranslationY(); 978 } 979 980 private void updateTranslationY() { 981 mBarView.setTranslationY(mBubbleBarTranslationY.value + mBubbleBarSwipeUpTranslationY 982 + mBubbleBarStashTranslationY + getBubbleBarTranslationYForTaskbarPinning()); 983 } 984 985 /** Computes translation y for taskbar pinning. */ 986 private float getBubbleBarTranslationYForTaskbarPinning() { 987 if (mTaskbarSharedState == null) return 0f; 988 float pinningProgress = mBubbleBarPinning.value; 989 if (mTaskbarSharedState.startTaskbarVariantIsTransient) { 990 return mapRange(pinningProgress, /* min = */ 0f, mTaskbarTranslationDelta); 991 } else { 992 return mapRange(pinningProgress, -mTaskbarTranslationDelta, /* max = */ 0f); 993 } 994 } 995 996 private void setBubbleBarScaleAndPadding(float pinningProgress) { 997 Resources res = mActivity.getResources(); 998 // determine icon scale for pinning 999 int persistentIconSize = res.getDimensionPixelSize( 1000 R.dimen.bubblebar_icon_size_persistent_taskbar); 1001 int transientIconSize = getBubbleBarIconSizeFromDeviceProfile(res, 1002 mActivity.getTransientTaskbarDeviceProfile()); 1003 float pinningIconSize = mapRange(pinningProgress, transientIconSize, persistentIconSize); 1004 1005 // determine bubble bar padding for pinning 1006 int persistentPadding = res.getDimensionPixelSize( 1007 R.dimen.bubblebar_icon_spacing_persistent_taskbar); 1008 int transientPadding = getBubbleBarPaddingFromDeviceProfile(res, 1009 mActivity.getTransientTaskbarDeviceProfile()); 1010 float pinningPadding = mapRange(pinningProgress, transientPadding, persistentPadding); 1011 mBarView.setIconSizeAndPaddingForPinning(pinningIconSize, pinningPadding); 1012 } 1013 1014 /** 1015 * Calculates the vertical difference in the bubble bar positions for pinned and transient 1016 * taskbar modes. 1017 */ 1018 private int getBubbleBarTranslationDeltaForTaskbar(TaskbarActivityContext activity) { 1019 Resources res = activity.getResources(); 1020 int persistentBubbleSize = res 1021 .getDimensionPixelSize(R.dimen.bubblebar_icon_size_persistent_taskbar); 1022 int persistentSpacingSize = res 1023 .getDimensionPixelSize(R.dimen.bubblebar_icon_spacing_persistent_taskbar); 1024 int persistentBubbleBarSize = persistentBubbleSize + persistentSpacingSize * 2; 1025 int persistentTaskbarHeight = activity.getPersistentTaskbarDeviceProfile().taskbarHeight; 1026 int persistentBubbleBarY = (persistentTaskbarHeight - persistentBubbleBarSize) / 2; 1027 int transientBubbleBarY = activity.getTransientTaskbarDeviceProfile().taskbarBottomMargin; 1028 return transientBubbleBarY - persistentBubbleBarY; 1029 } 1030 1031 private void updateScaleX(float scale) { 1032 mBarView.setScaleX(scale); 1033 } 1034 1035 private void updateScaleY(float scale) { 1036 mBarView.setScaleY(scale); 1037 } 1038 1039 private void updateBackgroundScaleX(float scale) { 1040 mBarView.setBackgroundScaleX(scale); 1041 } 1042 1043 private void updateBackgroundScaleY(float scale) { 1044 mBarView.setBackgroundScaleY(scale); 1045 } 1046 1047 private void updateBubbleAlpha(float alpha) { 1048 mBarView.setBubbleAlpha(alpha); 1049 } 1050 1051 private void updateBubbleOffsetY(float transY) { 1052 mBarView.setBubbleOffsetY(transY); 1053 } 1054 1055 private void updateBackgroundAlpha(float alpha) { 1056 mBarView.setBackgroundAlpha(alpha); 1057 } 1058 1059 // 1060 // Manipulating the specific bubble views in the bar 1061 // 1062 1063 /** 1064 * Removes the provided bubble from the bubble bar. 1065 */ 1066 public void removeBubble(BubbleBarBubble b) { 1067 if (b != null) { 1068 mBarView.removeBubble(b.getView()); 1069 b.getView().setController(null); 1070 } else { 1071 Log.w(TAG, "removeBubble, bubble was null!"); 1072 } 1073 } 1074 1075 /** Adds a new bubble and removes an old bubble at the same time. */ 1076 public void addBubbleAndRemoveBubble(BubbleBarBubble addedBubble, BubbleBarBubble removedBubble, 1077 @Nullable BubbleBarBubble bubbleToSelect, boolean isExpanding, 1078 boolean suppressAnimation, boolean addOverflowToo) { 1079 BubbleView bubbleToSelectView = bubbleToSelect == null ? null : bubbleToSelect.getView(); 1080 mBarView.addBubbleAndRemoveBubble(addedBubble.getView(), removedBubble.getView(), 1081 bubbleToSelectView, addOverflowToo ? () -> showOverflow(true) : null); 1082 addedBubble.getView().setOnClickListener(mBubbleClickListener); 1083 addedBubble.getView().setController(mBubbleViewController); 1084 removedBubble.getView().setController(null); 1085 mBubbleDragController.setupBubbleView(addedBubble.getView()); 1086 if (!suppressAnimation) { 1087 animateBubbleNotification(addedBubble, isExpanding, /* isUpdate= */ false); 1088 } 1089 } 1090 1091 /** Whether the overflow view is added to the bubble bar. */ 1092 public boolean isOverflowAdded() { 1093 return mOverflowAdded; 1094 } 1095 1096 /** Shows or hides the overflow view. */ 1097 public void showOverflow(boolean showOverflow) { 1098 if (mOverflowAdded == showOverflow) return; 1099 mOverflowAdded = showOverflow; 1100 if (mOverflowAdded) { 1101 mBarView.addBubble(mOverflowBubble.getView()); 1102 mOverflowBubble.getView().setOnClickListener(mBubbleClickListener); 1103 mOverflowBubble.getView().setController(mBubbleViewController); 1104 } else { 1105 mBarView.removeBubble(mOverflowBubble.getView()); 1106 mOverflowBubble.getView().setOnClickListener(null); 1107 mOverflowBubble.getView().setController(null); 1108 } 1109 } 1110 1111 /** Adds the overflow view to the bubble bar while animating a view away. */ 1112 public void addOverflowAndRemoveBubble(BubbleBarBubble removedBubble, 1113 @Nullable BubbleBarBubble bubbleToSelect) { 1114 if (mOverflowAdded) return; 1115 mOverflowAdded = true; 1116 BubbleView bubbleToSelectView = bubbleToSelect == null ? null : bubbleToSelect.getView(); 1117 mBarView.addBubbleAndRemoveBubble(mOverflowBubble.getView(), removedBubble.getView(), 1118 bubbleToSelectView, null /* onEndRunnable */); 1119 mOverflowBubble.getView().setOnClickListener(mBubbleClickListener); 1120 mOverflowBubble.getView().setController(mBubbleViewController); 1121 removedBubble.getView().setController(null); 1122 } 1123 1124 /** Removes the overflow view to the bubble bar while animating a view in. */ 1125 public void removeOverflowAndAddBubble(BubbleBarBubble addedBubble, 1126 @Nullable BubbleBarBubble bubbleToSelect) { 1127 if (!mOverflowAdded) return; 1128 mOverflowAdded = false; 1129 BubbleView bubbleToSelectView = bubbleToSelect == null ? null : bubbleToSelect.getView(); 1130 mBarView.addBubbleAndRemoveBubble(addedBubble.getView(), mOverflowBubble.getView(), 1131 bubbleToSelectView, null /* onEndRunnable */); 1132 addedBubble.getView().setOnClickListener(mBubbleClickListener); 1133 addedBubble.getView().setController(mBubbleViewController); 1134 mOverflowBubble.getView().setController(null); 1135 } 1136 1137 /** 1138 * Adds the provided bubble to the bubble bar. 1139 */ 1140 public void addBubble(BubbleBarItem b, 1141 boolean isExpanding, 1142 boolean suppressAnimation, 1143 @Nullable BubbleBarBubble bubbleToSelect 1144 ) { 1145 if (b != null) { 1146 BubbleView bubbleToSelectView = 1147 bubbleToSelect == null ? null : bubbleToSelect.getView(); 1148 mBarView.addBubble(b.getView(), bubbleToSelectView); 1149 b.getView().setOnClickListener(mBubbleClickListener); 1150 mBubbleDragController.setupBubbleView(b.getView()); 1151 b.getView().setController(mBubbleViewController); 1152 1153 if (suppressAnimation || !(b instanceof BubbleBarBubble bubble)) { 1154 // the bubble bar and handle are initialized as part of the first bubble animation. 1155 // if the animation is suppressed, immediately stash or show the bubble bar to 1156 // ensure they've been initialized. 1157 if (mTaskbarStashController.isInApp() 1158 && mBubbleStashController.isTransientTaskBar() 1159 && mTaskbarStashController.isStashed()) { 1160 mBubbleStashController.stashBubbleBarImmediate(); 1161 } else { 1162 mBubbleStashController.showBubbleBarImmediate(); 1163 } 1164 return; 1165 } 1166 animateBubbleNotification(bubble, isExpanding, /* isUpdate= */ false); 1167 } else { 1168 Log.w(TAG, "addBubble, bubble was null!"); 1169 } 1170 } 1171 1172 /** Animates the bubble bar to notify the user about a bubble change. */ 1173 public void animateBubbleNotification(BubbleBarBubble bubble, boolean isExpanding, 1174 boolean isUpdate) { 1175 // if we're not already animating another bubble, update the dot visibility. otherwise the 1176 // the dot will be handled as part of the animation. 1177 if (!mBubbleBarViewAnimator.isAnimating()) { 1178 bubble.getView().updateDotVisibility( 1179 /* animate= */ !mBubbleStashController.isStashed()); 1180 } 1181 // if we're expanded, don't animate the bubble bar. 1182 if (isExpanded()) { 1183 return; 1184 } 1185 boolean isInApp = mTaskbarStashController.isInApp(); 1186 // if this is the first bubble, animate to the initial state. 1187 if (mBarView.getBubbleChildCount() == 1 && !isUpdate) { 1188 // If a drop target is visible and the first bubble is added, hide the empty drop target 1189 if (mBarView.isShowingDropTarget()) { 1190 mBubbleBarPinController.hideDropTarget(); 1191 } 1192 mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding, 1193 mBarView.isShowingDropTarget()); 1194 return; 1195 } 1196 // if we're not stashed or we're in persistent taskbar, animate for collapsed state. 1197 boolean animateForCollapsed = !mBubbleStashController.isStashed() 1198 || !mBubbleStashController.isTransientTaskBar(); 1199 if (animateForCollapsed) { 1200 mBubbleBarViewAnimator.animateBubbleBarForCollapsed(bubble, isExpanding); 1201 return; 1202 } 1203 1204 if (isInApp && mBubbleStashController.getHasHandleView()) { 1205 mBubbleBarViewAnimator.animateBubbleInForStashed(bubble, isExpanding); 1206 } 1207 } 1208 1209 /** 1210 * Reorders the bubbles based on the provided list. 1211 */ 1212 public void reorderBubbles(List<BubbleBarBubble> newOrder) { 1213 List<BubbleView> viewList = newOrder.stream().filter(Objects::nonNull) 1214 .map(BubbleBarBubble::getView).toList(); 1215 mBarView.reorder(viewList); 1216 } 1217 1218 /** 1219 * Updates the selected bubble. 1220 */ 1221 public void updateSelectedBubble(BubbleBarItem newlySelected) { 1222 mBarView.setSelectedBubble(newlySelected.getView()); 1223 } 1224 1225 /** @see #setExpanded(boolean, boolean) */ 1226 public void setExpanded(boolean isExpanded) { 1227 setExpanded(isExpanded, /* maybeShowEdu= */ false); 1228 } 1229 1230 /** 1231 * Sets whether the bubble bar should be expanded (not unstashed, but have the contents 1232 * within it expanded). This method notifies SystemUI that the bubble bar is expanded and 1233 * showing a selected bubble. This method should ONLY be called from UI events originating 1234 * from Launcher. 1235 * 1236 * @param isExpanded whether the bar should be expanded 1237 * @param maybeShowEdu whether we should show the edu view before expanding 1238 */ 1239 public void setExpanded(boolean isExpanded, boolean maybeShowEdu) { 1240 // if we're trying to expand try showing the edu view instead 1241 if (maybeShowEdu && isExpanded && !mBarView.isExpanded() && maybeShowEduView()) { 1242 return; 1243 } 1244 if (!mBubbleBarPinning.isAnimating() && isExpanded != mBarView.isExpanded()) { 1245 mBarView.setExpanded(isExpanded); 1246 adjustTaskbarAndHotseatToBubbleBarState(isExpanded); 1247 if (!isExpanded) { 1248 mSystemUiProxy.collapseBubbles(); 1249 } else { 1250 mBubbleBarController.showSelectedBubble(); 1251 mTaskbarStashController.updateAndAnimateTransientTaskbar(true /* stash */, 1252 false /* shouldBubblesFollow */); 1253 } 1254 } 1255 } 1256 1257 /** 1258 * Hides the persistent taskbar if it is going to intersect with the expanded bubble bar if in 1259 * app or overview. 1260 */ 1261 private void adjustTaskbarAndHotseatToBubbleBarState(boolean isBubbleBarExpanded) { 1262 if (!mBubbleStashController.isBubblesShowingOnHome() 1263 && !mBubbleStashController.isTransientTaskBar()) { 1264 boolean hideTaskbar = isBubbleBarExpanded && isIntersectingTaskbar(); 1265 Animator taskbarAlphaAnimator = mTaskbarViewPropertiesProvider.getIconsAlpha() 1266 .animateToValue(hideTaskbar ? 0 : 1); 1267 taskbarAlphaAnimator.setDuration(hideTaskbar 1268 ? TASKBAR_FADE_OUT_DURATION_MS : TASKBAR_FADE_IN_DURATION_MS); 1269 if (!hideTaskbar) { 1270 taskbarAlphaAnimator.setStartDelay(TASKBAR_FADE_IN_DELAY_MS); 1271 } 1272 taskbarAlphaAnimator.setInterpolator(Interpolators.LINEAR); 1273 taskbarAlphaAnimator.start(); 1274 } 1275 } 1276 1277 /** Return {@code true} if expanded bubble bar would intersect the taskbar. */ 1278 public boolean isIntersectingTaskbar() { 1279 if (mBarView.isExpanding() || mBarView.isExpanded()) { 1280 Rect taskbarViewBounds = mTaskbarViewPropertiesProvider.getTaskbarViewBounds(); 1281 return mBarView.getBubbleBarExpandedBounds().intersect(taskbarViewBounds); 1282 } else { 1283 return false; 1284 } 1285 } 1286 1287 /** 1288 * Sets whether the bubble bar should be expanded. This method is used in response to UI events 1289 * from SystemUI. 1290 */ 1291 public void setExpandedFromSysui(boolean isExpanded) { 1292 if (isNewBubbleAnimationRunningOrPending() && isExpanded) { 1293 mBubbleBarViewAnimator.expandedWhileAnimating(); 1294 return; 1295 } 1296 if (!isExpanded) { 1297 mBubbleStashController.stashBubbleBar(); 1298 } else { 1299 mBubbleStashController.showBubbleBar(true /* expand the bubbles */); 1300 } 1301 } 1302 1303 /** 1304 * Stores a request to show the education view for later processing when appropriate. 1305 * 1306 * @see #maybeShowEduView() 1307 */ 1308 public void prepareToShowEducation() { 1309 mShouldShowEducation = true; 1310 } 1311 1312 /** 1313 * Updates the dragged bubble view in the bubble bar view, and notifies SystemUI 1314 * that a bubble is being dragged to dismiss. 1315 * 1316 * @param bubbleView dragged bubble view 1317 */ 1318 public void onBubbleDragStart(@NonNull BubbleView bubbleView) { 1319 if (bubbleView.getBubble() == null) return; 1320 1321 mSystemUiProxy.startBubbleDrag(bubbleView.getBubble().getKey()); 1322 mBarView.setDraggedBubble(bubbleView); 1323 } 1324 1325 /** 1326 * Notifies SystemUI to expand the selected bubble when the bubble is released. 1327 */ 1328 public void onBubbleDragRelease(BubbleBarLocation location) { 1329 mSystemUiProxy.stopBubbleDrag(location, mBarView.getRestingTopPositionOnScreen()); 1330 } 1331 1332 /** Handle given bubble being dismissed */ 1333 public void onBubbleDismissed(BubbleView bubble) { 1334 mBubbleBarController.onBubbleDismissed(bubble); 1335 mBarView.removeBubble(bubble); 1336 } 1337 1338 /** 1339 * Notifies {@link BubbleBarView} that drag and all animations are finished. 1340 */ 1341 public void onBubbleDragEnd() { 1342 mBarView.setDraggedBubble(null); 1343 } 1344 1345 /** Notifies that dragging the bubble bar ended. */ 1346 public void onBubbleBarDragEnd() { 1347 // we may have changed the bubble bar translation Y value from the value it had at the 1348 // beginning of the drag, so update the translation Y animator state 1349 mBubbleBarTranslationY.updateValue(mBarView.getTranslationY()); 1350 } 1351 1352 /** 1353 * Get translation for bubble bar when drag is released. 1354 * 1355 * @see BubbleBarView#getBubbleBarDragReleaseTranslation(PointF, BubbleBarLocation) 1356 */ 1357 public PointF getBubbleBarDragReleaseTranslation(PointF initialTranslation, 1358 BubbleBarLocation location) { 1359 return mBarView.getBubbleBarDragReleaseTranslation(initialTranslation, location); 1360 } 1361 1362 /** 1363 * Get translation for bubble view when drag is released. 1364 * 1365 * @see BubbleBarView#getDraggedBubbleReleaseTranslation(PointF, BubbleBarLocation) 1366 */ 1367 public PointF getDraggedBubbleReleaseTranslation(PointF initialTranslation, 1368 BubbleBarLocation location) { 1369 if (location == mBarView.getBubbleBarLocation()) { 1370 return initialTranslation; 1371 } 1372 return mBarView.getDraggedBubbleReleaseTranslation(initialTranslation, location); 1373 } 1374 1375 /** 1376 * Notify SystemUI that the given bubble has been dismissed. 1377 */ 1378 public void notifySysUiBubbleDismissed(@NonNull BubbleBarItem bubble) { 1379 mSystemUiProxy.dragBubbleToDismiss(bubble.getKey(), mTimeSource.currentTimeMillis()); 1380 } 1381 1382 /** 1383 * Called when bubble stack was dismissed 1384 */ 1385 public void onDismissAllBubbles() { 1386 mSystemUiProxy.removeAllBubbles(); 1387 } 1388 1389 /** Removes all existing bubble views */ 1390 public void removeAllBubbles() { 1391 mOverflowAdded = false; 1392 mBarView.removeAllViews(); 1393 } 1394 1395 /** Returns the view index of the existing bubble */ 1396 public int bubbleViewIndex(View bubbleView) { 1397 return mBarView.indexOfChild(bubbleView); 1398 } 1399 1400 /** 1401 * Set listener to be notified when bubble bar bounds have changed 1402 */ 1403 public void setBoundsChangeListener(@Nullable BubbleBarBoundsChangeListener listener) { 1404 mBoundsChangeListener = listener; 1405 } 1406 1407 /** Called when the controller is destroyed. */ 1408 public void onDestroy() { 1409 adjustTaskbarAndHotseatToBubbleBarState(/*isBubbleBarExpanded = */false); 1410 } 1411 1412 /** 1413 * Removes the bubble from the bubble bar and notifies sysui that the bubble should move to 1414 * full screen. 1415 */ 1416 public void moveDraggedBubbleToFullscreen(@NonNull BubbleView bubbleView, Point dropLocation) { 1417 if (bubbleView.getBubble() == null) { 1418 return; 1419 } 1420 String key = bubbleView.getBubble().getKey(); 1421 mSystemUiProxy.moveDraggedBubbleToFullscreen(key, dropLocation); 1422 onBubbleDismissed(bubbleView); 1423 } 1424 1425 /** 1426 * Create an animator for showing or hiding bubbles when stashed state changes 1427 * 1428 * @param isStashed {@code true} when bubble bar should be stashed to the handle 1429 */ 1430 public Animator createRevealAnimatorForStashChange(boolean isStashed) { 1431 Rect stashedHandleBounds = new Rect(); 1432 mBubbleStashController.getHandleBounds(stashedHandleBounds); 1433 int childCount = mBarView.getChildCount(); 1434 float newChildWidth = (float) stashedHandleBounds.width() / childCount; 1435 AnimatorSet animatorSet = new AnimatorSet(); 1436 for (int i = 0; i < childCount; i++) { 1437 BubbleView child = (BubbleView) mBarView.getChildAt(i); 1438 animatorSet.play( 1439 createRevealAnimForBubble(child, isStashed, stashedHandleBounds, 1440 newChildWidth)); 1441 } 1442 return animatorSet; 1443 } 1444 1445 private Animator createRevealAnimForBubble(BubbleView bubbleView, boolean isStashed, 1446 Rect stashedHandleBounds, float newWidth) { 1447 Rect viewBounds = new Rect(0, 0, bubbleView.getWidth(), bubbleView.getHeight()); 1448 1449 int viewCenterY = viewBounds.centerY(); 1450 int halfHandleHeight = stashedHandleBounds.height() / 2; 1451 int widthDelta = Math.max(0, (int) (viewBounds.width() - newWidth) / 2); 1452 1453 Rect stashedViewBounds = new Rect( 1454 viewBounds.left + widthDelta, 1455 viewCenterY - halfHandleHeight, 1456 viewBounds.right - widthDelta, 1457 viewCenterY + halfHandleHeight 1458 ); 1459 1460 float viewRadius = 0f; // Use 0 to not clip the new message dot or the app icon 1461 float stashedRadius = stashedViewBounds.height() / 2f; 1462 1463 return new RoundedRectRevealOutlineProvider(viewRadius, stashedRadius, viewBounds, 1464 stashedViewBounds).createRevealAnimator(bubbleView, !isStashed, 0); 1465 } 1466 1467 /** 1468 * Listener to receive updates about bubble bar bounds changing 1469 */ 1470 public interface BubbleBarBoundsChangeListener { 1471 /** Called when bounds have changed */ 1472 void onBoundsChanged(); 1473 } 1474 1475 /** Interface for getting the current timestamp. */ 1476 interface TimeSource { 1477 long currentTimeMillis(); 1478 } 1479 1480 /** Dumps the state of BubbleBarViewController. */ 1481 public void dump(PrintWriter pw) { 1482 pw.println("Bubble bar view controller state:"); 1483 pw.println(" mHiddenForSysui: " + mHiddenForSysui); 1484 pw.println(" mHiddenForNoBubbles: " + mHiddenForNoBubbles); 1485 pw.println(" mHiddenForStashed: " + mHiddenForStashed); 1486 pw.println(" mShouldShowEducation: " + mShouldShowEducation); 1487 pw.println(" mBubbleBarTranslationY.value: " + mBubbleBarTranslationY.value); 1488 pw.println(" mBubbleBarSwipeUpTranslationY: " + mBubbleBarSwipeUpTranslationY); 1489 pw.println(" mOverflowAdded: " + mOverflowAdded); 1490 if (mBarView != null) { 1491 mBarView.dump(pw); 1492 } else { 1493 pw.println(" Bubble bar view is null!"); 1494 } 1495 } 1496 1497 /** Interface for BubbleBarViewController to get the taskbar view properties. */ 1498 public interface TaskbarViewPropertiesProvider { 1499 1500 /** Returns the bounds of the taskbar. */ 1501 Rect getTaskbarViewBounds(); 1502 1503 /** Returns taskbar icons alpha */ 1504 MultiPropertyFactory<View>.MultiProperty getIconsAlpha(); 1505 } 1506 } 1507