• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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