• 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 package com.android.launcher3.taskbar.bubbles;
17 
18 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
19 
20 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
21 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
22 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
23 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
24 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
25 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
26 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
27 
28 import android.annotation.BinderThread;
29 import android.annotation.Nullable;
30 import android.content.Context;
31 import android.graphics.Point;
32 import android.os.Bundle;
33 import android.os.SystemProperties;
34 import android.util.ArrayMap;
35 import android.util.Log;
36 
37 import androidx.annotation.NonNull;
38 
39 import com.android.launcher3.taskbar.TaskbarSharedState;
40 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
41 import com.android.launcher3.util.Executors.SimpleThreadFactory;
42 import com.android.quickstep.SystemUiProxy;
43 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
44 import com.android.wm.shell.Flags;
45 import com.android.wm.shell.bubbles.IBubblesListener;
46 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
47 import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
48 import com.android.wm.shell.shared.bubbles.BubbleInfo;
49 import com.android.wm.shell.shared.bubbles.RemovedBubble;
50 
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.List;
54 import java.util.Objects;
55 import java.util.Optional;
56 import java.util.concurrent.Executor;
57 import java.util.concurrent.Executors;
58 
59 /**
60  * This registers a listener with SysUIProxy to get information about changes to the bubble
61  * stack state from WMShell (SysUI). The controller is also responsible for loading the necessary
62  * information to render each of the bubbles & dispatches changes to
63  * {@link BubbleBarViewController} which will then update {@link BubbleBarView} as needed.
64  *
65  * <p>For details around the behavior of the bubble bar, see {@link BubbleBarView}.
66  */
67 public class BubbleBarController extends IBubblesListener.Stub {
68 
69     private static final String TAG = "BubbleBarController";
70     private static final boolean DEBUG = false;
71 
72     /**
73      * Determines whether bubbles can be shown in the bubble bar. This value updates when the
74      * taskbar is recreated.
75      *
76      * @see #onTaskbarRecreated()
77      */
78     private static boolean sBubbleBarEnabled = Flags.enableBubbleBar()
79             || SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
80 
81     /** Whether showing bubbles in the launcher bubble bar is enabled. */
isBubbleBarEnabled()82     public static boolean isBubbleBarEnabled() {
83         return sBubbleBarEnabled;
84     }
85 
86     /** Re-reads the value of the flag from SystemProperties when taskbar is recreated. */
onTaskbarRecreated()87     public static void onTaskbarRecreated() {
88         sBubbleBarEnabled = Flags.enableBubbleBar()
89                 || SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
90     }
91 
92     private static final long MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING
93             | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
94             | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
95             | SYSUI_STATE_IME_VISIBLE
96             | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
97             | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
98 
99     private static final long MASK_HIDE_HANDLE_VIEW = SYSUI_STATE_BOUNCER_SHOWING
100             | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
101             | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
102 
103     private static final long MASK_SYSUI_LOCKED = SYSUI_STATE_BOUNCER_SHOWING
104             | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
105             | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
106 
107     private final Context mContext;
108     private final BubbleBarView mBarView;
109     private final ArrayMap<String, BubbleBarBubble> mBubbles = new ArrayMap<>();
110     private final ArrayMap<String, BubbleBarBubble> mSuppressedBubbles = new ArrayMap<>();
111 
112     private static final Executor BUBBLE_STATE_EXECUTOR = Executors.newSingleThreadExecutor(
113             new SimpleThreadFactory("BubbleStateUpdates-", THREAD_PRIORITY_BACKGROUND));
114     private final SystemUiProxy mSystemUiProxy;
115 
116     private BubbleBarItem mSelectedBubble;
117 
118     private TaskbarSharedState mSharedState;
119     private BubbleBarViewController mBubbleBarViewController;
120     private BubbleStashController mBubbleStashController;
121     private Optional<BubbleStashedHandleViewController> mBubbleStashedHandleViewController;
122     private BubblePinController mBubblePinController;
123     private BubbleCreator mBubbleCreator;
124     private BubbleBarLocationListener mBubbleBarLocationListener;
125 
126     // Cache last sent top coordinate to avoid sending duplicate updates to shell
127     private int mLastSentBubbleBarTop;
128 
129     private boolean mIsImeVisible = false;
130 
131     /**
132      * Similar to {@link BubbleBarUpdate} but rather than {@link BubbleInfo}s it uses
133      * {@link BubbleBarBubble}s so that it can be used to update the views.
134      */
135     private static class BubbleBarViewUpdate {
136         final boolean initialState;
137         boolean expandedChanged;
138         boolean expanded;
139         boolean shouldShowEducation;
140         String selectedBubbleKey;
141         String suppressedBubbleKey;
142         String unsuppressedBubbleKey;
143         BubbleBarLocation bubbleBarLocation;
144         List<RemovedBubble> removedBubbles;
145         List<String> bubbleKeysInOrder;
146         Point expandedViewDropTargetSize;
147         boolean showOverflow;
148         boolean showOverflowChanged;
149 
150         // These need to be loaded in the background
151         BubbleBarBubble addedBubble;
152         BubbleBarBubble updatedBubble;
153         List<BubbleBarBubble> currentBubbles;
154 
BubbleBarViewUpdate(BubbleBarUpdate update)155         BubbleBarViewUpdate(BubbleBarUpdate update) {
156             initialState = update.initialState;
157             expandedChanged = update.expandedChanged;
158             expanded = update.expanded;
159             shouldShowEducation = update.shouldShowEducation;
160             selectedBubbleKey = update.selectedBubbleKey;
161             suppressedBubbleKey = update.suppressedBubbleKey;
162             unsuppressedBubbleKey = update.unsupressedBubbleKey;
163             bubbleBarLocation = update.bubbleBarLocation;
164             removedBubbles = update.removedBubbles;
165             bubbleKeysInOrder = update.bubbleKeysInOrder;
166             expandedViewDropTargetSize = update.expandedViewDropTargetSize;
167             showOverflow = update.showOverflow;
168             showOverflowChanged = update.showOverflowChanged;
169         }
170     }
171 
BubbleBarController(Context context, BubbleBarView bubbleView)172     public BubbleBarController(Context context, BubbleBarView bubbleView) {
173         mContext = context;
174         mBarView = bubbleView; // Need the view for inflating bubble views.
175 
176         mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
177     }
178 
onDestroy()179     public void onDestroy() {
180         mSystemUiProxy.setBubblesListener(null);
181         // Saves bubble bar state
182         BubbleInfo[] bubbleInfoItems = new BubbleInfo[mBubbles.size()];
183         mBubbles.values().forEach(bubbleBarBubble -> {
184             int index = mBubbleBarViewController.bubbleViewIndex(bubbleBarBubble.getView());
185             if (index < 0 || index >= bubbleInfoItems.length) {
186                 Log.e(TAG, "Found improper index: " + index + " for " + bubbleBarBubble);
187             } else {
188                 bubbleInfoItems[index] = bubbleBarBubble.getInfo();
189             }
190         });
191         mSharedState.bubbleInfoItems = Arrays.asList(bubbleInfoItems);
192         mSharedState.suppressedBubbleInfoItems = new ArrayList<>(mSuppressedBubbles.size());
193         for (int i = 0; i < mSuppressedBubbles.size(); i++) {
194             mSharedState.suppressedBubbleInfoItems.add(mSuppressedBubbles.valueAt(i).getInfo());
195         }
196     }
197 
198     /** Initializes controllers. */
init(BubbleControllers bubbleControllers, BubbleBarLocationListener bubbleBarLocationListener, TaskbarSharedState sharedState)199     public void init(BubbleControllers bubbleControllers,
200             BubbleBarLocationListener bubbleBarLocationListener,
201             TaskbarSharedState sharedState) {
202         mSharedState = sharedState;
203         mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
204         mBubbleStashController = bubbleControllers.bubbleStashController;
205         mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
206         mBubblePinController = bubbleControllers.bubblePinController;
207         mBubbleCreator = bubbleControllers.bubbleCreator;
208         mBubbleBarLocationListener = bubbleBarLocationListener;
209 
210         bubbleControllers.runAfterInit(() -> {
211             restoreSavedState(sharedState);
212             mBubbleBarViewController.setHiddenForBubbles(
213                     !sBubbleBarEnabled || mBubbles.isEmpty());
214             mBubbleStashedHandleViewController.ifPresent(
215                     controller -> controller.setHiddenForBubbles(
216                             !sBubbleBarEnabled || mBubbles.isEmpty()));
217             mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
218                     key -> setSelectedBubbleInternal(mBubbles.get(key)));
219             mBubbleBarViewController.setBoundsChangeListener(this::onBubbleBarBoundsChanged);
220             mBubbleBarLocationListener.onBubbleBarLocationUpdated(
221                     mBubbleBarViewController.getBubbleBarLocation());
222             if (sBubbleBarEnabled) {
223                 mSystemUiProxy.setBubblesListener(this);
224             }
225         });
226     }
227 
228     /**
229      * Updates the bubble bar, handle bar, and stash controllers based on sysui state flags.
230      */
updateStateForSysuiFlags(@ystemUiStateFlags long flags)231     public void updateStateForSysuiFlags(@SystemUiStateFlags long flags) {
232         boolean hideBubbleBar = (flags & MASK_HIDE_BUBBLE_BAR) != 0;
233         mBubbleBarViewController.setHiddenForSysui(hideBubbleBar);
234 
235         boolean hideHandleView = (flags & MASK_HIDE_HANDLE_VIEW) != 0;
236         mBubbleStashedHandleViewController.ifPresent(
237                 controller -> controller.setHiddenForSysui(hideHandleView));
238 
239         boolean sysuiLocked = (flags & MASK_SYSUI_LOCKED) != 0;
240         mBubbleStashController.setSysuiLocked(sysuiLocked);
241         mIsImeVisible = (flags & SYSUI_STATE_IME_VISIBLE) != 0;
242         if (mIsImeVisible) {
243             mBubbleBarViewController.onImeVisible();
244         }
245     }
246 
247     //
248     // Bubble data changes
249     //
250 
251     @BinderThread
252     @Override
onBubbleStateChange(Bundle bundle)253     public void onBubbleStateChange(Bundle bundle) {
254         bundle.setClassLoader(BubbleBarUpdate.class.getClassLoader());
255         BubbleBarUpdate update = bundle.getParcelable("update", BubbleBarUpdate.class);
256         BubbleBarViewUpdate viewUpdate = new BubbleBarViewUpdate(update);
257         if (update.addedBubble != null
258                 || update.updatedBubble != null
259                 || !update.currentBubbleList.isEmpty()) {
260             // We have bubbles to load
261             BUBBLE_STATE_EXECUTOR.execute(() -> {
262                 if (update.addedBubble != null) {
263                     viewUpdate.addedBubble = mBubbleCreator.populateBubble(mContext,
264                             update.addedBubble,
265                             mBarView,
266                             null /* existingBubble */);
267                 }
268                 if (update.updatedBubble != null) {
269                     BubbleBarBubble existingBubble = mBubbles.get(update.updatedBubble.getKey());
270                     viewUpdate.updatedBubble =
271                             mBubbleCreator.populateBubble(mContext, update.updatedBubble,
272                                     mBarView,
273                                     existingBubble);
274                 }
275                 if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) {
276                     List<BubbleBarBubble> currentBubbles = new ArrayList<>();
277                     for (int i = 0; i < update.currentBubbleList.size(); i++) {
278                         BubbleBarBubble b = mBubbleCreator.populateBubble(mContext,
279                                 update.currentBubbleList.get(i), mBarView,
280                                 null /* existingBubble */);
281                         currentBubbles.add(b);
282                     }
283                     viewUpdate.currentBubbles = currentBubbles;
284                 }
285                 MAIN_EXECUTOR.execute(() -> applyViewChanges(viewUpdate));
286             });
287         } else {
288             // No bubbles to load, immediately apply the changes.
289             BUBBLE_STATE_EXECUTOR.execute(
290                     () -> MAIN_EXECUTOR.execute(() -> applyViewChanges(viewUpdate)));
291         }
292     }
293 
restoreSavedState(TaskbarSharedState sharedState)294     private void restoreSavedState(TaskbarSharedState sharedState) {
295         if (sharedState.bubbleBarLocation != null) {
296             updateBubbleBarLocationInternal(sharedState.bubbleBarLocation);
297         }
298         restoreSavedBubbles(sharedState.bubbleInfoItems);
299         restoreSuppressed(sharedState.suppressedBubbleInfoItems);
300     }
301 
restoreSavedBubbles(List<BubbleInfo> bubbleInfos)302     private void restoreSavedBubbles(List<BubbleInfo> bubbleInfos) {
303         if (bubbleInfos == null || bubbleInfos.isEmpty()) return;
304         // Iterate in reverse because new bubbles are added in front and the list is in order.
305         for (int i = bubbleInfos.size() - 1; i >= 0; i--) {
306             BubbleBarBubble bubble = mBubbleCreator.populateBubble(mContext,
307                     bubbleInfos.get(i), mBarView, /* existingBubble = */ null);
308             if (bubble == null) {
309                 Log.e(TAG, "Could not instantiate BubbleBarBubble for " + bubbleInfos.get(i));
310                 continue;
311             }
312             addBubbleInternally(bubble, /* isExpanding= */ false, /* suppressAnimation= */ true);
313         }
314     }
315 
restoreSuppressed(List<BubbleInfo> bubbleInfos)316     private void restoreSuppressed(List<BubbleInfo> bubbleInfos) {
317         if (bubbleInfos == null || bubbleInfos.isEmpty()) return;
318         for (BubbleInfo bubbleInfo : bubbleInfos.reversed()) {
319             BubbleBarBubble bb = mBubbleCreator.populateBubble(mContext, bubbleInfo,
320                     mBarView, /* existingBubble= */
321                     null);
322             if (bb != null) {
323                 mSuppressedBubbles.put(bb.getKey(), bb);
324             }
325         }
326     }
327 
applyViewChanges(BubbleBarViewUpdate update)328     private void applyViewChanges(BubbleBarViewUpdate update) {
329         final boolean isCollapsed = (update.expandedChanged && !update.expanded)
330                 || (!update.expandedChanged && !mBubbleBarViewController.isExpanded());
331         final boolean isExpanding = update.expandedChanged && update.expanded;
332         // don't animate bubbles if this is the initial state because we may be unfolding or
333         // enabling gesture nav. also suppress animation if the bubble bar is hidden for sysui e.g.
334         // the shade is open, or we're locked.
335         final boolean suppressAnimation =
336                 update.initialState || mBubbleBarViewController.isHiddenForSysui() || mIsImeVisible;
337 
338         if (update.initialState && mSharedState.hasSavedBubbles()) {
339             // clear restored state
340             mBubbleBarViewController.removeAllBubbles();
341             mBubbles.clear();
342             mBubbleBarViewController.showOverflow(update.showOverflow);
343         }
344 
345         if (update.addedBubble != null) {
346             mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
347         }
348         BubbleBarBubble bubbleToSelect = null;
349         if (update.selectedBubbleKey != null) {
350             if (mSelectedBubble == null
351                     || !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
352                 BubbleBarBubble newlySelected = mBubbles.get(update.selectedBubbleKey);
353                 if (newlySelected != null) {
354                     bubbleToSelect = newlySelected;
355                 } else {
356                     Log.w(TAG, "trying to select bubble that doesn't exist:"
357                             + update.selectedBubbleKey);
358                 }
359             }
360         }
361         if (Flags.enableOptionalBubbleOverflow()
362                 && update.showOverflowChanged && !update.showOverflow && update.addedBubble != null
363                 && update.removedBubbles.isEmpty()
364                 && !mBubbles.isEmpty()) {
365             // A bubble was added from the overflow (& now it's empty / not showing)
366             mBubbleBarViewController.removeOverflowAndAddBubble(update.addedBubble, bubbleToSelect);
367         } else if (update.addedBubble != null && update.removedBubbles.size() == 1) {
368             // we're adding and removing a bubble at the same time. handle this as a single update.
369             RemovedBubble removedBubble = update.removedBubbles.get(0);
370             BubbleBarBubble bubbleToRemove = mBubbles.remove(removedBubble.getKey());
371             boolean showOverflow = update.showOverflowChanged && update.showOverflow;
372             if (bubbleToRemove != null) {
373                 mBubbleBarViewController.addBubbleAndRemoveBubble(update.addedBubble,
374                         bubbleToRemove, bubbleToSelect, isExpanding, suppressAnimation,
375                         showOverflow);
376             } else {
377                 mBubbleBarViewController.addBubble(update.addedBubble, isExpanding,
378                         suppressAnimation, bubbleToSelect);
379                 Log.w(TAG, "trying to remove bubble that doesn't exist: " + removedBubble.getKey());
380             }
381         } else {
382             boolean overflowNeedsToBeAdded = Flags.enableOptionalBubbleOverflow()
383                     && update.showOverflowChanged && update.showOverflow;
384             if (!update.removedBubbles.isEmpty()) {
385                 for (int i = 0; i < update.removedBubbles.size(); i++) {
386                     RemovedBubble removedBubble = update.removedBubbles.get(i);
387                     BubbleBarBubble bubble = mBubbles.remove(removedBubble.getKey());
388                     if (bubble != null && overflowNeedsToBeAdded) {
389                         // First removal, show the overflow
390                         overflowNeedsToBeAdded = false;
391                         mBubbleBarViewController.addOverflowAndRemoveBubble(bubble, bubbleToSelect);
392                     } else if (bubble != null) {
393                         mBubbleBarViewController.removeBubble(bubble);
394                     } else {
395                         Log.w(TAG, "trying to remove bubble that doesn't exist: "
396                                 + removedBubble.getKey());
397                     }
398                 }
399             }
400             if (update.addedBubble != null) {
401                 mBubbleBarViewController.addBubble(update.addedBubble, isExpanding,
402                         suppressAnimation, bubbleToSelect);
403             }
404             if (Flags.enableOptionalBubbleOverflow()
405                     && update.showOverflowChanged
406                     && update.showOverflow != mBubbleBarViewController.isOverflowAdded()) {
407                 mBubbleBarViewController.showOverflow(update.showOverflow);
408             }
409         }
410 
411         // if a bubble was updated upstream, but removed before the update was received, add it back
412         if (update.updatedBubble != null && !mBubbles.containsKey(update.updatedBubble.getKey())) {
413             addBubbleInternally(update.updatedBubble, isExpanding, suppressAnimation);
414         }
415 
416         if (update.addedBubble != null && isCollapsed && bubbleToSelect == null) {
417             // If we're collapsed, the most recently added bubble will be selected.
418             bubbleToSelect = update.addedBubble;
419         }
420 
421         if (update.currentBubbles != null && !update.currentBubbles.isEmpty()) {
422             // Iterate in reverse because new bubbles are added in front and the list is in order.
423             for (int i = update.currentBubbles.size() - 1; i >= 0; i--) {
424                 BubbleBarBubble bubble = update.currentBubbles.get(i);
425                 if (bubble != null) {
426                     addBubbleInternally(bubble, isExpanding, suppressAnimation);
427                     if (isCollapsed && bubbleToSelect == null) {
428                         // If we're collapsed, the most recently added bubble will be selected.
429                         bubbleToSelect = bubble;
430                     }
431                 } else {
432                     Log.w(TAG, "trying to add bubble but null after loading! "
433                             + update.addedBubble.getKey());
434                 }
435             }
436         }
437         if (Flags.enableOptionalBubbleOverflow() && update.initialState && update.showOverflow) {
438             mBubbleBarViewController.showOverflow(true);
439         }
440 
441         if (update.suppressedBubbleKey != null) {
442             BubbleBarBubble bb = mBubbles.remove(update.suppressedBubbleKey);
443             if (bb != null) {
444                 mSuppressedBubbles.put(update.suppressedBubbleKey, bb);
445                 mBubbleBarViewController.removeBubble(bb);
446             }
447         }
448         if (update.unsuppressedBubbleKey != null) {
449             BubbleBarBubble bb = mSuppressedBubbles.remove(update.unsuppressedBubbleKey);
450             if (bb != null) {
451                 // Unsuppressing an existing bubble should not cause the bar to expand or animate
452                 addBubbleInternally(bb, /* isExpanding= */ false, /* suppressAnimation= */ true);
453                 if (mBubbleBarViewController.isHiddenForNoBubbles()) {
454                     mBubbleBarViewController.setHiddenForBubbles(false);
455                 }
456             }
457         }
458 
459         // Update the visibility if this is the initial state, if there are no bubbles, or if the
460         // animation is suppressed.
461         // If this is the initial bubble, the bubble bar will become visible as part of the
462         // animation.
463         if (update.initialState || mBubbles.isEmpty() || suppressAnimation) {
464             mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty());
465         }
466         mBubbleStashedHandleViewController.ifPresent(
467                 controller -> controller.setHiddenForBubbles(mBubbles.isEmpty()));
468 
469         if (mBubbles.isEmpty()) {
470             // all bubbles were removed. clear the selected bubble
471             mSelectedBubble = null;
472         }
473 
474         if (update.updatedBubble != null) {
475             // Updates mean the dot state may have changed; any other changes were updated in
476             // the populateBubble step.
477             BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
478             if (suppressAnimation) {
479                 // since we're not animating this update, we should update the dot visibility here.
480                 bb.getView().updateDotVisibility(/* animate= */ false);
481             } else {
482                 mBubbleBarViewController.animateBubbleNotification(
483                         bb, /* isExpanding= */ false, /* isUpdate= */ true);
484             }
485         }
486         if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
487             // Create the new list
488             List<BubbleBarBubble> newOrder = update.bubbleKeysInOrder.stream()
489                     .map(mBubbles::get).filter(Objects::nonNull).toList();
490             if (!newOrder.isEmpty()) {
491                 mBubbleBarViewController.reorderBubbles(newOrder);
492             }
493         }
494         if (bubbleToSelect != null) {
495             setSelectedBubbleInternal(bubbleToSelect);
496         }
497         if (update.shouldShowEducation) {
498             mBubbleBarViewController.prepareToShowEducation();
499         }
500         if (update.expandedChanged) {
501             if (update.expanded != mBubbleBarViewController.isExpanded()) {
502                 mBubbleBarViewController.setExpandedFromSysui(update.expanded);
503             } else {
504                 Log.w(TAG, "expansion was changed but is the same");
505             }
506         }
507         if (update.bubbleBarLocation != null) {
508             mSharedState.bubbleBarLocation = update.bubbleBarLocation;
509             if (update.bubbleBarLocation != mBubbleBarViewController.getBubbleBarLocation()) {
510                 updateBubbleBarLocationInternal(update.bubbleBarLocation);
511             }
512         }
513         if (update.expandedViewDropTargetSize != null) {
514             mBubblePinController.setDropTargetSize(update.expandedViewDropTargetSize);
515         }
516     }
517 
518     /**
519      * Removes the given bubble from the backing list of bubbles after it was dismissed by the user.
520      */
onBubbleDismissed(BubbleView bubble)521     public void onBubbleDismissed(BubbleView bubble) {
522         mBubbles.remove(bubble.getBubble().getKey());
523     }
524 
525     /** Tells WMShell to show the currently selected bubble. */
showSelectedBubble()526     public void showSelectedBubble() {
527         if (getSelectedBubbleKey() != null) {
528             mLastSentBubbleBarTop = mBarView.getRestingTopPositionOnScreen();
529             mSystemUiProxy.showBubble(getSelectedBubbleKey(), mLastSentBubbleBarTop);
530         } else {
531             Log.w(TAG, "Trying to show the selected bubble but it's null");
532         }
533     }
534 
535     /** Updates the currently selected bubble for launcher views and tells WMShell to show it. */
showAndSelectBubble(BubbleBarItem b)536     public void showAndSelectBubble(BubbleBarItem b) {
537         if (DEBUG) Log.w(TAG, "showingSelectedBubble: " + b.getKey());
538         setSelectedBubbleInternal(b);
539         showSelectedBubble();
540     }
541 
542     /**
543      * Sets the bubble that should be selected. This notifies the views, it does not notify
544      * WMShell that the selection has changed, that should go through either
545      * {@link #showSelectedBubble()} or {@link #showAndSelectBubble(BubbleBarItem)}.
546      */
setSelectedBubbleInternal(BubbleBarItem b)547     private void setSelectedBubbleInternal(BubbleBarItem b) {
548         if (!Objects.equals(b, mSelectedBubble)) {
549             if (DEBUG) Log.w(TAG, "selectingBubble: " + b.getKey());
550             mSelectedBubble = b;
551             mBubbleBarViewController.updateSelectedBubble(mSelectedBubble);
552         }
553     }
554 
555     /**
556      * Returns the selected bubble or null if no bubble is selected.
557      */
558     @Nullable
getSelectedBubbleKey()559     public String getSelectedBubbleKey() {
560         if (mSelectedBubble != null) {
561             return mSelectedBubble.getKey();
562         }
563         return null;
564     }
565 
566     /**
567      * Set a new bubble bar location.
568      * <p>
569      * Updates the value locally in Launcher and in WMShell.
570      */
updateBubbleBarLocation(BubbleBarLocation location, @BubbleBarLocation.UpdateSource int source)571     public void updateBubbleBarLocation(BubbleBarLocation location,
572             @BubbleBarLocation.UpdateSource int source) {
573         updateBubbleBarLocationInternal(location);
574         mSystemUiProxy.setBubbleBarLocation(location, source);
575     }
576 
updateBubbleBarLocationInternal(BubbleBarLocation location)577     private void updateBubbleBarLocationInternal(BubbleBarLocation location) {
578         mBubbleBarViewController.setBubbleBarLocation(location);
579         mBubbleStashController.setBubbleBarLocation(location);
580         mBubbleBarLocationListener.onBubbleBarLocationUpdated(location);
581     }
582 
583     @Override
animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation)584     public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
585         MAIN_EXECUTOR.execute(
586                 () -> {
587                     mBubbleBarViewController.animateBubbleBarLocation(bubbleBarLocation);
588                     mBubbleBarLocationListener.onBubbleBarLocationAnimated(bubbleBarLocation);
589                 });
590     }
591 
592     @Override
onDragItemOverBubbleBarDragZone(@onNull BubbleBarLocation bubbleBarLocation)593     public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation bubbleBarLocation) {
594         MAIN_EXECUTOR.execute(() -> {
595             mBubbleBarViewController.onDragItemOverBubbleBarDragZone(bubbleBarLocation);
596             if (mBubbleBarViewController.isLocationUpdatedForDropTarget()) {
597                 mBubbleBarLocationListener.onBubbleBarLocationAnimated(bubbleBarLocation);
598             }
599         });
600     }
601 
602     @Override
onItemDraggedOutsideBubbleBarDropZone()603     public void onItemDraggedOutsideBubbleBarDropZone() {
604         MAIN_EXECUTOR.execute(() -> {
605             if (mBubbleBarViewController.isLocationUpdatedForDropTarget()) {
606                 BubbleBarLocation original = mBubbleBarViewController.getBubbleBarLocation();
607                 mBubbleBarLocationListener.onBubbleBarLocationAnimated(original);
608             }
609             mBubbleBarViewController.onItemDraggedOutsideBubbleBarDropZone();
610         });
611     }
612 
613     /** Notifies WMShell to show the expanded view. */
showExpandedView()614     void showExpandedView() {
615         mSystemUiProxy.showExpandedView();
616     }
617 
618     //
619     // Loading data for the bubbles
620     //
621 
onBubbleBarBoundsChanged()622     private void onBubbleBarBoundsChanged() {
623         int newTop = mBarView.getRestingTopPositionOnScreen();
624         if (newTop != mLastSentBubbleBarTop) {
625             mLastSentBubbleBarTop = newTop;
626             mSystemUiProxy.updateBubbleBarTopOnScreen(newTop);
627         }
628     }
629 
addBubbleInternally(BubbleBarBubble bubble, boolean isExpanding, boolean suppressAnimation)630     private void addBubbleInternally(BubbleBarBubble bubble, boolean isExpanding,
631             boolean suppressAnimation) {
632         mBubbles.put(bubble.getKey(), bubble);
633         mBubbleBarViewController.addBubble(bubble, isExpanding,
634                 suppressAnimation, /* bubbleToSelect = */ null);
635     }
636 
637     /** Listener of {@link BubbleBarLocation} updates. */
638     public interface BubbleBarLocationListener {
639 
640         /** Called when {@link BubbleBarLocation} is animated, but change is not yet final. */
onBubbleBarLocationAnimated(BubbleBarLocation location)641         void onBubbleBarLocationAnimated(BubbleBarLocation location);
642 
643         /** Called when {@link BubbleBarLocation} is updated permanently. */
onBubbleBarLocationUpdated(BubbleBarLocation location)644         void onBubbleBarLocationUpdated(BubbleBarLocation location);
645     }
646 }
647