1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wm.shell.bubbles.bar; 18 19 import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN; 20 import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_OUT; 21 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_GESTURE; 22 import static com.android.wm.shell.shared.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA; 23 24 import android.annotation.Nullable; 25 import android.content.Context; 26 import android.graphics.Point; 27 import android.graphics.Rect; 28 import android.graphics.Region; 29 import android.graphics.drawable.ColorDrawable; 30 import android.util.Log; 31 import android.view.Gravity; 32 import android.view.SurfaceControl; 33 import android.view.TouchDelegate; 34 import android.view.View; 35 import android.view.ViewTreeObserver; 36 import android.view.WindowManager; 37 import android.widget.FrameLayout; 38 39 import androidx.annotation.NonNull; 40 import androidx.annotation.VisibleForTesting; 41 42 import com.android.wm.shell.bubbles.Bubble; 43 import com.android.wm.shell.bubbles.BubbleController; 44 import com.android.wm.shell.bubbles.BubbleData; 45 import com.android.wm.shell.bubbles.BubbleLogger; 46 import com.android.wm.shell.bubbles.BubbleOverflow; 47 import com.android.wm.shell.bubbles.BubblePositioner; 48 import com.android.wm.shell.bubbles.BubbleViewProvider; 49 import com.android.wm.shell.bubbles.DismissViewUtils; 50 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener; 51 import com.android.wm.shell.shared.bubbles.BaseBubblePinController; 52 import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; 53 import com.android.wm.shell.shared.bubbles.BubbleBarLocation; 54 import com.android.wm.shell.shared.bubbles.DeviceConfig; 55 import com.android.wm.shell.shared.bubbles.DismissView; 56 import com.android.wm.shell.shared.bubbles.DragZone; 57 import com.android.wm.shell.shared.bubbles.DragZoneFactory; 58 import com.android.wm.shell.shared.bubbles.DraggedObject; 59 import com.android.wm.shell.shared.bubbles.DropTargetManager; 60 61 import kotlin.Unit; 62 63 import java.util.Objects; 64 import java.util.function.Consumer; 65 66 /** 67 * Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window 68 * manager to display bubbles. However, it is only used when bubbles are being displayed in 69 * launcher in the bubble bar. This view does not show a stack of bubbles that can be moved around 70 * on screen and instead shows & animates the expanded bubble for the bubble bar. 71 */ 72 public class BubbleBarLayerView extends FrameLayout 73 implements ViewTreeObserver.OnComputeInternalInsetsListener { 74 75 private static final String TAG = BubbleBarLayerView.class.getSimpleName(); 76 77 private final BubbleController mBubbleController; 78 private final BubbleData mBubbleData; 79 private final BubblePositioner mPositioner; 80 private final BubbleLogger mBubbleLogger; 81 private final BubbleBarAnimationHelper mAnimationHelper; 82 private final BubbleEducationViewController mEducationViewController; 83 private final View mScrimView; 84 private final BubbleExpandedViewPinController mBubbleExpandedViewPinController; 85 @Nullable 86 private DropTargetManager mDropTargetManager = null; 87 @Nullable 88 private DragZoneFactory mDragZoneFactory = null; 89 90 @Nullable 91 private BubbleViewProvider mExpandedBubble; 92 @Nullable 93 private BubbleBarExpandedView mExpandedView; 94 @Nullable 95 private BubbleBarExpandedViewDragController mDragController; 96 private DismissView mDismissView; 97 private @Nullable Consumer<String> mUnBubbleConversationCallback; 98 99 /** Whether a bubble is expanded. */ 100 private boolean mIsExpanded = false; 101 102 private final Region mTouchableRegion = new Region(); 103 private final Rect mTempRect = new Rect(); 104 105 // Used to ensure touch target size for the menu shown on a bubble expanded view 106 private TouchDelegate mHandleTouchDelegate; 107 private final Rect mHandleTouchBounds = new Rect(); 108 BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData, BubbleLogger bubbleLogger)109 public BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData, 110 BubbleLogger bubbleLogger) { 111 super(context); 112 mBubbleController = controller; 113 mBubbleData = bubbleData; 114 mPositioner = mBubbleController.getPositioner(); 115 mBubbleLogger = bubbleLogger; 116 117 mAnimationHelper = new BubbleBarAnimationHelper(context, mPositioner); 118 mEducationViewController = new BubbleEducationViewController(context, (boolean visible) -> { 119 if (mExpandedView == null) return; 120 mExpandedView.setObscured(visible); 121 }); 122 123 mScrimView = new View(getContext()); 124 mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 125 mScrimView.setBackgroundDrawable(new ColorDrawable( 126 getResources().getColor(android.R.color.system_neutral1_1000))); 127 addView(mScrimView); 128 mScrimView.setAlpha(0f); 129 mScrimView.setBackgroundDrawable(new ColorDrawable( 130 getResources().getColor(android.R.color.system_neutral1_1000))); 131 132 setUpDismissView(); 133 134 mBubbleExpandedViewPinController = new BubbleExpandedViewPinController( 135 context, this, mPositioner); 136 LocationChangeListener locationChangeListener = new LocationChangeListener(); 137 mBubbleExpandedViewPinController.setListener(locationChangeListener); 138 139 if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) { 140 mDropTargetManager = new DropTargetManager(context, this, 141 new DropTargetManager.DragZoneChangedListener() { 142 private DragZone mLastBubbleLocationDragZone = null; 143 private BubbleBarLocation mInitialLocation = null; 144 @Override 145 public void onDragEnded(@NonNull DragZone zone) { 146 if (mExpandedBubble == null || !(mExpandedBubble instanceof Bubble)) { 147 Log.w(TAG, "dropped invalid bubble: " + mExpandedBubble); 148 return; 149 } 150 151 final boolean isBubbleLeft = zone instanceof DragZone.Bubble.Left; 152 final boolean isBubbleRight = zone instanceof DragZone.Bubble.Right; 153 if (!isBubbleLeft && !isBubbleRight) { 154 // If we didn't finish the "change" animation make sure to animate 155 // it back to the right spot 156 locationChangeListener.onChange(mInitialLocation); 157 } 158 if (zone instanceof DragZone.FullScreen) { 159 ((Bubble) mExpandedBubble).getTaskView().moveToFullscreen(); 160 // Make sure location change listener is updated with the initial 161 // location -- even if we "switched sides" during the drag, since 162 // we've ended up in fullscreen, the location shouldn't change. 163 locationChangeListener.onRelease(mInitialLocation); 164 } else if (isBubbleLeft) { 165 locationChangeListener.onRelease(BubbleBarLocation.LEFT); 166 } else if (isBubbleRight) { 167 locationChangeListener.onRelease(BubbleBarLocation.RIGHT); 168 } 169 } 170 171 @Override 172 public void onInitialDragZoneSet(@NonNull DragZone dragZone) { 173 mInitialLocation = dragZone instanceof DragZone.Bubble.Left 174 ? BubbleBarLocation.LEFT 175 : BubbleBarLocation.RIGHT; 176 locationChangeListener.onStart(mInitialLocation); 177 } 178 179 @Override 180 public void onDragZoneChanged(@NonNull DraggedObject draggedObject, 181 @NonNull DragZone from, @NonNull DragZone to) { 182 final boolean isBubbleLeft = to instanceof DragZone.Bubble.Left; 183 final boolean isBubbleRight = to instanceof DragZone.Bubble.Right; 184 if ((isBubbleLeft || isBubbleRight) 185 && to != mLastBubbleLocationDragZone) { 186 mLastBubbleLocationDragZone = to; 187 locationChangeListener.onChange(isBubbleLeft 188 ? BubbleBarLocation.LEFT 189 : BubbleBarLocation.RIGHT); 190 191 } 192 } 193 }); 194 // TODO - currently only fullscreen is supported, should enable for split & desktop 195 mDragZoneFactory = new DragZoneFactory(context, mPositioner.getCurrentConfig(), 196 new DragZoneFactory.SplitScreenModeChecker() { 197 @NonNull 198 @Override 199 public SplitScreenMode getSplitScreenMode() { 200 return SplitScreenMode.UNSUPPORTED; 201 } 202 }, 203 new DragZoneFactory.DesktopWindowModeChecker() { 204 @Override 205 public boolean isSupported() { 206 return false; 207 } 208 }); 209 } 210 setOnClickListener(view -> hideModalOrCollapse()); 211 } 212 213 /** Hides the expanded view drop target. */ hideBubbleBarExpandedViewDropTarget()214 public void hideBubbleBarExpandedViewDropTarget() { 215 mBubbleExpandedViewPinController.hideDropTarget(); 216 } 217 218 /** Shows the expanded view drop target at the requested {@link BubbleBarLocation location} */ showBubbleBarExtendedViewDropTarget(@onNull BubbleBarLocation bubbleBarLocation)219 public void showBubbleBarExtendedViewDropTarget(@NonNull BubbleBarLocation bubbleBarLocation) { 220 setVisibility(VISIBLE); 221 mBubbleExpandedViewPinController.showDropTarget(bubbleBarLocation); 222 } 223 224 @Override onAttachedToWindow()225 protected void onAttachedToWindow() { 226 super.onAttachedToWindow(); 227 WindowManager windowManager = mContext.getSystemService(WindowManager.class); 228 mPositioner.update(DeviceConfig.create(mContext, Objects.requireNonNull(windowManager))); 229 getViewTreeObserver().addOnComputeInternalInsetsListener(this); 230 } 231 232 @Override onDetachedFromWindow()233 protected void onDetachedFromWindow() { 234 super.onDetachedFromWindow(); 235 getViewTreeObserver().removeOnComputeInternalInsetsListener(this); 236 237 if (mExpandedView != null) { 238 mEducationViewController.hideEducation(/* animated = */ false); 239 removeView(mExpandedView); 240 mExpandedView = null; 241 } 242 } 243 244 @Override onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo)245 public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { 246 inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); 247 mTouchableRegion.setEmpty(); 248 getTouchableRegion(mTouchableRegion); 249 inoutInfo.touchableRegion.set(mTouchableRegion); 250 } 251 252 /** Updates the sizes of any displaying expanded view. */ onDisplaySizeChanged()253 public void onDisplaySizeChanged() { 254 if (mIsExpanded && mExpandedView != null) { 255 updateExpandedView(); 256 } 257 } 258 259 /** Whether the stack of bubbles is expanded or not. */ isExpanded()260 public boolean isExpanded() { 261 return mIsExpanded; 262 } 263 264 /** Return whether the expanded view is being dragged */ isExpandedViewDragged()265 public boolean isExpandedViewDragged() { 266 return mDragController != null && mDragController.isDragged(); 267 } 268 269 /** Shows the expanded view of the provided bubble. */ showExpandedView(BubbleViewProvider b)270 public void showExpandedView(BubbleViewProvider b) { 271 if (!canExpandView(b)) return; 272 animateExpand(prepareExpandedView(b)); 273 } 274 275 /** 276 * @return whether it's possible to expand {@param b} right now. This is {@code false} if 277 * the bubble has no view or if the bubble is already showing. 278 */ canExpandView(BubbleViewProvider b)279 public boolean canExpandView(BubbleViewProvider b) { 280 if (b.getBubbleBarExpandedView() == null) return false; 281 if (mExpandedBubble != null && mIsExpanded && b.getKey().equals(mExpandedBubble.getKey())) { 282 // Already showing this bubble so can't expand it. 283 return false; 284 } 285 return true; 286 } 287 288 /** 289 * Prepares the expanded view of the provided bubble to be shown. This includes removing any 290 * stale content and cancelling any related animations. 291 * 292 * @return previous open bubble if there was one. 293 */ prepareExpandedView(BubbleViewProvider b)294 private BubbleViewProvider prepareExpandedView(BubbleViewProvider b) { 295 if (!canExpandView(b)) { 296 throw new IllegalStateException("Can't prepare expand. Check canExpandView(b) first."); 297 } 298 BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView(); 299 BubbleViewProvider previousBubble = null; 300 if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) { 301 if (mIsExpanded && mExpandedBubble.getBubbleBarExpandedView() != null) { 302 // Previous expanded view open, keep it visible to animate the switch 303 previousBubble = mExpandedBubble; 304 } else { 305 removeView(mExpandedView); 306 } 307 mExpandedView = null; 308 } 309 if (mExpandedView == null) { 310 if (expandedView.getParent() != null) { 311 // Expanded view might be animating collapse and is still attached 312 // Cancel current animations and remove from parent 313 mAnimationHelper.cancelAnimations(); 314 removeView(expandedView); 315 } 316 mExpandedBubble = b; 317 mExpandedView = expandedView; 318 boolean isOverflowExpanded = b.getKey().equals(BubbleOverflow.KEY); 319 final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded); 320 final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded); 321 mExpandedView.setVisibility(GONE); 322 mExpandedView.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height); 323 mExpandedView.setLayerBoundsSupplier(() -> new Rect(0, 0, getWidth(), getHeight())); 324 mExpandedView.setListener(new BubbleBarExpandedView.Listener() { 325 @Override 326 public void onTaskCreated() { 327 if (mEducationViewController != null && mExpandedView != null) { 328 mEducationViewController.maybeShowManageEducation(b, mExpandedView); 329 } 330 } 331 332 @Override 333 public void onUnBubbleConversation(String bubbleKey) { 334 if (mUnBubbleConversationCallback != null) { 335 mUnBubbleConversationCallback.accept(bubbleKey); 336 } 337 } 338 339 @Override 340 public void onBackPressed() { 341 hideModalOrCollapse(); 342 } 343 }); 344 345 DragListener dragListener = inDismiss -> { 346 if (inDismiss && mExpandedBubble != null) { 347 mBubbleController.dismissBubble(mExpandedBubble.getKey(), DISMISS_USER_GESTURE); 348 logBubbleEvent(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW); 349 } 350 }; 351 mDragController = new BubbleBarExpandedViewDragController( 352 mContext, 353 mExpandedView, 354 mDismissView, 355 mAnimationHelper, 356 mPositioner, 357 mBubbleExpandedViewPinController, 358 mDropTargetManager, 359 mDragZoneFactory, 360 dragListener); 361 362 addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT)); 363 } 364 365 if (mEducationViewController.isEducationVisible()) { 366 mEducationViewController.hideEducation(/* animated = */ true); 367 } 368 369 mIsExpanded = true; 370 mBubbleController.getSysuiProxy().onStackExpandChanged(true); 371 showScrim(true); 372 return previousBubble; 373 } 374 375 /** 376 * Performs an animation to open a bubble with content that is not already visible. 377 * 378 * @param previousBubble If non-null, this is a bubble that is already showing before the new 379 * bubble is expanded. 380 */ animateExpand(BubbleViewProvider previousBubble)381 public void animateExpand(BubbleViewProvider previousBubble) { 382 if (!mIsExpanded || mExpandedBubble == null) { 383 throw new IllegalStateException("Can't animateExpand without expnaded state"); 384 } 385 final Runnable afterAnimation = () -> { 386 if (mExpandedView == null) return; 387 // Touch delegate for the menu 388 BubbleBarHandleView view = mExpandedView.getHandleView(); 389 view.getBoundsOnScreen(mHandleTouchBounds); 390 // Move top value up to ensure touch target is large enough 391 mHandleTouchBounds.top -= mPositioner.getBubblePaddingTop(); 392 mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds, 393 mExpandedView.getHandleView()); 394 setTouchDelegate(mHandleTouchDelegate); 395 }; 396 397 if (previousBubble != null) { 398 final BubbleBarExpandedView previousExpandedView = 399 previousBubble.getBubbleBarExpandedView(); 400 mAnimationHelper.animateSwitch(previousBubble, mExpandedBubble, () -> { 401 removeView(previousExpandedView); 402 afterAnimation.run(); 403 }); 404 } else { 405 mAnimationHelper.animateExpansion(mExpandedBubble, afterAnimation); 406 } 407 } 408 409 /** 410 * Like {@link #prepareExpandedView} but also makes the current expanded bubble visible 411 * immediately so it gets a surface that can be animated. Since the surface may not be ready 412 * yet, this keeps the TaskView alpha=0. 413 */ prepareConvertedView(BubbleViewProvider b)414 public BubbleViewProvider prepareConvertedView(BubbleViewProvider b) { 415 final BubbleViewProvider prior = prepareExpandedView(b); 416 417 final BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView(); 418 if (bbev != null) { 419 updateExpandedView(); 420 bbev.setAnimating(true); 421 bbev.setContentVisibility(true); 422 bbev.setSurfaceZOrderedOnTop(true); 423 bbev.setTaskViewAlpha(0.f); 424 bbev.setVisibility(VISIBLE); 425 } 426 427 return prior; 428 } 429 430 /** 431 * Starts and animates a conversion-from transition. 432 * 433 * @param startT A transaction with first-frame work. this *will* be applied here! 434 */ animateConvert(@onNull SurfaceControl.Transaction startT, @NonNull Rect startBounds, float startScale, @NonNull SurfaceControl snapshot, SurfaceControl taskLeash, Runnable animFinish)435 public void animateConvert(@NonNull SurfaceControl.Transaction startT, 436 @NonNull Rect startBounds, float startScale, @NonNull SurfaceControl snapshot, 437 SurfaceControl taskLeash, Runnable animFinish) { 438 if (!mIsExpanded || mExpandedBubble == null) { 439 throw new IllegalStateException("Can't animateExpand without expanded state"); 440 } 441 mAnimationHelper.animateConvert(mExpandedBubble, startT, startBounds, startScale, snapshot, 442 taskLeash, animFinish); 443 } 444 445 /** 446 * Populates {@param out} with the rest bounds of an expanded bubble. 447 */ getExpandedViewRestBounds(Rect out)448 public void getExpandedViewRestBounds(Rect out) { 449 mAnimationHelper.getExpandedViewRestBounds(out); 450 } 451 452 /** Removes the given {@code bubble}. */ removeBubble(Bubble bubble, Runnable endAction)453 public void removeBubble(Bubble bubble, Runnable endAction) { 454 final boolean inTransition = bubble.getPreparingTransition() != null; 455 Runnable cleanUp = () -> { 456 // The transition is already managing the task/wm state. 457 bubble.cleanupViews(!inTransition); 458 endAction.run(); 459 }; 460 if (mBubbleData.getBubbles().isEmpty() || inTransition) { 461 // If we are removing the last bubble or removing the current bubble via transition, 462 // collapse the expanded view and clean up bubbles at the end. 463 collapse(cleanUp); 464 } else { 465 cleanUp.run(); 466 } 467 } 468 469 /** Collapses any showing expanded view */ collapse()470 public void collapse() { 471 collapse(/* endAction= */ null); 472 } 473 474 /** 475 * Collapses any showing expanded view. 476 * 477 * @param endAction an action to run and the end of the collapse animation. 478 */ collapse(@ullable Runnable endAction)479 public void collapse(@Nullable Runnable endAction) { 480 if (!mIsExpanded) { 481 if (endAction != null) { 482 endAction.run(); 483 } 484 return; 485 } 486 mIsExpanded = false; 487 final BubbleBarExpandedView viewToRemove = mExpandedView; 488 mEducationViewController.hideEducation(/* animated = */ true); 489 Runnable runnable = () -> { 490 removeView(viewToRemove); 491 if (endAction != null) { 492 endAction.run(); 493 } 494 if (mBubbleData.getBubbles().isEmpty()) { 495 mBubbleController.onAllBubblesAnimatedOut(); 496 } 497 }; 498 if (mDragController != null && mDragController.isStuckToDismiss()) { 499 mAnimationHelper.animateDismiss(runnable); 500 } else { 501 mAnimationHelper.animateCollapse(runnable); 502 } 503 mBubbleController.getSysuiProxy().onStackExpandChanged(false); 504 mExpandedView = null; 505 mDragController = null; 506 setTouchDelegate(null); 507 showScrim(false); 508 } 509 510 /** 511 * Show bubble bar user education relative to the reference position. 512 * @param position the reference position in Screen coordinates. 513 */ showUserEducation(Point position)514 public void showUserEducation(Point position) { 515 mEducationViewController.showStackEducation(position, /* root = */ this, () -> { 516 // When the user education is clicked hide it and expand the selected bubble 517 mEducationViewController.hideEducation(/* animated = */ true, () -> { 518 mBubbleController.expandStackWithSelectedBubble(); 519 return Unit.INSTANCE; 520 }); 521 return Unit.INSTANCE; 522 }); 523 } 524 525 /** Sets the function to call to un-bubble the given conversation. */ setUnBubbleConversationCallback( @ullable Consumer<String> unBubbleConversationCallback)526 public void setUnBubbleConversationCallback( 527 @Nullable Consumer<String> unBubbleConversationCallback) { 528 mUnBubbleConversationCallback = unBubbleConversationCallback; 529 } 530 setUpDismissView()531 private void setUpDismissView() { 532 if (mDismissView != null) { 533 removeView(mDismissView); 534 } 535 mDismissView = new DismissView(getContext()); 536 DismissViewUtils.setup(mDismissView); 537 addView(mDismissView); 538 } 539 540 /** Hides the current modal education/menu view, IME or collapses the expanded view */ hideModalOrCollapse()541 private void hideModalOrCollapse() { 542 if (mEducationViewController.isEducationVisible()) { 543 mEducationViewController.hideEducation(/* animated = */ true); 544 return; 545 } 546 if (isExpanded() && mExpandedView != null) { 547 boolean menuHidden = mExpandedView.hideMenuIfVisible(); 548 if (menuHidden) { 549 return; 550 } 551 boolean imeHidden = mExpandedView.hideImeIfVisible(); 552 if (imeHidden) { 553 return; 554 } 555 } 556 mBubbleController.collapseStack(); 557 } 558 559 /** Updates the expanded view size and position. */ updateExpandedView()560 public void updateExpandedView() { 561 if (mExpandedView == null || mExpandedBubble == null || mExpandedView.isAnimating()) return; 562 boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY); 563 mPositioner.getBubbleBarExpandedViewBounds(mPositioner.isBubbleBarOnLeft(), 564 isOverflowExpanded, mTempRect); 565 FrameLayout.LayoutParams lp = (LayoutParams) mExpandedView.getLayoutParams(); 566 lp.width = mTempRect.width(); 567 lp.height = mTempRect.height(); 568 mExpandedView.setLayoutParams(lp); 569 mExpandedView.setX(mTempRect.left); 570 mExpandedView.setY(mTempRect.top); 571 mExpandedView.updateLocation(); 572 } 573 showScrim(boolean show)574 private void showScrim(boolean show) { 575 if (show) { 576 mScrimView.animate() 577 .setInterpolator(ALPHA_IN) 578 .alpha(BUBBLE_EXPANDED_SCRIM_ALPHA) 579 .start(); 580 } else { 581 mScrimView.animate() 582 .alpha(0f) 583 .setInterpolator(ALPHA_OUT) 584 .start(); 585 } 586 } 587 588 /** 589 * Fills in the touchable region for expanded view. This is used by window manager to 590 * decide which touch events go to the expanded view. 591 */ getTouchableRegion(Region outRegion)592 private void getTouchableRegion(Region outRegion) { 593 mTempRect.setEmpty(); 594 if (mIsExpanded || mEducationViewController.isEducationVisible()) { 595 getBoundsOnScreen(mTempRect); 596 outRegion.op(mTempRect, Region.Op.UNION); 597 } 598 } 599 600 /** Handles IME position changes. */ onImeTopChanged(int imeTop)601 public void onImeTopChanged(int imeTop) { 602 if (mIsExpanded) { 603 mAnimationHelper.onImeTopChanged(imeTop); 604 } 605 } 606 607 /** 608 * Log the event only if {@link #mExpandedBubble} is a {@link Bubble}. 609 * <p> 610 * Skips logging if it is {@link BubbleOverflow}. 611 */ logBubbleEvent(BubbleLogger.Event event)612 private void logBubbleEvent(BubbleLogger.Event event) { 613 if (mExpandedBubble != null && mExpandedBubble instanceof Bubble) { 614 mBubbleLogger.log((Bubble) mExpandedBubble, event); 615 } 616 } 617 618 @Nullable 619 @VisibleForTesting getDragController()620 public BubbleBarExpandedViewDragController getDragController() { 621 return mDragController; 622 } 623 624 private class LocationChangeListener implements 625 BaseBubblePinController.LocationChangeListener { 626 627 private BubbleBarLocation mInitialLocation; 628 629 @Override onStart(@onNull BubbleBarLocation location)630 public void onStart(@NonNull BubbleBarLocation location) { 631 mInitialLocation = location; 632 } 633 634 @Override onChange(@onNull BubbleBarLocation bubbleBarLocation)635 public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) { 636 mBubbleController.animateBubbleBarLocation(bubbleBarLocation); 637 } 638 639 @Override onRelease(@onNull BubbleBarLocation location)640 public void onRelease(@NonNull BubbleBarLocation location) { 641 mBubbleController.setBubbleBarLocation(location, 642 BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW); 643 if (location != mInitialLocation) { 644 BubbleLogger.Event event = location.isOnLeft(isLayoutRtl()) 645 ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW 646 : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW; 647 logBubbleEvent(event); 648 } 649 } 650 } 651 } 652