• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.bubbles;
18 
19 import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
20 import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
21 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
22 import static android.view.View.INVISIBLE;
23 import static android.view.View.VISIBLE;
24 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
25 import static android.view.WindowManager.TRANSIT_CHANGE;
26 
27 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
28 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
29 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED;
30 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_GROUP_CANCELLED;
31 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_INVALID_INTENT;
32 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NOTIF_CANCEL;
33 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_BUBBLE_UP;
34 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_LONGER_BUBBLE;
35 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
36 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
37 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
38 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
39 
40 import android.annotation.BinderThread;
41 import android.annotation.NonNull;
42 import android.annotation.UserIdInt;
43 import android.app.ActivityManager;
44 import android.app.ActivityOptions;
45 import android.app.Notification;
46 import android.app.NotificationChannel;
47 import android.app.PendingIntent;
48 import android.app.TaskInfo;
49 import android.content.BroadcastReceiver;
50 import android.content.ClipDescription;
51 import android.content.Context;
52 import android.content.Intent;
53 import android.content.IntentFilter;
54 import android.content.pm.LauncherApps;
55 import android.content.pm.PackageManager;
56 import android.content.pm.ShortcutInfo;
57 import android.content.pm.UserInfo;
58 import android.content.res.Configuration;
59 import android.graphics.PixelFormat;
60 import android.graphics.Point;
61 import android.graphics.Rect;
62 import android.graphics.drawable.Icon;
63 import android.os.Binder;
64 import android.os.Bundle;
65 import android.os.Handler;
66 import android.os.RemoteException;
67 import android.os.ServiceManager;
68 import android.os.UserHandle;
69 import android.os.UserManager;
70 import android.service.notification.NotificationListenerService;
71 import android.service.notification.NotificationListenerService.RankingMap;
72 import android.util.Log;
73 import android.util.Pair;
74 import android.util.SparseArray;
75 import android.view.IWindowManager;
76 import android.view.SurfaceControl;
77 import android.view.View;
78 import android.view.ViewGroup;
79 import android.view.ViewRootImpl;
80 import android.view.WindowInsets;
81 import android.view.WindowManager;
82 import android.window.ScreenCapture;
83 import android.window.ScreenCapture.SynchronousScreenCaptureListener;
84 import android.window.TransitionInfo;
85 import android.window.WindowContainerToken;
86 import android.window.WindowContainerTransaction;
87 
88 import androidx.annotation.MainThread;
89 import androidx.annotation.Nullable;
90 
91 import com.android.internal.annotations.VisibleForTesting;
92 import com.android.internal.protolog.ProtoLog;
93 import com.android.internal.statusbar.IStatusBarService;
94 import com.android.internal.util.CollectionUtils;
95 import com.android.launcher3.icons.BubbleIconFactory;
96 import com.android.wm.shell.Flags;
97 import com.android.wm.shell.R;
98 import com.android.wm.shell.ShellTaskOrganizer;
99 import com.android.wm.shell.bubbles.bar.BubbleBarDragListener;
100 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
101 import com.android.wm.shell.bubbles.shortcut.BubbleShortcutHelper;
102 import com.android.wm.shell.common.DisplayController;
103 import com.android.wm.shell.common.DisplayImeController;
104 import com.android.wm.shell.common.DisplayInsetsController;
105 import com.android.wm.shell.common.ExternalInterfaceBinder;
106 import com.android.wm.shell.common.FloatingContentCoordinator;
107 import com.android.wm.shell.common.ImeListener;
108 import com.android.wm.shell.common.RemoteCallable;
109 import com.android.wm.shell.common.ShellExecutor;
110 import com.android.wm.shell.common.SingleInstanceRemoteListener;
111 import com.android.wm.shell.common.SyncTransactionQueue;
112 import com.android.wm.shell.common.TaskStackListenerCallback;
113 import com.android.wm.shell.common.TaskStackListenerImpl;
114 import com.android.wm.shell.draganddrop.DragAndDropController;
115 import com.android.wm.shell.onehanded.OneHandedController;
116 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
117 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
118 import com.android.wm.shell.shared.annotations.ShellMainThread;
119 import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
120 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
121 import com.android.wm.shell.shared.bubbles.BubbleBarLocation.UpdateSource;
122 import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
123 import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider;
124 import com.android.wm.shell.shared.bubbles.ContextUtils;
125 import com.android.wm.shell.shared.bubbles.DeviceConfig;
126 import com.android.wm.shell.shared.draganddrop.DragAndDropConstants;
127 import com.android.wm.shell.sysui.ConfigurationChangeListener;
128 import com.android.wm.shell.sysui.ShellCommandHandler;
129 import com.android.wm.shell.sysui.ShellController;
130 import com.android.wm.shell.sysui.ShellInit;
131 import com.android.wm.shell.taskview.TaskView;
132 import com.android.wm.shell.taskview.TaskViewController;
133 import com.android.wm.shell.taskview.TaskViewRepository;
134 import com.android.wm.shell.taskview.TaskViewTaskController;
135 import com.android.wm.shell.taskview.TaskViewTransitions;
136 import com.android.wm.shell.transition.Transitions;
137 
138 import java.io.PrintWriter;
139 import java.util.ArrayList;
140 import java.util.HashMap;
141 import java.util.HashSet;
142 import java.util.List;
143 import java.util.Locale;
144 import java.util.Map;
145 import java.util.Objects;
146 import java.util.Optional;
147 import java.util.Set;
148 import java.util.concurrent.Executor;
149 import java.util.function.Consumer;
150 import java.util.function.IntConsumer;
151 
152 /**
153  * Bubbles are a special type of content that can "float" on top of other apps or System UI.
154  * Bubbles can be expanded to show more content.
155  *
156  * The controller manages addition, removal, and visible state of bubbles on screen.
157  */
158 public class BubbleController implements ConfigurationChangeListener,
159         RemoteCallable<BubbleController>, Bubbles.SysuiProxy.Provider,
160         BubbleBarDragListener, BubbleTaskUnfoldTransitionMerger {
161 
162     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
163 
164     // Should match with PhoneWindowManager
165     private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
166     private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
167     private static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
168     private static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
169 
170     /**
171      * Common interface to send updates to bubble views.
172      */
173     public interface BubbleViewCallback {
174         /** Called when the provided bubble should be removed. */
removeBubble(Bubble removedBubble)175         void removeBubble(Bubble removedBubble);
176         /** Called when the provided bubble should be added. */
addBubble(Bubble addedBubble)177         void addBubble(Bubble addedBubble);
178         /** Called when the provided bubble should be updated. */
updateBubble(Bubble updatedBubble)179         void updateBubble(Bubble updatedBubble);
180         /** Called when the provided bubble should be selected. */
selectionChanged(BubbleViewProvider selectedBubble)181         void selectionChanged(BubbleViewProvider selectedBubble);
182         /** Called when the provided bubble's suppression state has changed. */
suppressionChanged(Bubble bubble, boolean isSuppressed)183         void suppressionChanged(Bubble bubble, boolean isSuppressed);
184         /** Called when the expansion state of bubbles has changed. */
expansionChanged(boolean isExpanded)185         void expansionChanged(boolean isExpanded);
186         /**
187          * Called when the order of the bubble list has changed. Depending on the expanded state
188          * the pointer might need to be updated.
189          */
bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer)190         void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer);
191         /** Called when the bubble overflow empty state changes, used to show/hide the overflow. */
bubbleOverflowChanged(boolean hasBubbles)192         void bubbleOverflowChanged(boolean hasBubbles);
193     }
194 
195     private final Context mContext;
196     private final BubblesImpl mImpl = new BubblesImpl();
197     private Bubbles.BubbleExpandListener mExpandListener;
198     @Nullable private final BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
199     private final FloatingContentCoordinator mFloatingContentCoordinator;
200     private final BubbleDataRepository mDataRepository;
201     private final DisplayInsetsController mDisplayInsetsController;
202     private final DisplayImeController mDisplayImeController;
203     private final UserManager mUserManager;
204     private final LauncherApps mLauncherApps;
205     private final IStatusBarService mBarService;
206     private final WindowManager mWindowManager;
207     private final TaskStackListenerImpl mTaskStackListener;
208     private final ShellTaskOrganizer mTaskOrganizer;
209     private final DisplayController mDisplayController;
210     private final TaskViewController mTaskViewController;
211     private final Transitions mTransitions;
212     private final SyncTransactionQueue mSyncQueue;
213     private final ShellController mShellController;
214     private final ShellCommandHandler mShellCommandHandler;
215     private final IWindowManager mWmService;
216     private final BubbleTaskViewFactory mBubbleTaskViewFactory;
217     private final BubbleExpandedViewManager mExpandedViewManager;
218     private final ResizabilityChecker mResizabilityChecker;
219 
220     // Used to post to main UI thread
221     private final ShellExecutor mMainExecutor;
222     private final Handler mMainHandler;
223     private final ShellExecutor mBackgroundExecutor;
224 
225     private final BubbleLogger mLogger;
226     private final BubbleData mBubbleData;
227     @Nullable private BubbleStackView mStackView;
228     @Nullable private BubbleBarLayerView mLayerView;
229     private BubbleIconFactory mBubbleIconFactory;
230     private final BubblePositioner mBubblePositioner;
231     private Bubbles.SysuiProxy mSysuiProxy;
232 
233     @Nullable private Runnable mOnImeHidden;
234 
235     // Tracks the id of the current (foreground) user.
236     private int mCurrentUserId;
237     // Current profiles of the user (e.g. user with a workprofile)
238     private SparseArray<UserInfo> mCurrentProfiles;
239     // Saves data about active bubbles when users are switched.
240     private final SparseArray<UserBubbleData> mSavedUserBubbleData;
241 
242     // Used when ranking updates occur and we check if things should bubble / unbubble
243     private NotificationListenerService.Ranking mTmpRanking;
244 
245     // Callback that updates BubbleOverflowActivity on data change.
246     @Nullable private BubbleData.Listener mOverflowListener = null;
247 
248     // Typically only load once & after user switches
249     private boolean mOverflowDataLoadNeeded = true;
250 
251     /**
252      * When the shade status changes to SHADE (from anything but SHADE, like LOCKED) we'll select
253      * this bubble and expand the stack.
254      */
255     @Nullable private BubbleEntry mNotifEntryToExpandOnShadeUnlock;
256 
257     /** LayoutParams used to add the BubbleStackView to the window manager. */
258     private WindowManager.LayoutParams mWmLayoutParams;
259     /** Whether or not the BubbleStackView has been added to the WindowManager. */
260     private boolean mAddedToWindowManager = false;
261 
262     /**
263      * Saved screen density, used to detect display size changes in {@link #onConfigurationChanged}.
264      */
265     private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
266 
267     /**
268      * Saved screen bounds, used to detect screen size changes in {@link #onConfigurationChanged}.
269      */
270     private final Rect mScreenBounds = new Rect();
271 
272     /** Saved font scale, used to detect font size changes in {@link #onConfigurationChanged}. */
273     private float mFontScale = 0;
274 
275     /** Saved locale, used to detect local changes in {@link #onConfigurationChanged}. */
276     private Locale mLocale = null;
277 
278     /** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */
279     private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
280 
281     /** Saved insets, used to detect WindowInset changes. */
282     private WindowInsets mWindowInsets;
283 
284     private boolean mInflateSynchronously;
285 
286     /** True when user is in status bar unlock shade. */
287     private boolean mIsStatusBarShade = true;
288 
289     /** One handed mode controller to register transition listener. */
290     private final Optional<OneHandedController> mOneHandedOptional;
291     /** Drag and drop controller to register listener for onDragStarted. */
292     private final DragAndDropController mDragAndDropController;
293     /** Used to send bubble events to launcher. */
294     private Bubbles.BubbleStateListener mBubbleStateListener;
295     /**
296      * Used to track previous navigation mode to detect switch to buttons navigation. Set to
297      * true to switch the bubble bar to the opposite side for 3 nav buttons mode on device boot.
298      */
299     private boolean mIsPrevNavModeGestures = true;
300     /** Used to send updates to the views from {@link #mBubbleDataListener}. */
301     private BubbleViewCallback mBubbleViewCallback;
302 
303     private final BubbleTransitions mBubbleTransitions;
304 
BubbleController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, ShellController shellController, BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, FloatingContentCoordinator floatingContentCoordinator, BubbleDataRepository dataRepository, @Nullable IStatusBarService statusBarService, WindowManager windowManager, DisplayInsetsController displayInsetsController, DisplayImeController displayImeController, UserManager userManager, LauncherApps launcherApps, BubbleLogger bubbleLogger, TaskStackListenerImpl taskStackListener, ShellTaskOrganizer organizer, BubblePositioner positioner, DisplayController displayController, Optional<OneHandedController> oneHandedOptional, DragAndDropController dragAndDropController, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler, @ShellBackgroundThread ShellExecutor bgExecutor, TaskViewRepository taskViewRepository, TaskViewTransitions taskViewTransitions, Transitions transitions, SyncTransactionQueue syncQueue, IWindowManager wmService, ResizabilityChecker resizabilityChecker)305     public BubbleController(Context context,
306             ShellInit shellInit,
307             ShellCommandHandler shellCommandHandler,
308             ShellController shellController,
309             BubbleData data,
310             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
311             FloatingContentCoordinator floatingContentCoordinator,
312             BubbleDataRepository dataRepository,
313             @Nullable IStatusBarService statusBarService,
314             WindowManager windowManager,
315             DisplayInsetsController displayInsetsController,
316             DisplayImeController displayImeController,
317             UserManager userManager,
318             LauncherApps launcherApps,
319             BubbleLogger bubbleLogger,
320             TaskStackListenerImpl taskStackListener,
321             ShellTaskOrganizer organizer,
322             BubblePositioner positioner,
323             DisplayController displayController,
324             Optional<OneHandedController> oneHandedOptional,
325             DragAndDropController dragAndDropController,
326             @ShellMainThread ShellExecutor mainExecutor,
327             @ShellMainThread Handler mainHandler,
328             @ShellBackgroundThread ShellExecutor bgExecutor,
329             TaskViewRepository taskViewRepository,
330             TaskViewTransitions taskViewTransitions,
331             Transitions transitions,
332             SyncTransactionQueue syncQueue,
333             IWindowManager wmService,
334             ResizabilityChecker resizabilityChecker) {
335         mContext = context;
336         mShellCommandHandler = shellCommandHandler;
337         mShellController = shellController;
338         mLauncherApps = launcherApps;
339         mBarService = statusBarService == null
340                 ? IStatusBarService.Stub.asInterface(
341                 ServiceManager.getService(Context.STATUS_BAR_SERVICE))
342                 : statusBarService;
343         mWindowManager = windowManager;
344         mDisplayInsetsController = displayInsetsController;
345         mDisplayImeController = displayImeController;
346         mUserManager = userManager;
347         mFloatingContentCoordinator = floatingContentCoordinator;
348         mDataRepository = dataRepository;
349         mLogger = bubbleLogger;
350         mMainExecutor = mainExecutor;
351         mMainHandler = mainHandler;
352         mBackgroundExecutor = bgExecutor;
353         mTaskStackListener = taskStackListener;
354         mTaskOrganizer = organizer;
355         mSurfaceSynchronizer = synchronizer;
356         mCurrentUserId = ActivityManager.getCurrentUser();
357         mBubblePositioner = positioner;
358         mBubbleData = data;
359         mSavedUserBubbleData = new SparseArray<>();
360         mBubbleIconFactory = new BubbleIconFactory(context,
361                 context.getResources().getDimensionPixelSize(R.dimen.bubble_size),
362                 context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
363                 context.getResources().getColor(
364                         com.android.launcher3.icons.R.color.important_conversation),
365                 context.getResources().getDimensionPixelSize(
366                         com.android.internal.R.dimen.importance_ring_stroke_width));
367         mDisplayController = displayController;
368         final TaskViewTransitions tvTransitions;
369         if (TaskViewTransitions.useRepo()) {
370             tvTransitions = new TaskViewTransitions(transitions, taskViewRepository, organizer,
371                     syncQueue);
372         } else {
373             tvTransitions = taskViewTransitions;
374         }
375         mTaskViewController = new BubbleTaskViewController(tvTransitions);
376         mBubbleTransitions = new BubbleTransitions(transitions, organizer, taskViewRepository, data,
377                 tvTransitions, context);
378         mTransitions = transitions;
379         mOneHandedOptional = oneHandedOptional;
380         mDragAndDropController = dragAndDropController;
381         mSyncQueue = syncQueue;
382         mWmService = wmService;
383         shellInit.addInitCallback(this::onInit, this);
384         mBubbleTaskViewFactory = new BubbleTaskViewFactory() {
385             @Override
386             public BubbleTaskView create() {
387                 TaskViewTaskController taskViewTaskController = new TaskViewTaskController(
388                         context, organizer, mTaskViewController, syncQueue);
389                 TaskView taskView = new TaskView(context, mTaskViewController,
390                         taskViewTaskController);
391                 return new BubbleTaskView(taskView, mainExecutor);
392             }
393         };
394         mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this);
395         mResizabilityChecker = resizabilityChecker;
396     }
397 
registerOneHandedState(OneHandedController oneHanded)398     private void registerOneHandedState(OneHandedController oneHanded) {
399         oneHanded.registerTransitionCallback(
400                 new OneHandedTransitionCallback() {
401                     @Override
402                     public void onStartFinished(Rect bounds) {
403                         mMainExecutor.execute(() -> {
404                             if (mStackView != null) {
405                                 mStackView.onVerticalOffsetChanged(bounds.top);
406                             }
407                         });
408                     }
409 
410                     @Override
411                     public void onStopFinished(Rect bounds) {
412                         mMainExecutor.execute(() -> {
413                             if (mStackView != null) {
414                                 mStackView.onVerticalOffsetChanged(bounds.top);
415                             }
416                         });
417                     }
418                 });
419     }
420 
onInit()421     protected void onInit() {
422         mBubbleViewCallback = isShowingAsBubbleBar()
423                 ? mBubbleBarViewCallback
424                 : mBubbleStackViewCallback;
425         mBubbleData.setListener(mBubbleDataListener);
426         mBubbleData.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged);
427         mDataRepository.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged);
428         mBubbleData.setPendingIntentCancelledListener(bubble -> {
429             if (bubble.getPendingIntent() == null) {
430                 return;
431             }
432             if (bubble.isPendingIntentActive()
433                     || mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
434                 bubble.setPendingIntentCanceled();
435                 return;
436             }
437             mMainExecutor.execute(() -> removeBubble(bubble.getKey(), DISMISS_INVALID_INTENT));
438         });
439 
440         BubblesImeListener bubblesImeListener =
441                 new BubblesImeListener(mDisplayController, mContext.getDisplayId());
442         // the insets controller is notified whenever the IME visibility changes whether the IME is
443         // requested by a bubbled task or non-bubbled task. in the latter case, we need to update
444         // the position of the stack to avoid overlapping with the IME.
445         mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
446                 bubblesImeListener);
447         // the ime controller is notified when the IME is requested only by a bubbled task.
448         mDisplayImeController.addPositionProcessor(bubblesImeListener);
449 
450         mBubbleData.setCurrentUserId(mCurrentUserId);
451 
452         mTaskOrganizer.addLocusIdListener((taskId, locus, visible) ->
453                 mBubbleData.onLocusVisibilityChanged(taskId, locus, visible));
454 
455         mLauncherApps.registerCallback(new LauncherApps.Callback() {
456             @Override
457             public void onPackageAdded(String s, UserHandle userHandle) {}
458 
459             @Override
460             public void onPackageChanged(String s, UserHandle userHandle) {}
461 
462             @Override
463             public void onPackageRemoved(String s, UserHandle userHandle) {
464                 // Remove bubbles with this package name, since it has been uninstalled and attempts
465                 // to open a bubble from an uninstalled app can cause issues.
466                 mBubbleData.removeBubblesWithPackageName(s, DISMISS_PACKAGE_REMOVED);
467             }
468 
469             @Override
470             public void onPackagesAvailable(String[] strings, UserHandle userHandle, boolean b) {}
471 
472             @Override
473             public void onPackagesUnavailable(String[] packages, UserHandle userHandle,
474                     boolean b) {
475                 for (String packageName : packages) {
476                     // Remove bubbles from unavailable apps. This can occur when the app is on
477                     // external storage that has been removed.
478                     mBubbleData.removeBubblesWithPackageName(packageName, DISMISS_PACKAGE_REMOVED);
479                 }
480             }
481 
482             @Override
483             public void onShortcutsChanged(String packageName, List<ShortcutInfo> validShortcuts,
484                     UserHandle user) {
485                 super.onShortcutsChanged(packageName, validShortcuts, user);
486 
487                 // Remove bubbles whose shortcuts aren't in the latest list of valid shortcuts.
488                 mBubbleData.removeBubblesWithInvalidShortcuts(
489                         packageName, validShortcuts, DISMISS_SHORTCUT_REMOVED);
490             }
491         }, mMainHandler);
492 
493         mTransitions.registerObserver(new BubblesTransitionObserver(this, mBubbleData));
494 
495         mTaskStackListener.addListener(new TaskStackListenerCallback() {
496             @Override
497             public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
498                     boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
499                 final int taskId = task.taskId;
500                 Bubble bubble = mBubbleData.getBubbleInStackWithTaskId(taskId);
501                 if (bubble != null) {
502                     ProtoLog.d(WM_SHELL_BUBBLES,
503                             "onActivityRestartAttempt - taskId=%d selecting matching bubble=%s",
504                             taskId, bubble.getKey());
505                     mBubbleData.setSelectedBubbleAndExpandStack(bubble);
506                     return;
507                 }
508 
509                 bubble = mBubbleData.getOverflowBubbleWithTaskId(taskId);
510                 if (bubble != null) {
511                     ProtoLog.d(WM_SHELL_BUBBLES, "onActivityRestartAttempt - taskId=%d "
512                                     + "selecting matching overflow bubble=%s",
513                             taskId, bubble.getKey());
514                     promoteBubbleFromOverflow(bubble);
515                     mBubbleData.setExpanded(true);
516                 }
517             }
518         });
519 
520         mDisplayController.addDisplayChangingController(
521                 (displayId, fromRotation, toRotation, newDisplayAreaInfo, t) -> {
522                     Rect newScreenBounds = new Rect();
523                     if (newDisplayAreaInfo != null) {
524                         newScreenBounds =
525                                 newDisplayAreaInfo.configuration.windowConfiguration.getBounds();
526                     }
527                     // This is triggered right before the rotation or new screen size is applied
528                     if (fromRotation != toRotation || !newScreenBounds.equals(mScreenBounds)) {
529                         if (mStackView != null) {
530                             // Layout listener set on stackView will update the positioner
531                             // once the rotation or screen change is applied
532                             mStackView.onOrientationChanged();
533                         }
534                     }
535                 });
536 
537         mOneHandedOptional.ifPresent(this::registerOneHandedState);
538         mDragAndDropController.addListener(new DragAndDropController.DragAndDropListener() {
539             @Override
540             public void onDragStarted() {
541                 collapseStack();
542             }
543         });
544 
545         // Clear out any persisted bubbles on disk that no longer have a valid user.
546         List<UserInfo> users = mUserManager.getAliveUsers();
547         mDataRepository.sanitizeBubbles(users);
548 
549         // Init profiles
550         SparseArray<UserInfo> userProfiles = new SparseArray<>();
551         for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
552             userProfiles.put(user.id, user);
553         }
554         mCurrentProfiles = userProfiles;
555 
556         if (Flags.enableRetrievableBubbles()) {
557             registerShortcutBroadcastReceiver();
558         }
559 
560         mShellController.addConfigurationChangeListener(this);
561         mShellController.addExternalInterface(IBubbles.DESCRIPTOR,
562                 this::createExternalInterface, this);
563         mShellCommandHandler.addDumpCallback(this::dump, this);
564     }
565 
createExternalInterface()566     private ExternalInterfaceBinder createExternalInterface() {
567         return new IBubblesImpl(this);
568     }
569 
570     @VisibleForTesting
asBubbles()571     public Bubbles asBubbles() {
572         return mImpl;
573     }
574 
575     @VisibleForTesting
getImplCachedState()576     public BubblesImpl.CachedState getImplCachedState() {
577         return mImpl.mCachedState;
578     }
579 
getMainExecutor()580     public ShellExecutor getMainExecutor() {
581         return mMainExecutor;
582     }
583 
584     @Override
getContext()585     public Context getContext() {
586         return mContext;
587     }
588 
589     @Override
getRemoteCallExecutor()590     public ShellExecutor getRemoteCallExecutor() {
591         return mMainExecutor;
592     }
593 
594     /**
595      * Sets a listener to be notified of bubble updates. This is used by launcher so that
596      * it may render bubbles in itself. Only one listener is supported.
597      *
598      * <p>If bubble bar is supported, bubble views will be updated to switch to bar mode.
599      */
registerBubbleStateListener(Bubbles.BubbleStateListener listener)600     public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) {
601         final boolean bubbleBarAllowed = Flags.enableBubbleBar()
602                 && (mBubblePositioner.isLargeScreen() || Flags.enableBubbleBarOnPhones())
603                 && listener != null;
604         if (bubbleBarAllowed) {
605             // Only set the listener if we can show the bubble bar.
606             mBubbleStateListener = listener;
607             setUpBubbleViewsForMode();
608             sendInitialListenerUpdate();
609         } else {
610             mBubbleStateListener = null;
611         }
612     }
613 
614     /**
615      * Unregisters the {@link Bubbles.BubbleStateListener}.
616      *
617      * <p>If there's an existing listener, then we're switching back to stack mode and bubble views
618      * will be updated accordingly.
619      */
unregisterBubbleStateListener()620     public void unregisterBubbleStateListener() {
621         if (mBubbleStateListener != null) {
622             mBubbleStateListener = null;
623             setUpBubbleViewsForMode();
624         }
625     }
626 
627     /**
628      * If a {@link Bubbles.BubbleStateListener} is present, this will send the current bubble
629      * state to it.
630      */
sendInitialListenerUpdate()631     private void sendInitialListenerUpdate() {
632         if (mBubbleStateListener != null) {
633             boolean isCurrentNavModeGestures = ContextUtils.isGestureNavigationMode(mContext);
634             if (mIsPrevNavModeGestures && !isCurrentNavModeGestures) {
635                 BubbleBarLocation bubbleBarLocation = ContextUtils.isRtl(mContext)
636                         ? BubbleBarLocation.RIGHT : BubbleBarLocation.LEFT;
637                 mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
638             }
639             mIsPrevNavModeGestures = isCurrentNavModeGestures;
640             BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
641             mBubbleStateListener.onBubbleStateChange(update);
642         }
643     }
644 
645     /**
646      * Hides the current input method, wherever it may be focused, via InputMethodManagerInternal.
647      */
hideCurrentInputMethod(@ullable Runnable onImeHidden)648     void hideCurrentInputMethod(@Nullable Runnable onImeHidden) {
649         mOnImeHidden = onImeHidden;
650         mBubblePositioner.setImeVisible(false /* visible */, 0 /* height */);
651         int displayId = mWindowManager.getDefaultDisplay().getDisplayId();
652         // if the device is locked we can't use the status bar service to hide the IME because
653         // the IME state is frozen and it will lead to internal IME state going out of sync. This
654         // will make the IME visible when the device is unlocked. Instead we use
655         // DisplayImeController directly to make sure the state is correct when the device unlocks.
656         if (isDeviceLocked()) {
657             mDisplayImeController.hideImeForBubblesWhenLocked(displayId);
658             return;
659         }
660         try {
661             mBarService.hideCurrentInputMethodForBubbles(displayId);
662         } catch (RemoteException e) {
663             Log.e(TAG, "Failed to hide IME", e);
664         }
665     }
666 
667     /**
668      * Called when the status bar has become visible or invisible (either permanently or
669      * temporarily).
670      */
onStatusBarVisibilityChanged(boolean visible)671     private void onStatusBarVisibilityChanged(boolean visible) {
672         if (mStackView != null) {
673             // Hide the stack temporarily if the status bar has been made invisible, and the stack
674             // is collapsed. An expanded stack should remain visible until collapsed.
675             mStackView.setTemporarilyInvisible(!visible && !isStackExpanded());
676             ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarVisibilityChanged=%b stackExpanded=%b",
677                     visible, isStackExpanded());
678         }
679     }
680 
onZenStateChanged()681     private void onZenStateChanged() {
682         if (hasBubbles()) {
683             ProtoLog.d(WM_SHELL_BUBBLES, "onZenStateChanged");
684         }
685         for (Bubble b : mBubbleData.getBubbles()) {
686             b.setShowDot(b.showInShade());
687         }
688     }
689 
690     @VisibleForTesting
onStatusBarStateChanged(boolean isShade)691     public void onStatusBarStateChanged(boolean isShade) {
692         boolean didChange = mIsStatusBarShade != isShade;
693         ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarStateChanged "
694                         + "isShade=%b didChange=%b mNotifEntryToExpandOnShadeUnlock=%s",
695                 isShade, didChange, (mNotifEntryToExpandOnShadeUnlock != null
696                         ? mNotifEntryToExpandOnShadeUnlock.getKey() : "null"));
697         mIsStatusBarShade = isShade;
698         if (!mIsStatusBarShade && didChange) {
699             if (mBubbleData.isExpanded()) {
700                 // If the IME is visible, hide it first and then collapse.
701                 if (mBubblePositioner.isImeVisible()) {
702                     hideCurrentInputMethod(this::collapseStack);
703                 } else {
704                     collapseStack();
705                 }
706             } else if (mOnImeHidden != null) {
707                 // a request to collapse started before we're notified that the device is locking.
708                 // we're currently waiting for the IME to collapse, before mOnImeHidden can be
709                 // executed, which may not happen since the screen may already be off. hide the IME
710                 // immediately now that we're locked and pass the same runnable so it can complete.
711                 hideCurrentInputMethod(mOnImeHidden);
712             }
713         }
714 
715         if (mNotifEntryToExpandOnShadeUnlock != null) {
716             expandStackAndSelectBubble(mNotifEntryToExpandOnShadeUnlock);
717         }
718 
719         updateBubbleViews();
720     }
721 
722     @VisibleForTesting
onBubbleMetadataFlagChanged(Bubble bubble)723     public void onBubbleMetadataFlagChanged(Bubble bubble) {
724         ProtoLog.d(WM_SHELL_BUBBLES, "onBubbleMetadataFlagChanged=%s flags=%d",
725                 bubble.getKey(), bubble.getFlags());
726         // Make sure NoMan knows suppression state so that anyone querying it can tell.
727         try {
728             mBarService.onBubbleMetadataFlagChanged(bubble.getKey(), bubble.getFlags());
729         } catch (RemoteException e) {
730             // Bad things have happened
731         }
732         mImpl.mCachedState.updateBubbleSuppressedState(bubble);
733     }
734 
735     /** Called when the current user changes. */
736     @VisibleForTesting
onUserChanged(int newUserId)737     public void onUserChanged(int newUserId) {
738         ProtoLog.d(WM_SHELL_BUBBLES, "onUserChanged currentUser=%d newUser=%d",
739                 mCurrentUserId, newUserId);
740         saveBubbles(mCurrentUserId);
741         mCurrentUserId = newUserId;
742 
743         mBubbleData.dismissAll(DISMISS_USER_CHANGED);
744         mBubbleData.clearOverflow();
745         mOverflowDataLoadNeeded = true;
746 
747         restoreBubbles(newUserId);
748         mBubbleData.setCurrentUserId(newUserId);
749     }
750 
751     /** Called when the profiles for the current user change. **/
onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles)752     public void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) {
753         mCurrentProfiles = currentProfiles;
754     }
755 
756     /** Called when a user is removed from the device, including work profiles. */
onUserRemoved(int removedUserId)757     public void onUserRemoved(int removedUserId) {
758         UserInfo parent = mUserManager.getProfileParent(removedUserId);
759         int parentUserId = parent != null ? parent.getUserHandle().getIdentifier() : -1;
760         mBubbleData.removeBubblesForUser(removedUserId);
761         // Typically calls from BubbleData would remove bubbles from the DataRepository as well,
762         // however, this gets complicated when users are removed (mCurrentUserId won't necessarily
763         // be correct for this) so we update the repo directly.
764         mDataRepository.removeBubblesForUser(removedUserId, parentUserId);
765     }
766 
767     /** Called when sensitive notification state has changed */
onSensitiveNotificationProtectionStateChanged( boolean sensitiveNotificationProtectionActive)768     public void onSensitiveNotificationProtectionStateChanged(
769             boolean sensitiveNotificationProtectionActive) {
770         if (mStackView != null) {
771             mStackView.onSensitiveNotificationProtectionStateChanged(
772                     sensitiveNotificationProtectionActive);
773             ProtoLog.d(WM_SHELL_BUBBLES, "onSensitiveNotificationProtectionStateChanged=%b",
774                     sensitiveNotificationProtectionActive);
775         }
776     }
777 
778     /** Whether bubbles would be shown with the bubble bar UI. */
isShowingAsBubbleBar()779     public boolean isShowingAsBubbleBar() {
780         return Flags.enableBubbleBar()
781                 && (mBubblePositioner.isLargeScreen() || Flags.enableBubbleBarOnPhones())
782                 && mBubbleStateListener != null;
783     }
784 
785     /**
786      * Returns current {@link BubbleBarLocation} if bubble bar is being used.
787      * Otherwise returns <code>null</code>
788      */
789     @Nullable
getBubbleBarLocation()790     public BubbleBarLocation getBubbleBarLocation() {
791         if (isShowingAsBubbleBar()) {
792             return mBubblePositioner.getBubbleBarLocation();
793         }
794         return null;
795     }
796 
797     /**
798      * Update bubble bar location and trigger and update to listeners
799      */
setBubbleBarLocation(BubbleBarLocation bubbleBarLocation, @UpdateSource int source)800     public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation,
801             @UpdateSource int source) {
802         if (isShowingAsBubbleBar()) {
803             updateExpandedViewForBubbleBarLocation(bubbleBarLocation, source);
804             BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
805             bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation;
806             mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
807         }
808     }
809 
updateExpandedViewForBubbleBarLocation(BubbleBarLocation bubbleBarLocation, @UpdateSource int source)810     private void updateExpandedViewForBubbleBarLocation(BubbleBarLocation bubbleBarLocation,
811             @UpdateSource int source) {
812         if (isShowingAsBubbleBar()) {
813             BubbleBarLocation previousLocation = mBubblePositioner.getBubbleBarLocation();
814             mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
815             if (mLayerView != null && !mLayerView.isExpandedViewDragged()) {
816                 mLayerView.updateExpandedView();
817             }
818             logBubbleBarLocationIfChanged(bubbleBarLocation, previousLocation, source);
819         }
820     }
821 
logBubbleBarLocationIfChanged(BubbleBarLocation location, BubbleBarLocation previous, @UpdateSource int source)822     private void logBubbleBarLocationIfChanged(BubbleBarLocation location,
823             BubbleBarLocation previous,
824             @UpdateSource int source) {
825         if (mLayerView == null) {
826             return;
827         }
828         boolean isRtl = mLayerView.isLayoutRtl();
829         boolean wasLeft = previous.isOnLeft(isRtl);
830         boolean onLeft = location.isOnLeft(isRtl);
831         if (wasLeft == onLeft) {
832             // No changes, skip logging
833             return;
834         }
835         switch (source) {
836             case UpdateSource.DRAG_BAR:
837             case UpdateSource.A11Y_ACTION_BAR:
838                 mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR
839                         : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR);
840                 break;
841             case UpdateSource.DRAG_BUBBLE:
842             case UpdateSource.A11Y_ACTION_BUBBLE:
843                 mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE
844                         : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE);
845                 break;
846             case UpdateSource.DRAG_EXP_VIEW:
847             case UpdateSource.A11Y_ACTION_EXP_VIEW:
848                 // TODO(b/349845968): move logging from BubbleBarLayerView to here
849                 break;
850             case UpdateSource.APP_ICON_DRAG:
851                 mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_APP_ICON_DROP
852                         : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_APP_ICON_DROP);
853                 break;
854             case UpdateSource.DRAG_TASK:
855                 mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_TASK
856                         : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_TASK);
857                 break;
858         }
859     }
860 
861     /**
862      * Animate bubble bar to the given location. The location change is transient. It does not
863      * update the state of the bubble bar.
864      * To update bubble bar pinned location, use
865      * {@link #setBubbleBarLocation(BubbleBarLocation, int)}.
866      */
animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation)867     public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
868         if (isShowingAsBubbleBar()) {
869             mBubbleStateListener.animateBubbleBarLocation(bubbleBarLocation);
870         }
871     }
872 
873     @Override
onDragItemOverBubbleBarDragZone(@ullable BubbleBarLocation bubbleBarLocation)874     public void onDragItemOverBubbleBarDragZone(@Nullable BubbleBarLocation bubbleBarLocation) {
875         if (bubbleBarLocation == null) return;
876         if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
877             mBubbleStateListener.onDragItemOverBubbleBarDragZone(bubbleBarLocation);
878             showBubbleBarExpandedViewDropTarget(bubbleBarLocation);
879         }
880     }
881 
882     @Override
onItemDraggedOutsideBubbleBarDropZone()883     public void onItemDraggedOutsideBubbleBarDropZone() {
884         if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
885             mBubbleStateListener.onItemDraggedOutsideBubbleBarDropZone();
886             hideBubbleBarExpandedViewDropTarget();
887         }
888     }
889 
890     @Override
onItemDroppedOverBubbleBarDragZone(@onNull BubbleBarLocation location, Intent itemIntent)891     public void onItemDroppedOverBubbleBarDragZone(@NonNull BubbleBarLocation location,
892             Intent itemIntent) {
893         hideBubbleBarExpandedViewDropTarget();
894         ShortcutInfo shortcutInfo = (ShortcutInfo) itemIntent
895                 .getExtra(DragAndDropConstants.EXTRA_SHORTCUT_INFO);
896         if (shortcutInfo != null) {
897             expandStackAndSelectBubble(shortcutInfo, location);
898             return;
899         }
900         UserHandle user = (UserHandle) itemIntent.getExtra(Intent.EXTRA_USER);
901         PendingIntent pendingIntent = (PendingIntent) itemIntent
902                 .getExtra(ClipDescription.EXTRA_PENDING_INTENT);
903         if (pendingIntent != null && user != null) {
904             expandStackAndSelectBubble(pendingIntent, user, location);
905         }
906     }
907 
908     @Override
getBubbleBarDropZones(int l, int t, int r, int b)909     public Map<BubbleBarLocation, Rect> getBubbleBarDropZones(int l, int t, int r, int b) {
910         Map<BubbleBarLocation, Rect> result = new HashMap<>();
911         if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
912             // TODO(b/393172431) : Utilise DragZoneFactory once it is ready
913             final int bubbleBarDropZoneSideSize = getContext().getResources().getDimensionPixelSize(
914                     R.dimen.bubble_bar_drop_zone_side_size);
915             int top = b - bubbleBarDropZoneSideSize;
916             result.put(BubbleBarLocation.LEFT,
917                     new Rect(l, top, l + bubbleBarDropZoneSideSize, b));
918             result.put(BubbleBarLocation.RIGHT,
919                     new Rect(r - bubbleBarDropZoneSideSize, top, r, b));
920         }
921         return result;
922     }
923 
showBubbleBarExpandedViewDropTarget(BubbleBarLocation bubbleBarLocation)924     private void showBubbleBarExpandedViewDropTarget(BubbleBarLocation bubbleBarLocation) {
925         ensureBubbleViewsAndWindowCreated();
926         if (mLayerView != null) {
927             mLayerView.showBubbleBarExtendedViewDropTarget(bubbleBarLocation);
928         }
929     }
930 
hideBubbleBarExpandedViewDropTarget()931     private void hideBubbleBarExpandedViewDropTarget() {
932         if (mLayerView != null) {
933             mLayerView.hideBubbleBarExpandedViewDropTarget();
934         }
935     }
936 
937     /** Whether this userId belongs to the current user. */
isCurrentProfile(int userId)938     private boolean isCurrentProfile(int userId) {
939         return userId == UserHandle.USER_ALL
940                 || (mCurrentProfiles != null && mCurrentProfiles.get(userId) != null);
941     }
942 
943     /**
944      * Sets whether to perform inflation on the same thread as the caller. This method should only
945      * be used in tests, not in production.
946      */
947     @VisibleForTesting
setInflateSynchronously(boolean inflateSynchronously)948     public void setInflateSynchronously(boolean inflateSynchronously) {
949         mInflateSynchronously = inflateSynchronously;
950     }
951 
952     /** Set a listener to be notified of when overflow view update. */
setOverflowListener(BubbleData.Listener listener)953     public void setOverflowListener(BubbleData.Listener listener) {
954         mOverflowListener = listener;
955     }
956 
957     /**
958      * @return Bubbles for updating overflow.
959      */
getOverflowBubbles()960     List<Bubble> getOverflowBubbles() {
961         return mBubbleData.getOverflowBubbles();
962     }
963 
964     /** The task listener for events in bubble tasks. */
getTaskOrganizer()965     public ShellTaskOrganizer getTaskOrganizer() {
966         return mTaskOrganizer;
967     }
968 
969     /** Contains information to help position things on the screen. */
970     @VisibleForTesting
getPositioner()971     public BubblePositioner getPositioner() {
972         return mBubblePositioner;
973     }
974 
975     /** Provides bounds for drag zone drop targets */
getBubbleDropTargetBoundsProvider()976     public BubbleDropTargetBoundsProvider getBubbleDropTargetBoundsProvider() {
977         return mBubblePositioner;
978     }
979 
getIconFactory()980     BubbleIconFactory getIconFactory() {
981         return mBubbleIconFactory;
982     }
983 
984     @Override
getSysuiProxy()985     public Bubbles.SysuiProxy getSysuiProxy() {
986         return mSysuiProxy;
987     }
988 
989     /**
990      * The view and window for bubbles is lazily created by this method the first time a Bubble
991      * is added. Depending on the device state, this method will:
992      * - initialize a {@link BubbleStackView} and add it to window manager OR
993      * - initialize a {@link com.android.wm.shell.bubbles.bar.BubbleBarLayerView} and adds
994      *   it to window manager.
995      */
ensureBubbleViewsAndWindowCreated()996     private void ensureBubbleViewsAndWindowCreated() {
997         mBubblePositioner.setShowingInBubbleBar(isShowingAsBubbleBar());
998         if (isShowingAsBubbleBar()) {
999             // When we're showing in launcher / bubble bar is enabled, we don't have bubble stack
1000             // view, instead we just show the expanded bubble view as necessary. We still need a
1001             // window to show this in, but we use a separate code path.
1002             // TODO(b/273312602): consider foldables where we do need a stack view when folded
1003             if (mLayerView == null) {
1004                 mLayerView = new BubbleBarLayerView(mContext, this, mBubbleData, mLogger);
1005                 mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
1006             }
1007         } else {
1008             if (mStackView == null) {
1009                 BubbleStackViewManager bubbleStackViewManager =
1010                         BubbleStackViewManager.fromBubbleController(this);
1011                 mStackView = new BubbleStackView(
1012                         mContext, bubbleStackViewManager, mBubblePositioner, mBubbleData,
1013                         mSurfaceSynchronizer, mFloatingContentCoordinator, this, mMainExecutor);
1014                 mStackView.onOrientationChanged();
1015                 if (mExpandListener != null) {
1016                     mStackView.setExpandListener(mExpandListener);
1017                 }
1018                 mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
1019             }
1020         }
1021         addToWindowManagerMaybe();
1022     }
1023 
1024     /** Adds the appropriate view to WindowManager if it's not already there. */
addToWindowManagerMaybe()1025     private void addToWindowManagerMaybe() {
1026         // If already added, don't add it.
1027         if (mAddedToWindowManager) {
1028             return;
1029         }
1030         // If the appropriate view is null, don't add it.
1031         if (isShowingAsBubbleBar() && mLayerView == null) {
1032             return;
1033         } else if (!isShowingAsBubbleBar() && mStackView == null) {
1034             return;
1035         }
1036 
1037         mWmLayoutParams = new WindowManager.LayoutParams(
1038                 // Fill the screen so we can use translation animations to position the bubble
1039                 // views. We'll use touchable regions to ignore touches that are not on the bubbles
1040                 // themselves.
1041                 ViewGroup.LayoutParams.MATCH_PARENT,
1042                 ViewGroup.LayoutParams.MATCH_PARENT,
1043                 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
1044                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1045                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
1046                         | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
1047                 PixelFormat.TRANSLUCENT);
1048 
1049         mWmLayoutParams.setTrustedOverlay();
1050         mWmLayoutParams.setFitInsetsTypes(0);
1051         mWmLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
1052         mWmLayoutParams.token = new Binder();
1053         mWmLayoutParams.setTitle("Bubbles!");
1054         mWmLayoutParams.packageName = mContext.getPackageName();
1055         mWmLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
1056         mWmLayoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
1057 
1058         try {
1059             mAddedToWindowManager = true;
1060             registerBroadcastReceiver();
1061             if (isShowingAsBubbleBar()) {
1062                 mBubbleData.getOverflow().initializeForBubbleBar(
1063                         mExpandedViewManager, mBubblePositioner);
1064             } else {
1065                 mBubbleData.getOverflow().initialize(
1066                         mExpandedViewManager, mStackView, mBubblePositioner);
1067             }
1068             // (TODO: b/273314541) some duplication in the inset listener
1069             if (isShowingAsBubbleBar()) {
1070                 mWindowManager.addView(mLayerView, mWmLayoutParams);
1071                 mLayerView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
1072                     if (!windowInsets.equals(mWindowInsets) && mLayerView != null) {
1073                         mWindowInsets = windowInsets;
1074                         mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager));
1075                         mLayerView.onDisplaySizeChanged();
1076                     }
1077                     return windowInsets;
1078                 });
1079             } else {
1080                 mWindowManager.addView(mStackView, mWmLayoutParams);
1081                 mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
1082                     if (!windowInsets.equals(mWindowInsets) && mStackView != null) {
1083                         mWindowInsets = windowInsets;
1084                         mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager));
1085                         mStackView.onDisplaySizeChanged();
1086                     }
1087                     return windowInsets;
1088                 });
1089             }
1090         } catch (IllegalStateException e) {
1091             // This means the view has already been added. This shouldn't happen...
1092             e.printStackTrace();
1093         }
1094     }
1095 
1096     /**
1097      * In some situations bubble's should be able to receive key events for back:
1098      * - when the bubble overflow is showing
1099      * - when the user education for the stack is showing.
1100      *
1101      * @param interceptBack whether back should be intercepted or not.
1102      */
updateWindowFlagsForBackpress(boolean interceptBack)1103     void updateWindowFlagsForBackpress(boolean interceptBack) {
1104         if (mAddedToWindowManager) {
1105             ProtoLog.d(WM_SHELL_BUBBLES, "updateFlagsForBackPress interceptBack=%b", interceptBack);
1106             mWmLayoutParams.flags = interceptBack
1107                     ? 0
1108                     : WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1109                             | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
1110             mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
1111             if (mStackView != null) {
1112                 mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
1113             } else if (mLayerView != null) {
1114                 mWindowManager.updateViewLayout(mLayerView, mWmLayoutParams);
1115             }
1116         }
1117     }
1118 
1119     /** Removes any bubble views from the WindowManager that exist. */
removeFromWindowManagerMaybe()1120     private void removeFromWindowManagerMaybe() {
1121         if (!mAddedToWindowManager) {
1122             return;
1123         }
1124 
1125         mAddedToWindowManager = false;
1126         // Put on background for this binder call, was causing jank
1127         mBackgroundExecutor.execute(() -> {
1128             try {
1129                 mContext.unregisterReceiver(mBroadcastReceiver);
1130             } catch (IllegalArgumentException e) {
1131                 // Not sure if this happens in production, but was happening in tests
1132                 // (b/253647225)
1133                 e.printStackTrace();
1134             }
1135         });
1136         try {
1137             if (mStackView != null) {
1138                 mWindowManager.removeView(mStackView);
1139                 mBubbleData.getOverflow().cleanUpExpandedState();
1140             }
1141             if (mLayerView != null) {
1142                 mWindowManager.removeView(mLayerView);
1143                 mBubbleData.getOverflow().cleanUpExpandedState();
1144             }
1145         } catch (IllegalArgumentException e) {
1146             // This means the stack has already been removed - it shouldn't happen, but ignore if it
1147             // does, since we wanted it removed anyway.
1148             e.printStackTrace();
1149         }
1150     }
1151 
registerBroadcastReceiver()1152     private void registerBroadcastReceiver() {
1153         IntentFilter filter = new IntentFilter();
1154         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
1155         filter.addAction(Intent.ACTION_SCREEN_OFF);
1156         mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
1157     }
1158 
1159     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
1160         @Override
1161         public void onReceive(Context context, Intent intent) {
1162             if (!isStackExpanded()) return; // Nothing to do
1163 
1164             String action = intent.getAction();
1165             String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
1166             boolean validReasonToCollapse = SYSTEM_DIALOG_REASON_RECENT_APPS.equals(reason)
1167                     || SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason)
1168                     || SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason);
1169             if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) && validReasonToCollapse)
1170                     || Intent.ACTION_SCREEN_OFF.equals(action)) {
1171                 mMainExecutor.execute(() -> collapseStack());
1172             }
1173         }
1174     };
1175 
registerShortcutBroadcastReceiver()1176     private void registerShortcutBroadcastReceiver() {
1177         IntentFilter shortcutFilter = new IntentFilter();
1178         shortcutFilter.addAction(BubbleShortcutHelper.ACTION_SHOW_BUBBLES);
1179         ProtoLog.d(WM_SHELL_BUBBLES, "register broadcast receive for bubbles shortcut");
1180         mContext.registerReceiver(mShortcutBroadcastReceiver, shortcutFilter,
1181                 Context.RECEIVER_NOT_EXPORTED);
1182     }
1183 
1184     private final BroadcastReceiver mShortcutBroadcastReceiver = new BroadcastReceiver() {
1185         @Override
1186         public void onReceive(Context context, Intent intent) {
1187             ProtoLog.v(WM_SHELL_BUBBLES, "receive broadcast to show bubbles %s",
1188                     intent.getAction());
1189             if (BubbleShortcutHelper.ACTION_SHOW_BUBBLES.equals(intent.getAction())) {
1190                 mMainExecutor.execute(() -> showBubblesFromShortcut());
1191             }
1192         }
1193     };
1194 
1195     /**
1196      * Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
1197      * added in the meantime.
1198      */
onAllBubblesAnimatedOut()1199     public void onAllBubblesAnimatedOut() {
1200         if (mStackView != null) {
1201             mStackView.setVisibility(INVISIBLE);
1202             removeFromWindowManagerMaybe();
1203         } else if (mLayerView != null) {
1204             mLayerView.setVisibility(INVISIBLE);
1205             removeFromWindowManagerMaybe();
1206         }
1207     }
1208 
1209     /**
1210      * Records the notification key for any active bubbles. These are used to restore active
1211      * bubbles when the user returns to the foreground.
1212      *
1213      * @param userId the id of the user
1214      */
saveBubbles(@serIdInt int userId)1215     private void saveBubbles(@UserIdInt int userId) {
1216         // First clear any existing keys that might be stored.
1217         mSavedUserBubbleData.remove(userId);
1218         UserBubbleData userBubbleData = new UserBubbleData();
1219         // Add in all active bubbles for the current user.
1220         for (Bubble bubble : mBubbleData.getBubbles()) {
1221             userBubbleData.add(bubble.getKey(), bubble.showInShade());
1222         }
1223         mSavedUserBubbleData.put(userId, userBubbleData);
1224     }
1225 
1226     /**
1227      * Promotes existing notifications to Bubbles if they were previously bubbles.
1228      *
1229      * @param userId the id of the user
1230      */
restoreBubbles(@serIdInt int userId)1231     private void restoreBubbles(@UserIdInt int userId) {
1232         UserBubbleData savedBubbleData = mSavedUserBubbleData.get(userId);
1233         if (savedBubbleData == null) {
1234             // There were no bubbles saved for this used.
1235             return;
1236         }
1237         mSysuiProxy.getShouldRestoredEntries(savedBubbleData.getKeys(), (entries) -> {
1238             mMainExecutor.execute(() -> {
1239                 for (BubbleEntry e : entries) {
1240                     if (canLaunchInTaskView(mContext, e)) {
1241                         boolean showInShade = savedBubbleData.isShownInShade(e.getKey());
1242                         updateBubble(e, true /* suppressFlyout */, showInShade);
1243                     }
1244                 }
1245             });
1246         });
1247         // Finally, remove the entries for this user now that bubbles are restored.
1248         mSavedUserBubbleData.remove(userId);
1249     }
1250 
1251     @Override
onThemeChanged()1252     public void onThemeChanged() {
1253         if (mStackView != null) {
1254             mStackView.onThemeChanged();
1255         }
1256         mBubbleIconFactory = new BubbleIconFactory(mContext,
1257                 mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
1258                 mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
1259                 mContext.getResources().getColor(
1260                         com.android.launcher3.icons.R.color.important_conversation),
1261                 mContext.getResources().getDimensionPixelSize(
1262                         com.android.internal.R.dimen.importance_ring_stroke_width));
1263 
1264         // Reload each bubble
1265         for (Bubble b : mBubbleData.getBubbles()) {
1266             b.inflate(null /* callback */,
1267                     mContext,
1268                     mExpandedViewManager,
1269                     mBubbleTaskViewFactory,
1270                     mBubblePositioner,
1271                     mStackView,
1272                     mLayerView,
1273                     mBubbleIconFactory,
1274                     false /* skipInflation */);
1275         }
1276         for (Bubble b : mBubbleData.getOverflowBubbles()) {
1277             b.inflate(null /* callback */,
1278                     mContext,
1279                     mExpandedViewManager,
1280                     mBubbleTaskViewFactory,
1281                     mBubblePositioner,
1282                     mStackView,
1283                     mLayerView,
1284                     mBubbleIconFactory,
1285                     false /* skipInflation */);
1286         }
1287     }
1288 
1289     @Override
onConfigurationChanged(Configuration newConfig)1290     public void onConfigurationChanged(Configuration newConfig) {
1291         if (mBubblePositioner != null) {
1292             mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager));
1293         }
1294         if (mStackView != null && newConfig != null) {
1295             if (newConfig.densityDpi != mDensityDpi
1296                     || !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) {
1297                 mDensityDpi = newConfig.densityDpi;
1298                 mScreenBounds.set(newConfig.windowConfiguration.getBounds());
1299                 mBubbleData.onMaxBubblesChanged();
1300                 mBubbleIconFactory = new BubbleIconFactory(mContext,
1301                         mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
1302                         mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
1303                         mContext.getResources().getColor(
1304                                 com.android.launcher3.icons.R.color.important_conversation),
1305                         mContext.getResources().getDimensionPixelSize(
1306                                 com.android.internal.R.dimen.importance_ring_stroke_width));
1307                 mStackView.onDisplaySizeChanged();
1308             }
1309             if (newConfig.fontScale != mFontScale) {
1310                 mFontScale = newConfig.fontScale;
1311                 mStackView.updateFontScale();
1312             }
1313             if (newConfig.getLayoutDirection() != mLayoutDirection) {
1314                 mLayoutDirection = newConfig.getLayoutDirection();
1315                 mStackView.onLayoutDirectionChanged(mLayoutDirection);
1316             }
1317             Locale newLocale = newConfig.locale;
1318             if (newLocale != null && !newLocale.equals(mLocale)) {
1319                 mLocale = newLocale;
1320                 mStackView.updateLocale();
1321             }
1322         }
1323     }
1324 
onNotificationPanelExpandedChanged(boolean expanded)1325     private void onNotificationPanelExpandedChanged(boolean expanded) {
1326         if (mStackView != null && mStackView.isExpanded()) {
1327             ProtoLog.d(WM_SHELL_BUBBLES,
1328                     "onNotificationPanelExpandedChanged expanded=%b", expanded);
1329             if (expanded) {
1330                 mStackView.stopMonitoringSwipeUpGesture();
1331             } else {
1332                 mStackView.startMonitoringSwipeUpGesture();
1333             }
1334         }
1335     }
1336 
setSysuiProxy(Bubbles.SysuiProxy proxy)1337     private void setSysuiProxy(Bubbles.SysuiProxy proxy) {
1338         mSysuiProxy = proxy;
1339     }
1340 
1341     @VisibleForTesting
setExpandListener(Bubbles.BubbleExpandListener listener)1342     public void setExpandListener(Bubbles.BubbleExpandListener listener) {
1343         mExpandListener = ((isExpanding, key) -> {
1344             if (listener != null) {
1345                 listener.onBubbleExpandChanged(isExpanding, key);
1346             }
1347         });
1348         if (mStackView != null) {
1349             mStackView.setExpandListener(mExpandListener);
1350         }
1351     }
1352 
1353     /**
1354      * Whether or not there are bubbles present, regardless of them being visible on the
1355      * screen (e.g. if on AOD).
1356      */
1357     @VisibleForTesting
hasBubbles()1358     public boolean hasBubbles() {
1359         if (mStackView == null && mLayerView == null) {
1360             return false;
1361         }
1362         return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow();
1363     }
1364 
isStackExpanded()1365     public boolean isStackExpanded() {
1366         return mBubbleData.isExpanded();
1367     }
1368 
collapseStack()1369     public void collapseStack() {
1370         mBubbleData.setExpanded(false /* expanded */);
1371     }
1372 
1373     /**
1374      * A bubble is being dragged in Launcher.
1375      * Will be called only when bubble bar is expanded.
1376      *
1377      * @param bubbleKey key of the bubble being dragged
1378      */
startBubbleDrag(String bubbleKey)1379     public void startBubbleDrag(String bubbleKey) {
1380         if (mBubbleData.getSelectedBubble() != null) {
1381             collapseExpandedViewForBubbleBar();
1382         }
1383         if (mBubbleStateListener != null) {
1384             boolean overflow = BubbleOverflow.KEY.equals(bubbleKey);
1385             Rect rect = new Rect();
1386             mBubblePositioner.getBubbleBarExpandedViewBounds(mBubblePositioner.isBubbleBarOnLeft(),
1387                     overflow, rect);
1388             BubbleBarUpdate update = new BubbleBarUpdate();
1389             update.expandedViewDropTargetSize = new Point(rect.width(), rect.height());
1390             mBubbleStateListener.onBubbleStateChange(update);
1391         }
1392     }
1393 
1394     /**
1395      * A bubble is no longer being dragged in Launcher. And was released in given location.
1396      * Will be called only when bubble bar is expanded.
1397      *
1398      * @param location location where bubble was released
1399      * @param topOnScreen      top coordinate of the bubble bar on the screen after release
1400      */
stopBubbleDrag(BubbleBarLocation location, int topOnScreen)1401     public void stopBubbleDrag(BubbleBarLocation location, int topOnScreen) {
1402         mBubblePositioner.setBubbleBarLocation(location);
1403         mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen);
1404         if (mBubbleData.getSelectedBubble() != null) {
1405             showExpandedViewForBubbleBar();
1406         }
1407     }
1408 
1409     /**
1410      * A bubble was dragged and is released in dismiss target in Launcher.
1411      *
1412      * @param bubbleKey key of the bubble being dragged to dismiss target
1413      * @param timestamp the timestamp of the removal
1414      */
dragBubbleToDismiss(String bubbleKey, long timestamp)1415     public void dragBubbleToDismiss(String bubbleKey, long timestamp) {
1416         final String selectedBubbleKey = mBubbleData.getSelectedBubbleKey();
1417         final Bubble bubbleToDismiss = mBubbleData.getAnyBubbleWithKey(bubbleKey);
1418         if (bubbleToDismiss != null) {
1419             mBubbleData.dismissBubbleWithKey(
1420                     bubbleKey, Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER, timestamp);
1421             mLogger.log(bubbleToDismiss,
1422                     BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_BUBBLE);
1423         }
1424         if (mBubbleData.hasBubbles()) {
1425             // We still have bubbles, if we dragged an individual bubble to dismiss we were expanded
1426             // so re-expand to whatever is selected.
1427             showExpandedViewForBubbleBar();
1428             if (bubbleKey.equals(selectedBubbleKey)) {
1429                 // We dragged the selected bubble to dismiss, log switch event
1430                 if (mBubbleData.getSelectedBubble() instanceof Bubble) {
1431                     // Log only bubbles as overflow can't be dragged
1432                     mLogger.log((Bubble) mBubbleData.getSelectedBubble(),
1433                             BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
1434                 }
1435             }
1436         }
1437     }
1438 
1439     /**
1440      * Show bubble bar user education relative to the reference position.
1441      * @param position the reference position in Screen coordinates.
1442      */
showUserEducation(Point position)1443     public void showUserEducation(Point position) {
1444         if (mLayerView == null) return;
1445         mLayerView.showUserEducation(position);
1446     }
1447 
1448     @VisibleForTesting
isBubbleNotificationSuppressedFromShade(String key, String groupKey)1449     public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
1450         final boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
1451                 && !mBubbleData.getAnyBubbleWithKey(key).showInShade());
1452 
1453         final boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey);
1454         final boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey));
1455         return (isSummary && isSuppressedSummary) || isSuppressedBubble;
1456     }
1457 
1458     /** Promote the provided bubble from the overflow view. */
promoteBubbleFromOverflow(Bubble bubble)1459     public void promoteBubbleFromOverflow(Bubble bubble) {
1460         if (isShowingAsBubbleBar()) {
1461             mLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_REMOVE_BACK_TO_BAR);
1462         } else {
1463             mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
1464         }
1465         ProtoLog.d(WM_SHELL_BUBBLES, "promoteBubbleFromOverflow=%s", bubble.getKey());
1466         bubble.setInflateSynchronously(mInflateSynchronously);
1467         bubble.setShouldAutoExpand(true);
1468         bubble.markAsAccessedAt(System.currentTimeMillis());
1469         setIsBubble(bubble, true /* isBubble */);
1470     }
1471 
1472     /**
1473      * Expands and selects the provided bubble as long as it already exists in the stack or the
1474      * overflow.
1475      *
1476      * <p>This is used by external callers (launcher).
1477      */
1478     @VisibleForTesting
expandStackAndSelectBubbleFromLauncher(String key, int topOnScreen)1479     public void expandStackAndSelectBubbleFromLauncher(String key, int topOnScreen) {
1480         mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen);
1481 
1482         if (BubbleOverflow.KEY.equals(key)) {
1483             mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
1484             mLayerView.showExpandedView(mBubbleData.getOverflow());
1485             mLogger.log(BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_SELECTED);
1486             return;
1487         }
1488 
1489         final Bubble b = mBubbleData.getAnyBubbleWithKey(key);
1490         if (b == null) {
1491             return;
1492         }
1493         final boolean wasExpanded = (mLayerView != null && mLayerView.isExpanded());
1494         if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) {
1495             // already in the stack
1496             mBubbleData.setSelectedBubbleFromLauncher(b);
1497             mLayerView.showExpandedView(b);
1498             if (wasExpanded) {
1499                 mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
1500             } else {
1501                 mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
1502             }
1503         } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
1504             // TODO: (b/271468319) handle overflow
1505         } else {
1506             Log.w(TAG, "didn't add bubble from launcher: " + key);
1507         }
1508     }
1509 
1510     /**
1511      * Expands the stack if the selected bubble is present. This is currently used when user
1512      * education view is clicked to expand the selected bubble.
1513      */
expandStackWithSelectedBubble()1514     public void expandStackWithSelectedBubble() {
1515         if (mBubbleData.getSelectedBubble() != null) {
1516             mBubbleData.setExpanded(true);
1517         }
1518     }
1519 
1520     /**
1521      * Expands and selects the provided bubble as long as it already exists in the stack or the
1522      * overflow. This is currently used when opening a bubble via clicking on a conversation widget.
1523      */
expandStackAndSelectBubble(Bubble b)1524     public void expandStackAndSelectBubble(Bubble b) {
1525         if (b == null) {
1526             return;
1527         }
1528         if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) {
1529             // already in the stack
1530             mBubbleData.setSelectedBubbleAndExpandStack(b);
1531         } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
1532             // promote it out of the overflow
1533             promoteBubbleFromOverflow(b);
1534         }
1535     }
1536 
1537     /**
1538      * Expands and selects a bubble created or found via the provided shortcut info.
1539      *
1540      * @param info the shortcut info for the bubble.
1541      * @param bubbleBarLocation optional location in case bubble bar should be repositioned.
1542      */
expandStackAndSelectBubble(ShortcutInfo info, @Nullable BubbleBarLocation bubbleBarLocation)1543     public void expandStackAndSelectBubble(ShortcutInfo info,
1544             @Nullable BubbleBarLocation bubbleBarLocation) {
1545         if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
1546         Bubble b = mBubbleData.getOrCreateBubble(info); // Removes from overflow
1547         ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - shortcut=%s", info);
1548         expandStackAndSelectAppBubble(b, bubbleBarLocation, UpdateSource.APP_ICON_DRAG);
1549     }
1550 
1551     /**
1552      * Expands and selects a bubble created or found for this app.
1553      *
1554      * @param intent the intent for the bubble.
1555      */
expandStackAndSelectBubble(Intent intent, UserHandle user, @Nullable BubbleBarLocation bubbleBarLocation)1556     public void expandStackAndSelectBubble(Intent intent, UserHandle user,
1557             @Nullable BubbleBarLocation bubbleBarLocation) {
1558         if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
1559         Bubble b = mBubbleData.getOrCreateBubble(intent, user); // Removes from overflow
1560         ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent);
1561         expandStackAndSelectAppBubble(b, bubbleBarLocation, UpdateSource.APP_ICON_DRAG);
1562     }
1563 
1564     /**
1565      * Expands and selects a bubble created or found for this app.
1566      *
1567      * @param pendingIntent     the intent for the bubble.
1568      * @param bubbleBarLocation optional location in case bubble bar should be repositioned.
1569      */
expandStackAndSelectBubble(PendingIntent pendingIntent, UserHandle user, @Nullable BubbleBarLocation bubbleBarLocation)1570     public void expandStackAndSelectBubble(PendingIntent pendingIntent, UserHandle user,
1571             @Nullable BubbleBarLocation bubbleBarLocation) {
1572         if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
1573         Bubble b = mBubbleData.getOrCreateBubble(pendingIntent, user); // Removes from overflow
1574         ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - pendingIntent=%s",
1575                 pendingIntent);
1576         expandStackAndSelectAppBubble(b, bubbleBarLocation, UpdateSource.APP_ICON_DRAG);
1577     }
1578 
expandStackAndSelectAppBubble(Bubble b, @Nullable BubbleBarLocation bubbleBarLocation, @UpdateSource int source)1579     private void expandStackAndSelectAppBubble(Bubble b,
1580             @Nullable BubbleBarLocation bubbleBarLocation, @UpdateSource int source) {
1581         if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
1582         BubbleBarLocation updateLocation = isShowingAsBubbleBar() ? bubbleBarLocation : null;
1583         if (updateLocation != null) {
1584             updateExpandedViewForBubbleBarLocation(updateLocation, source);
1585         }
1586         if (b.isInflated()) {
1587             mBubbleData.setSelectedBubbleAndExpandStack(b, updateLocation);
1588         } else {
1589             b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
1590             inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false, updateLocation);
1591         }
1592     }
1593 
1594     /**
1595      * Expands and selects a bubble created from a running task in a different mode.
1596      *
1597      * @param taskInfo the task.
1598      * @param dragData optional information about the task when it is being dragged into a bubble
1599      */
expandStackAndSelectBubble(ActivityManager.RunningTaskInfo taskInfo, @Nullable BubbleTransitions.DragData dragData)1600     public void expandStackAndSelectBubble(ActivityManager.RunningTaskInfo taskInfo,
1601             @Nullable BubbleTransitions.DragData dragData) {
1602         if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) return;
1603         Bubble b = mBubbleData.getOrCreateBubble(taskInfo); // Removes from overflow
1604         ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - taskId=%s", taskInfo.taskId);
1605         BubbleBarLocation location = null;
1606         if (dragData != null) {
1607             location =
1608                     dragData.isReleasedOnLeft() ? BubbleBarLocation.LEFT : BubbleBarLocation.RIGHT;
1609         }
1610         if (b.isInflated()) {
1611             mBubbleData.setSelectedBubbleAndExpandStack(b, location);
1612             if (dragData != null && dragData.getPendingWct() != null) {
1613                 mTransitions.startTransition(TRANSIT_CHANGE,
1614                         dragData.getPendingWct(), /* handler= */ null);
1615             }
1616         } else {
1617             if (location != null) {
1618                 setBubbleBarLocation(location, UpdateSource.DRAG_TASK);
1619             }
1620             b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
1621             // Lazy init stack view when a bubble is created
1622             ensureBubbleViewsAndWindowCreated();
1623             mBubbleTransitions.startConvertToBubble(b, taskInfo, mExpandedViewManager,
1624                     mBubbleTaskViewFactory, mBubblePositioner, mStackView, mLayerView,
1625                     mBubbleIconFactory, dragData, mInflateSynchronously);
1626         }
1627     }
1628 
1629     /**
1630      * Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble
1631      * exists for this entry, and it is able to bubble, a new bubble will be created.
1632      *
1633      * <p>This is the method to use when opening a bubble via a notification or in a state where
1634      * the device might not be unlocked.
1635      *
1636      * @param entry the entry to use for the bubble.
1637      */
expandStackAndSelectBubble(BubbleEntry entry)1638     public void expandStackAndSelectBubble(BubbleEntry entry) {
1639         ProtoLog.d(WM_SHELL_BUBBLES, "opening bubble from notification key=%s mIsStatusBarShade=%b",
1640                 entry.getKey(), mIsStatusBarShade);
1641         if (mIsStatusBarShade) {
1642             mNotifEntryToExpandOnShadeUnlock = null;
1643 
1644             String key = entry.getKey();
1645             Bubble bubble = mBubbleData.getBubbleInStackWithKey(key);
1646             if (bubble != null) {
1647                 mBubbleData.setSelectedBubbleAndExpandStack(bubble);
1648             } else {
1649                 bubble = mBubbleData.getOverflowBubbleWithKey(key);
1650                 if (bubble != null) {
1651                     promoteBubbleFromOverflow(bubble);
1652                 } else if (entry.canBubble()) {
1653                     // It can bubble but it's not -- it got aged out of the overflow before it
1654                     // was dismissed or opened, make it a bubble again.
1655                     setIsBubble(entry, true /* isBubble */, true /* autoExpand */);
1656                 }
1657             }
1658         } else {
1659             // Wait until we're unlocked to expand, so that the user can see the expand animation
1660             // and also to work around bugs with expansion animation + shade unlock happening at the
1661             // same time.
1662             mNotifEntryToExpandOnShadeUnlock = entry;
1663         }
1664     }
1665 
1666     /**
1667      * Adds or updates a bubble associated with the provided notification entry.
1668      *
1669      * @param notif the notification associated with this bubble.
1670      */
1671     @VisibleForTesting
updateBubble(BubbleEntry notif)1672     public void updateBubble(BubbleEntry notif) {
1673         int bubbleUserId = notif.getStatusBarNotification().getUserId();
1674         if (isCurrentProfile(bubbleUserId)) {
1675             updateBubble(notif, false /* suppressFlyout */, true /* showInShade */);
1676         } else {
1677             // Skip update, but store it in user bubbles so it gets restored after user switch
1678             mSavedUserBubbleData.get(bubbleUserId, new UserBubbleData()).add(notif.getKey(),
1679                     true /* shownInShade */);
1680             Log.w(TAG, "updateBubble, ignore update for non-active user=" + bubbleUserId
1681                     + " currentUser=" + mCurrentUserId);
1682         }
1683     }
1684 
1685     /**
1686      * This method has different behavior depending on:
1687      *    - if a notes bubble exists
1688      *    - if a notes bubble is expanded
1689      *
1690      * If no notes bubble exists, this will add and expand a bubble with the provided intent. The
1691      * intent must be explicit (i.e. include a package name or fully qualified component class name)
1692      * and the activity for it should be resizable.
1693      *
1694      * If a notes bubble exists, this will toggle the visibility of it, i.e. if the notes bubble is
1695      * expanded, calling this method will collapse it. If the notes bubble is not expanded, calling
1696      * this method will expand it.
1697      *
1698      * These bubbles are <b>not</b> backed by a notification and remain until the user dismisses
1699      * the bubble or bubble stack.
1700      *
1701      * Some details:
1702      *    - Calling this method with a different intent than the existing bubble will do nothing
1703      *
1704      * @param intent the intent to display in the bubble expanded view.
1705      * @param user the {@link UserHandle} of the user to start this activity for.
1706      * @param icon the {@link Icon} to use for the bubble view.
1707      */
showOrHideNotesBubble(Intent intent, UserHandle user, @Nullable Icon icon)1708     public void showOrHideNotesBubble(Intent intent, UserHandle user, @Nullable Icon icon) {
1709         if (intent == null || intent.getPackage() == null) {
1710             Log.w(TAG, "Notes bubble failed to show, invalid intent: " + intent
1711                     + ((intent != null) ? " with package: " + intent.getPackage() : " "));
1712             return;
1713         }
1714 
1715         String noteBubbleKey = Bubble.getNoteBubbleKeyForApp(intent.getPackage(), user);
1716         PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
1717         if (!mResizabilityChecker.isResizableActivity(intent, packageManager, noteBubbleKey)) {
1718             // resize check logs any errors
1719             return;
1720         }
1721 
1722         Bubble existingNotebubble = mBubbleData.getBubbleInStackWithKey(noteBubbleKey);
1723         ProtoLog.d(WM_SHELL_BUBBLES,
1724                 "showOrHideNotesBubble, key=%s existingAppBubble=%s stackVisibility=%s "
1725                         + "statusBarShade=%s",
1726                 noteBubbleKey, existingNotebubble,
1727                 (mStackView != null ? mStackView.getVisibility() : "null"),
1728                 mIsStatusBarShade);
1729 
1730         if (existingNotebubble != null) {
1731             BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
1732             if (isStackExpanded()) {
1733                 if (selectedBubble != null && noteBubbleKey.equals(selectedBubble.getKey())) {
1734                     ProtoLog.d(WM_SHELL_BUBBLES, "collapseStack for %s", noteBubbleKey);
1735                     // Notes bubble is expanded, lets collapse
1736                     collapseStack();
1737                 } else {
1738                     ProtoLog.d(WM_SHELL_BUBBLES, "setSelected for %s", noteBubbleKey);
1739                     // Notes bubble is not selected, select it
1740                     mBubbleData.setSelectedBubble(existingNotebubble);
1741                 }
1742             } else {
1743                 ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleAndExpandStack %s", noteBubbleKey);
1744                 // Notes bubble is not selected, select it & expand
1745                 mBubbleData.setSelectedBubbleAndExpandStack(existingNotebubble);
1746             }
1747         } else {
1748             // Check if it exists in the overflow
1749             Bubble b = mBubbleData.getOverflowBubbleWithKey(noteBubbleKey);
1750             if (b != null) {
1751                 // It's in the overflow, so remove it & reinflate
1752                 mBubbleData.dismissBubbleWithKey(noteBubbleKey, Bubbles.DISMISS_NOTIF_CANCEL);
1753                 // Update the bubble entry in the overflow with the latest intent.
1754                 b.setIntent(intent);
1755             } else {
1756                 // Notes bubble does not exist, lets add and expand it
1757                 b = Bubble.createNotesBubble(intent, user, icon, mMainExecutor,
1758                         mBackgroundExecutor);
1759             }
1760             ProtoLog.d(WM_SHELL_BUBBLES, "inflateAndAdd %s", noteBubbleKey);
1761             b.setShouldAutoExpand(true);
1762             inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
1763         }
1764     }
1765 
1766     /**
1767      * Dismiss bubble if it exists and remove it from the stack
1768      */
dismissBubble(Bubble bubble, @Bubbles.DismissReason int reason)1769     public void dismissBubble(Bubble bubble, @Bubbles.DismissReason int reason) {
1770         dismissBubble(bubble.getKey(), reason);
1771     }
1772 
1773     /**
1774      * Dismiss bubble with given key if it exists and remove it from the stack
1775      */
dismissBubble(String key, @Bubbles.DismissReason int reason)1776     public void dismissBubble(String key, @Bubbles.DismissReason int reason) {
1777         mBubbleData.dismissBubbleWithKey(key, reason);
1778     }
1779 
1780     /**
1781      * Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot
1782      * can be access via the supplied {@link SynchronousScreenCaptureListener#getBuffer()}
1783      * asynchronously.
1784      */
getScreenshotExcludingBubble(int displayId, SynchronousScreenCaptureListener screenCaptureListener)1785     public void getScreenshotExcludingBubble(int displayId,
1786             SynchronousScreenCaptureListener screenCaptureListener) {
1787         try {
1788             ScreenCapture.CaptureArgs args = null;
1789             View viewToUse = mStackView != null ? mStackView : mLayerView;
1790             if (viewToUse != null) {
1791                 ViewRootImpl viewRoot = viewToUse.getViewRootImpl();
1792                 if (viewRoot != null) {
1793                     SurfaceControl bubbleLayer = viewRoot.getSurfaceControl();
1794                     if (bubbleLayer != null) {
1795                         args = new ScreenCapture.CaptureArgs.Builder<>()
1796                                 .setExcludeLayers(new SurfaceControl[] {bubbleLayer})
1797                                 .build();
1798                     }
1799                 }
1800             }
1801 
1802             mWmService.captureDisplay(displayId, args, screenCaptureListener);
1803         } catch (RemoteException e) {
1804             Log.e(TAG, "Failed to capture screenshot");
1805         }
1806     }
1807 
1808     /** Sets the note bubble's taskId which is cached for SysUI. */
setNoteBubbleTaskId(String key, int taskId)1809     public void setNoteBubbleTaskId(String key, int taskId) {
1810         mImpl.mCachedState.setNoteBubbleTaskId(key, taskId);
1811     }
1812 
1813     /**
1814      * Fills the overflow bubbles by loading them from disk.
1815      */
loadOverflowBubblesFromDisk()1816     void loadOverflowBubblesFromDisk() {
1817         if (!mOverflowDataLoadNeeded) {
1818             return;
1819         }
1820         mOverflowDataLoadNeeded = false;
1821         List<UserInfo> users = mUserManager.getAliveUsers();
1822         List<Integer> userIds = users.stream().map(userInfo -> userInfo.id).toList();
1823         mDataRepository.loadBubbles(mCurrentUserId, userIds, (bubbles) -> {
1824             bubbles.forEach(bubble -> {
1825                 if (mBubbleData.hasAnyBubbleWithKey(bubble.getKey())) {
1826                     // if the bubble is already active, there's no need to push it to overflow
1827                     return;
1828                 }
1829                 bubble.inflate(
1830                         (b) -> mBubbleData.overflowBubble(Bubbles.DISMISS_RELOAD_FROM_DISK, bubble),
1831                         mContext,
1832                         mExpandedViewManager,
1833                         mBubbleTaskViewFactory,
1834                         mBubblePositioner,
1835                         mStackView,
1836                         mLayerView,
1837                         mBubbleIconFactory,
1838                         true /* skipInflation */);
1839             });
1840             return null;
1841         });
1842     }
1843 
setUpBubbleViewsForMode()1844     void setUpBubbleViewsForMode() {
1845         mBubbleViewCallback = isShowingAsBubbleBar()
1846                 ? mBubbleBarViewCallback
1847                 : mBubbleStackViewCallback;
1848 
1849         // reset the overflow so that it can be re-added later if needed.
1850         if (mStackView != null) {
1851             mStackView.resetOverflowView();
1852             mStackView.removeAllViews();
1853         }
1854         // cleanup existing bubble views so they can be recreated later if needed, but retain
1855         // TaskView.
1856         mBubbleData.getBubbles().forEach(b -> b.cleanupViews(/* cleanupTaskView= */ false));
1857 
1858         // remove the current bubble container from window manager, null it out, and create a new
1859         // container based on the current mode.
1860         removeFromWindowManagerMaybe();
1861         mLayerView = null;
1862         mStackView = null;
1863 
1864         if (!mBubbleData.hasBubbles()) {
1865             // if there are no bubbles, don't create the stack or layer views. they will be created
1866             // later when the first bubble is added.
1867             return;
1868         }
1869 
1870         ensureBubbleViewsAndWindowCreated();
1871 
1872         // inflate bubble views
1873         BubbleViewInfoTask.Callback callback = null;
1874         if (!isShowingAsBubbleBar()) {
1875             callback = b -> {
1876                 if (mStackView != null) {
1877                     b.setSuppressFlyout(true);
1878                     mStackView.addBubble(b);
1879                     mStackView.setSelectedBubble(b);
1880                 } else {
1881                     Log.w(TAG, "Tried to add a bubble to the stack but the stack is null");
1882                 }
1883             };
1884         } else if (mBubbleData.isExpanded() && mBubbleData.getSelectedBubble() != null) {
1885             callback = b -> {
1886                 if (b.getKey().equals(mBubbleData.getSelectedBubbleKey())) {
1887                     mLayerView.showExpandedView(b);
1888                 }
1889             };
1890         }
1891         for (int i = mBubbleData.getBubbles().size() - 1; i >= 0; i--) {
1892             Bubble bubble = mBubbleData.getBubbles().get(i);
1893             bubble.inflate(callback,
1894                     mContext,
1895                     mExpandedViewManager,
1896                     mBubbleTaskViewFactory,
1897                     mBubblePositioner,
1898                     mStackView,
1899                     mLayerView,
1900                     mBubbleIconFactory,
1901                     false /* skipInflation */);
1902         }
1903     }
1904 
1905     /**
1906      * Adds or updates a bubble associated with the provided notification entry.
1907      *
1908      * @param notif          the notification associated with this bubble.
1909      * @param suppressFlyout this bubble suppress flyout or not.
1910      * @param showInShade    this bubble show in shade or not.
1911      */
1912     @VisibleForTesting
updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade)1913     public void updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade) {
1914         // If this is an interruptive notif, mark that it's interrupted
1915         mSysuiProxy.setNotificationInterruption(notif.getKey());
1916         final boolean isNonInterruptiveNotExpanding = !notif.getRanking().isTextChanged()
1917                 && (notif.getBubbleMetadata() != null
1918                 && !notif.getBubbleMetadata().getAutoExpandBubble());
1919         final Bubble bubble;
1920         if (isNonInterruptiveNotExpanding
1921                 && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
1922             // Update the bubble but don't promote it out of overflow
1923             bubble = mBubbleData.getOverflowBubbleWithKey(notif.getKey());
1924             if (notif.isBubble()) {
1925                 notif.setFlagBubble(false);
1926             }
1927             updateNotNotifyingEntry(bubble, notif, showInShade);
1928         } else if (mBubbleData.hasAnyBubbleWithKey(notif.getKey())
1929                 && isNonInterruptiveNotExpanding) {
1930             bubble = mBubbleData.getAnyBubbleWithKey(notif.getKey());
1931             if (bubble != null) {
1932                 updateNotNotifyingEntry(bubble, notif, showInShade);
1933             }
1934         } else if (mBubbleData.isSuppressedWithLocusId(notif.getLocusId())) {
1935             // Update the bubble but don't promote it out of overflow
1936             bubble = mBubbleData.getSuppressedBubbleWithKey(notif.getKey());
1937             if (bubble != null) {
1938                 updateNotNotifyingEntry(bubble, notif, showInShade);
1939             }
1940         } else {
1941             bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
1942             if (notif.shouldSuppressNotificationList()) {
1943                 // If we're suppressing notifs for DND, we don't want the bubbles to randomly
1944                 // expand when DND turns off so flip the flag.
1945                 if (bubble.shouldAutoExpand()) {
1946                     bubble.setShouldAutoExpand(false);
1947                 }
1948                 mImpl.mCachedState.updateBubbleSuppressedState(bubble);
1949             } else {
1950                 inflateAndAdd(bubble, suppressFlyout, showInShade);
1951             }
1952         }
1953     }
1954 
updateNotNotifyingEntry(Bubble b, BubbleEntry entry, boolean showInShade)1955     void updateNotNotifyingEntry(Bubble b, BubbleEntry entry, boolean showInShade) {
1956         boolean showInShadeBefore = b.showInShade();
1957         boolean isBubbleSelected = Objects.equals(b, mBubbleData.getSelectedBubble());
1958         boolean isBubbleExpandedAndSelected = isStackExpanded() && isBubbleSelected;
1959         b.setEntry(entry);
1960         boolean suppress = isBubbleExpandedAndSelected || !showInShade || !b.showInShade();
1961         b.setSuppressNotification(suppress);
1962         b.setShowDot(!isBubbleExpandedAndSelected);
1963         if (showInShadeBefore != b.showInShade()) {
1964             mImpl.mCachedState.updateBubbleSuppressedState(b);
1965         }
1966     }
1967 
1968     @VisibleForTesting
inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade)1969     public void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
1970         inflateAndAdd(bubble, suppressFlyout, showInShade, /* bubbleBarLocation= */ null);
1971     }
1972 
1973     /**
1974      * Inflates and adds a bubble. Updates Bubble Bar location if bubbles
1975      * are shown in the Bubble Bar and the location is not null.
1976      */
1977     @VisibleForTesting
inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade, @Nullable BubbleBarLocation bubbleBarLocation)1978     public void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade,
1979             @Nullable BubbleBarLocation bubbleBarLocation) {
1980         // Lazy init stack view when a bubble is created
1981         ensureBubbleViewsAndWindowCreated();
1982         bubble.setInflateSynchronously(mInflateSynchronously);
1983         bubble.inflate(
1984                 b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade,
1985                         bubbleBarLocation),
1986                 mContext,
1987                 mExpandedViewManager,
1988                 mBubbleTaskViewFactory,
1989                 mBubblePositioner,
1990                 mStackView,
1991                 mLayerView,
1992                 mBubbleIconFactory,
1993                 false /* skipInflation */);
1994     }
1995 
1996     /**
1997      * Removes the bubble with the given key.
1998      * <p>
1999      * Must be called from the main thread.
2000      */
2001     @MainThread
removeBubble(String key, int reason)2002     public void removeBubble(String key, int reason) {
2003         if (mBubbleData.hasAnyBubbleWithKey(key)) {
2004             mBubbleData.dismissBubbleWithKey(key, reason);
2005         }
2006     }
2007 
2008     /**
2009      * Removes all the bubbles.
2010      * <p>
2011      * Must be called from the main thread.
2012      */
2013     @VisibleForTesting
2014     @MainThread
removeAllBubbles(@ubbles.DismissReason int reason)2015     public void removeAllBubbles(@Bubbles.DismissReason int reason) {
2016         mBubbleData.dismissAll(reason);
2017         if (reason == Bubbles.DISMISS_USER_GESTURE) {
2018             mLogger.log(BubbleLogger.Event.BUBBLE_BAR_DISMISSED_DRAG_BAR);
2019         }
2020     }
2021 
onEntryAdded(BubbleEntry entry)2022     private void onEntryAdded(BubbleEntry entry) {
2023         if (canLaunchInTaskView(mContext, entry)) {
2024             updateBubble(entry);
2025         }
2026     }
2027 
2028     @VisibleForTesting
onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem)2029     public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem) {
2030         if (!fromSystem) {
2031             return;
2032         }
2033         // shouldBubbleUp checks canBubble & for bubble metadata
2034         boolean shouldBubble = shouldBubbleUp && canLaunchInTaskView(mContext, entry);
2035         if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
2036             // It was previously a bubble but no longer a bubble -- lets remove it
2037             removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE);
2038         } else if (shouldBubble && entry.isBubble()) {
2039             updateBubble(entry);
2040         }
2041     }
2042 
onEntryRemoved(BubbleEntry entry)2043     private void onEntryRemoved(BubbleEntry entry) {
2044         if (isSummaryOfBubbles(entry)) {
2045             final String groupKey = entry.getStatusBarNotification().getGroupKey();
2046             mBubbleData.removeSuppressedSummary(groupKey);
2047 
2048             // Remove any associated bubble children with the summary
2049             final List<Bubble> bubbleChildren = getBubblesInGroup(groupKey);
2050             for (int i = 0; i < bubbleChildren.size(); i++) {
2051                 removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED);
2052             }
2053         } else {
2054             removeBubble(entry.getKey(), DISMISS_NOTIF_CANCEL);
2055         }
2056     }
2057 
2058     @VisibleForTesting
onRankingUpdated(RankingMap rankingMap, HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey)2059     public void onRankingUpdated(RankingMap rankingMap,
2060             HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
2061         if (mTmpRanking == null) {
2062             mTmpRanking = new NotificationListenerService.Ranking();
2063         }
2064         String[] orderedKeys = rankingMap.getOrderedKeys();
2065         for (int i = 0; i < orderedKeys.length; i++) {
2066             String key = orderedKeys[i];
2067             Pair<BubbleEntry, Boolean> entryData = entryDataByKey.get(key);
2068             BubbleEntry entry = entryData.first;
2069             boolean shouldBubbleUp = entryData.second;
2070             if (entry != null && !isCurrentProfile(
2071                     entry.getStatusBarNotification().getUser().getIdentifier())) {
2072                 return;
2073             }
2074             if (entry != null && (entry.shouldSuppressNotificationList()
2075                     || entry.getRanking().isSuspended())) {
2076                 shouldBubbleUp = false;
2077             }
2078             rankingMap.getRanking(key, mTmpRanking);
2079             boolean isActiveOrInOverflow = mBubbleData.hasAnyBubbleWithKey(key);
2080             boolean isActive = mBubbleData.hasBubbleInStackWithKey(key);
2081             if (isActiveOrInOverflow && !mTmpRanking.canBubble()) {
2082                 // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
2083                 // This means that the app or channel's ability to bubble has been revoked.
2084                 mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED);
2085             } else if (isActiveOrInOverflow && !shouldBubbleUp) {
2086                 // If this entry is allowed to bubble, but cannot currently bubble up or is
2087                 // suspended, dismiss it. This happens when DND is enabled and configured to hide
2088                 // bubbles, or focus mode is enabled and the app is designated as distracting.
2089                 // Dismissing with the reason DISMISS_NO_BUBBLE_UP will retain the underlying
2090                 // notification, so that the bubble will be re-created if shouldBubbleUp returns
2091                 // true.
2092                 mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP);
2093             } else if (entry != null && mTmpRanking.isBubble() && !isActiveOrInOverflow) {
2094                 entry.setFlagBubble(true);
2095                 onEntryUpdated(entry, shouldBubbleUp, /* fromSystem= */ true);
2096             }
2097         }
2098     }
2099 
2100     @VisibleForTesting
onNotificationChannelModified(String pkg, UserHandle user, NotificationChannel channel, int modificationType)2101     public void onNotificationChannelModified(String pkg, UserHandle user,
2102             NotificationChannel channel, int modificationType) {
2103         // Only query overflow bubbles here because active bubbles will have an active notification
2104         // and channel changes we care about would result in a ranking update.
2105         List<Bubble> overflowBubbles = new ArrayList<>(mBubbleData.getOverflowBubbles());
2106         for (int i = 0; i < overflowBubbles.size(); i++) {
2107             Bubble b = overflowBubbles.get(i);
2108             if (Objects.equals(b.getShortcutId(), channel.getConversationId())
2109                     && b.getPackageName().equals(pkg)
2110                     && b.getUser().getIdentifier() == user.getIdentifier()) {
2111                 if (!channel.canBubble() || channel.isDeleted()) {
2112                     mBubbleData.dismissBubbleWithKey(b.getKey(), DISMISS_NO_LONGER_BUBBLE);
2113                 }
2114             }
2115         }
2116     }
2117 
2118     /**
2119      * Retrieves any bubbles that are part of the notification group represented by the provided
2120      * group key.
2121      */
getBubblesInGroup(@ullable String groupKey)2122     private ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey) {
2123         ArrayList<Bubble> bubbleChildren = new ArrayList<>();
2124         if (groupKey == null) {
2125             return bubbleChildren;
2126         }
2127         for (Bubble bubble : mBubbleData.getBubbles()) {
2128             if (bubble.getGroupKey() != null && groupKey.equals(bubble.getGroupKey())) {
2129                 bubbleChildren.add(bubble);
2130             }
2131         }
2132         return bubbleChildren;
2133     }
2134 
setIsBubble(@onNull final BubbleEntry entry, final boolean isBubble, final boolean autoExpand)2135     private void setIsBubble(@NonNull final BubbleEntry entry, final boolean isBubble,
2136             final boolean autoExpand) {
2137         Objects.requireNonNull(entry);
2138         entry.setFlagBubble(isBubble);
2139         try {
2140             int flags = 0;
2141             if (autoExpand) {
2142                 flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
2143                 flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
2144             }
2145             mBarService.onNotificationBubbleChanged(entry.getKey(), isBubble, flags);
2146         } catch (RemoteException e) {
2147             // Bad things have happened
2148         }
2149     }
2150 
setIsBubble(@onNull final Bubble b, final boolean isBubble)2151     private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) {
2152         Objects.requireNonNull(b);
2153         b.setIsBubble(isBubble);
2154         mSysuiProxy.getPendingOrActiveEntry(b.getKey(), (entry) -> {
2155             mMainExecutor.execute(() -> {
2156                 if (entry != null) {
2157                     // Updating the entry to be a bubble will trigger our normal update flow
2158                     setIsBubble(entry, isBubble, b.shouldAutoExpand());
2159                 } else if (isBubble) {
2160                     // If bubble doesn't exist, it's a persisted bubble so we need to add it to the
2161                     // stack ourselves
2162                     Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */);
2163                     inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */,
2164                             !bubble.shouldAutoExpand() /* showInShade */);
2165                 }
2166             });
2167         });
2168     }
2169 
2170     @Override
mergeTaskWithUnfold(@onNull ActivityManager.RunningTaskInfo taskInfo, @NonNull TransitionInfo.Change change, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT)2171     public boolean mergeTaskWithUnfold(@NonNull ActivityManager.RunningTaskInfo taskInfo,
2172             @NonNull TransitionInfo.Change change,
2173             @NonNull SurfaceControl.Transaction startT,
2174             @NonNull SurfaceControl.Transaction finishT) {
2175         if (!mBubbleTransitions.mTaskViewTransitions.isTaskViewTask(taskInfo)) {
2176             // if this task isn't managed by bubble transitions just bail.
2177             return false;
2178         }
2179         if (isShowingAsBubbleBar()) {
2180             // if bubble bar is enabled, the task view will switch to a new surface on unfold, so we
2181             // should not merge the transition.
2182             return false;
2183         }
2184 
2185         boolean merged = mBubbleTransitions.mTaskViewTransitions.updateBoundsForUnfold(
2186                 change.getEndAbsBounds(), startT, finishT, change.getTaskInfo(), change.getLeash());
2187         if (merged) {
2188             BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
2189             if (selectedBubble != null && selectedBubble.getExpandedView() != null) {
2190                 selectedBubble.getExpandedView().onContainerClipUpdate();
2191             }
2192         }
2193         return merged;
2194     }
2195 
2196     /** When bubbles are floating, this will be used to notify the floating views. */
2197     private final BubbleViewCallback mBubbleStackViewCallback = new BubbleViewCallback() {
2198         @Override
2199         public void removeBubble(Bubble removedBubble) {
2200             if (mStackView != null) {
2201                 mStackView.removeBubble(removedBubble);
2202             }
2203         }
2204 
2205         @Override
2206         public void addBubble(Bubble addedBubble) {
2207             if (mStackView != null) {
2208                 mStackView.addBubble(addedBubble);
2209             }
2210         }
2211 
2212         @Override
2213         public void updateBubble(Bubble updatedBubble) {
2214             if (mStackView != null) {
2215                 mStackView.updateBubble(updatedBubble);
2216             }
2217         }
2218 
2219         @Override
2220         public void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer) {
2221             if (mStackView != null) {
2222                 mStackView.updateBubbleOrder(bubbleOrder, updatePointer);
2223             }
2224         }
2225 
2226         @Override
2227         public void suppressionChanged(Bubble bubble, boolean isSuppressed) {
2228             if (mStackView != null) {
2229                 mStackView.setBubbleSuppressed(bubble, isSuppressed);
2230             }
2231         }
2232 
2233         @Override
2234         public void expansionChanged(boolean isExpanded) {
2235             if (mStackView != null) {
2236                 mStackView.setExpanded(isExpanded);
2237             }
2238         }
2239 
2240         @Override
2241         public void selectionChanged(BubbleViewProvider selectedBubble) {
2242             if (mStackView != null) {
2243                 mStackView.setSelectedBubble(selectedBubble);
2244             }
2245 
2246         }
2247 
2248         @Override
2249         public void bubbleOverflowChanged(boolean hasBubbles) {
2250             if (Flags.enableOptionalBubbleOverflow()) {
2251                 if (mStackView != null) {
2252                     mStackView.showOverflow(hasBubbles);
2253                 }
2254             }
2255         }
2256     };
2257 
2258     /** When bubbles are in the bubble bar, this will be used to notify bubble bar views. */
2259     private final BubbleViewCallback mBubbleBarViewCallback = new BubbleViewCallback() {
2260         @Override
2261         public void removeBubble(Bubble removedBubble) {
2262             if (mLayerView != null) {
2263                 final BubbleTransitions.BubbleTransition bubbleTransit =
2264                         removedBubble.getPreparingTransition();
2265                 mLayerView.removeBubble(removedBubble, () -> {
2266                     if (bubbleTransit != null) {
2267                         bubbleTransit.continueCollapse();
2268                     }
2269                     if (!mBubbleData.hasBubbles() && !isStackExpanded()) {
2270                         mLayerView.setVisibility(INVISIBLE);
2271                         removeFromWindowManagerMaybe();
2272                     }
2273                 });
2274             }
2275         }
2276 
2277         @Override
2278         public void addBubble(Bubble addedBubble) {
2279             // Only log metrics event
2280             mLogger.log(addedBubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED);
2281             // Nothing to do for adds, these are handled by launcher / in the bubble bar.
2282         }
2283 
2284         @Override
2285         public void updateBubble(Bubble updatedBubble) {
2286             // Only log metrics event
2287             mLogger.log(updatedBubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_UPDATED);
2288             // Nothing to do for updates, these are handled by launcher / in the bubble bar.
2289         }
2290 
2291         @Override
2292         public void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer) {
2293             // Nothing to do for order changes, these are handled by launcher / in the bubble bar.
2294         }
2295 
2296         @Override
2297         public void bubbleOverflowChanged(boolean hasBubbles) {
2298             // Nothing to do for our views, handled by launcher / in the bubble bar.
2299         }
2300 
2301         @Override
2302         public void suppressionChanged(Bubble bubble, boolean isSuppressed) {
2303             // Nothing to do for our views, handled by launcher / in the bubble bar.
2304         }
2305 
2306         @Override
2307         public void expansionChanged(boolean isExpanded) {
2308             // in bubble bar mode, let the request to show the expanded view come from launcher.
2309             // only collapse here if we're collapsing.
2310             if (!isExpanded) {
2311                 collapseExpandedViewForBubbleBar();
2312             }
2313 
2314             BubbleLogger.Event event = isExpanded ? BubbleLogger.Event.BUBBLE_BAR_EXPANDED
2315                     : BubbleLogger.Event.BUBBLE_BAR_COLLAPSED;
2316             BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
2317             if (selectedBubble instanceof Bubble) {
2318                 mLogger.log((Bubble) selectedBubble, event);
2319             } else {
2320                 mLogger.log(event);
2321             }
2322         }
2323 
2324         @Override
2325         public void selectionChanged(BubbleViewProvider selectedBubble) {
2326             // Only need to update the layer view if we're currently expanded for selection changes.
2327             if (mLayerView != null && mLayerView.isExpanded()) {
2328                 mLayerView.showExpandedView(selectedBubble);
2329                 if (selectedBubble instanceof Bubble) {
2330                     mLogger.log((Bubble) selectedBubble,
2331                             BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
2332                 }
2333             }
2334         }
2335     };
2336 
2337     @SuppressWarnings("FieldCanBeLocal")
2338     private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {
2339 
2340         @Override
2341         public void applyUpdate(BubbleData.Update update) {
2342             ProtoLog.d(WM_SHELL_BUBBLES, "mBubbleDataListener#applyUpdate:"
2343                     + " added=%s removed=%b updated=%s orderChanged=%b expansionChanged=%b"
2344                     + " expanded=%b selectionChanged=%b selected=%s"
2345                     + " suppressed=%s unsupressed=%s shouldShowEducation=%b showOverflowChanged=%b"
2346                     + " bubbleBarLocation=%s",
2347                     update.addedBubble != null ? update.addedBubble.getKey() : "null",
2348                     !update.removedBubbles.isEmpty(),
2349                     update.updatedBubble != null ? update.updatedBubble.getKey() : "null",
2350                     update.orderChanged, update.expandedChanged, update.expanded,
2351                     update.selectionChanged,
2352                     update.selectedBubble != null ? update.selectedBubble.getKey() : "null",
2353                     update.suppressedBubble != null ? update.suppressedBubble.getKey() : "null",
2354                     update.unsuppressedBubble != null ? update.unsuppressedBubble.getKey() : "null",
2355                     update.shouldShowEducation, update.showOverflowChanged,
2356                     update.mBubbleBarLocation != null ? update.mBubbleBarLocation.toString()
2357                             : "null");
2358 
2359             ensureBubbleViewsAndWindowCreated();
2360 
2361             // Lazy load overflow bubbles from disk
2362             loadOverflowBubblesFromDisk();
2363 
2364             if (update.showOverflowChanged) {
2365                 mBubbleViewCallback.bubbleOverflowChanged(!update.overflowBubbles.isEmpty());
2366             }
2367 
2368             // If bubbles in the overflow have a dot, make sure the overflow shows a dot
2369             updateOverflowButtonDot();
2370 
2371             // Update bubbles in overflow.
2372             if (mOverflowListener != null) {
2373                 mOverflowListener.applyUpdate(update);
2374             }
2375 
2376             // Do removals, if any.
2377             ArrayList<Pair<Bubble, Integer>> removedBubbles =
2378                     new ArrayList<>(update.removedBubbles);
2379             ArrayList<Bubble> bubblesToBeRemovedFromRepository = new ArrayList<>();
2380             for (Pair<Bubble, Integer> removed : removedBubbles) {
2381                 final Bubble bubble = removed.first;
2382                 @Bubbles.DismissReason final int reason = removed.second;
2383 
2384                 mBubbleViewCallback.removeBubble(bubble);
2385 
2386                 // Leave the notification in place if we're dismissing due to user switching, or
2387                 // because DND is suppressing the bubble. In both of those cases, we need to be able
2388                 // to restore the bubble from the notification later.
2389                 if (reason == DISMISS_USER_CHANGED || reason == DISMISS_NO_BUBBLE_UP) {
2390                     continue;
2391                 }
2392                 if (reason == DISMISS_NOTIF_CANCEL
2393                         || reason == DISMISS_SHORTCUT_REMOVED) {
2394                     bubblesToBeRemovedFromRepository.add(bubble);
2395                 }
2396                 if (!mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
2397                     if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey())
2398                             && (!bubble.showInShade()
2399                             || reason == DISMISS_NOTIF_CANCEL
2400                             || reason == DISMISS_GROUP_CANCELLED)) {
2401                         // The bubble is now gone & the notification is hidden from the shade, so
2402                         // time to actually remove it
2403                         mSysuiProxy.notifyRemoveNotification(bubble.getKey(), REASON_CANCEL);
2404                     } else {
2405                         if (bubble.isBubble()) {
2406                             setIsBubble(bubble, false /* isBubble */);
2407                         }
2408                         mSysuiProxy.updateNotificationBubbleButton(bubble.getKey());
2409                     }
2410                 }
2411             }
2412             mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);
2413 
2414             if (update.addedBubble != null) {
2415                 mDataRepository.addBubble(mCurrentUserId, update.addedBubble);
2416                 mBubbleViewCallback.addBubble(update.addedBubble);
2417             }
2418 
2419             if (update.updatedBubble != null) {
2420                 mBubbleViewCallback.updateBubble(update.updatedBubble);
2421             }
2422 
2423             if (update.suppressedBubble != null) {
2424                 mBubbleViewCallback.suppressionChanged(update.suppressedBubble, true);
2425             }
2426 
2427             if (update.unsuppressedBubble != null) {
2428                 mBubbleViewCallback.suppressionChanged(update.unsuppressedBubble, false);
2429             }
2430 
2431             boolean collapseStack = update.expandedChanged && !update.expanded;
2432 
2433             // At this point, the correct bubbles are inflated in the stack.
2434             // Make sure the order in bubble data is reflected in bubble row.
2435             if (update.orderChanged) {
2436                 mDataRepository.addBubbles(mCurrentUserId, update.bubbles);
2437                 // if the stack is going to be collapsed, do not update pointer position
2438                 // after reordering
2439                 mBubbleViewCallback.bubbleOrderChanged(update.bubbles, !collapseStack);
2440             }
2441 
2442             if (collapseStack) {
2443                 mBubbleViewCallback.expansionChanged(/* expanded= */ false);
2444                 mSysuiProxy.requestNotificationShadeTopUi(false, TAG);
2445             }
2446 
2447             if (update.selectionChanged) {
2448                 mBubbleViewCallback.selectionChanged(update.selectedBubble);
2449             }
2450 
2451             // Expanding? Apply this last.
2452             if (update.expandedChanged && update.expanded) {
2453                 mBubbleViewCallback.expansionChanged(/* expanded= */ true);
2454                 mSysuiProxy.requestNotificationShadeTopUi(true, TAG);
2455             }
2456 
2457             mSysuiProxy.notifyInvalidateNotifications("BubbleData.Listener.applyUpdate");
2458             updateBubbleViews();
2459 
2460             // Update the cached state for queries from SysUI
2461             mImpl.mCachedState.update(update);
2462 
2463             if (isShowingAsBubbleBar()) {
2464                 BubbleBarUpdate bubbleBarUpdate = update.toBubbleBarUpdate();
2465                 // Some updates aren't relevant to the bubble bar so check first.
2466                 if (bubbleBarUpdate.anythingChanged()) {
2467                     mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
2468                 }
2469             }
2470         }
2471     };
2472 
showExpandedViewForBubbleBar()2473     private void showExpandedViewForBubbleBar() {
2474         BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
2475         if (selectedBubble == null) return;
2476         if (selectedBubble instanceof Bubble) {
2477             final Bubble bubble = (Bubble) selectedBubble;
2478             if (bubble.getPreparingTransition() != null) {
2479                 bubble.getPreparingTransition().continueExpand();
2480                 return;
2481             }
2482         }
2483         if (mLayerView == null) return;
2484         mLayerView.showExpandedView(selectedBubble);
2485     }
2486 
collapseExpandedViewForBubbleBar()2487     private void collapseExpandedViewForBubbleBar() {
2488         if (mLayerView != null && mLayerView.isExpanded()) {
2489             if (mBubblePositioner.isImeVisible()) {
2490                 // If we're collapsing, hide the IME
2491                 hideCurrentInputMethod(null);
2492             }
2493             mLayerView.collapse();
2494         }
2495     }
2496 
updateOverflowButtonDot()2497     private void updateOverflowButtonDot() {
2498         BubbleOverflow overflow = mBubbleData.getOverflow();
2499         if (overflow == null) return;
2500 
2501         for (Bubble b : mBubbleData.getOverflowBubbles()) {
2502             if (b.showDot()) {
2503                 overflow.setShowDot(true);
2504                 return;
2505             }
2506         }
2507         overflow.setShowDot(false);
2508     }
2509 
handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children, IntConsumer removeCallback)2510     private boolean handleDismissalInterception(BubbleEntry entry,
2511             @Nullable List<BubbleEntry> children, IntConsumer removeCallback) {
2512         if (isSummaryOfBubbles(entry)) {
2513             handleSummaryDismissalInterception(entry, children, removeCallback);
2514         } else {
2515             Bubble bubble = mBubbleData.getBubbleInStackWithKey(entry.getKey());
2516             if (bubble == null || !entry.isBubble()) {
2517                 bubble = mBubbleData.getOverflowBubbleWithKey(entry.getKey());
2518             }
2519             if (bubble == null) {
2520                 return false;
2521             }
2522             bubble.setSuppressNotification(true);
2523             bubble.setShowDot(false /* show */);
2524         }
2525         // Update the shade
2526         mSysuiProxy.notifyInvalidateNotifications("BubbleController.handleDismissalInterception");
2527         return true;
2528     }
2529 
isSummaryOfBubbles(BubbleEntry entry)2530     private boolean isSummaryOfBubbles(BubbleEntry entry) {
2531         String groupKey = entry.getStatusBarNotification().getGroupKey();
2532         ArrayList<Bubble> bubbleChildren = getBubblesInGroup(groupKey);
2533         boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey)
2534                 && mBubbleData.getSummaryKey(groupKey).equals(entry.getKey());
2535         boolean isSummary = entry.getStatusBarNotification().getNotification().isGroupSummary();
2536         return (isSuppressedSummary || isSummary) && !bubbleChildren.isEmpty();
2537     }
2538 
handleSummaryDismissalInterception( BubbleEntry summary, @Nullable List<BubbleEntry> children, IntConsumer removeCallback)2539     private void handleSummaryDismissalInterception(
2540             BubbleEntry summary, @Nullable List<BubbleEntry> children, IntConsumer removeCallback) {
2541         if (children != null) {
2542             for (int i = 0; i < children.size(); i++) {
2543                 final BubbleEntry child = children.get(i);
2544                 if (mBubbleData.hasAnyBubbleWithKey(child.getKey())) {
2545                     // Suppress the bubbled child
2546                     // As far as group manager is concerned, once a child is no longer shown
2547                     // in the shade, it is essentially removed.
2548                     final Bubble bubbleChild = mBubbleData.getAnyBubbleWithKey(child.getKey());
2549                     if (bubbleChild != null) {
2550                         bubbleChild.setSuppressNotification(true);
2551                         bubbleChild.setShowDot(false /* show */);
2552                     }
2553                 } else {
2554                     // non-bubbled children can be removed
2555                     removeCallback.accept(i);
2556                 }
2557             }
2558         }
2559 
2560         // And since all children are removed, remove the summary.
2561         removeCallback.accept(-1);
2562 
2563         mBubbleData.addSummaryToSuppress(summary.getStatusBarNotification().getGroupKey(),
2564                 summary.getKey());
2565     }
2566 
2567     /**
2568      * Updates the visibility of the bubbles based on current state.
2569      * Does not un-bubble, just hides or un-hides the views themselves.
2570      *
2571      * Updates view description for TalkBack focus.
2572      * Updates bubbles' icon views clickable states (when floating).
2573      */
updateBubbleViews()2574     public void updateBubbleViews() {
2575         if (mStackView == null && mLayerView == null) {
2576             return;
2577         }
2578         ProtoLog.v(WM_SHELL_BUBBLES, "updateBubbleViews mIsStatusBarShade=%s hasBubbles=%s",
2579                 mIsStatusBarShade, hasBubbles());
2580         if (!mIsStatusBarShade) {
2581             // Bubbles don't appear when the device is locked.
2582             if (mStackView != null) {
2583                 mStackView.setVisibility(INVISIBLE);
2584             }
2585             if (mLayerView != null) {
2586                 mLayerView.setVisibility(INVISIBLE);
2587             }
2588         } else if (hasBubbles()) {
2589             // If we're unlocked, show the stack if we have bubbles. If we don't have bubbles, the
2590             // stack will be set to INVISIBLE in onAllBubblesAnimatedOut after the bubbles animate
2591             // out.
2592             if (mStackView != null) {
2593                 mStackView.setVisibility(VISIBLE);
2594             }
2595             if (mLayerView != null) {
2596                 mLayerView.setVisibility(VISIBLE);
2597             }
2598         }
2599 
2600         if (mStackView != null) {
2601             mStackView.updateContentDescription();
2602             mStackView.updateBubblesAcessibillityStates();
2603         } else if (mLayerView != null) {
2604             // TODO(b/273313561): handle a11y for BubbleBarLayerView
2605         }
2606     }
2607 
2608     /**
2609      * Returns whether the stack is animating or not.
2610      */
isStackAnimating()2611     public boolean isStackAnimating() {
2612         return mStackView != null
2613                 && (mStackView.isExpansionAnimating()
2614                 || mStackView.isSwitchAnimating());
2615     }
2616 
2617     @VisibleForTesting
2618     @Nullable
getStackView()2619     public BubbleStackView getStackView() {
2620         return mStackView;
2621     }
2622 
2623     @VisibleForTesting
2624     @Nullable
getLayerView()2625     public BubbleBarLayerView getLayerView() {
2626         return mLayerView;
2627     }
2628 
2629     /**
2630      * Check if notification panel is in an expanded state.
2631      * Makes a call to System UI process and delivers the result via {@code callback} on the
2632      * WM Shell main thread.
2633      *
2634      * @param callback callback that has the result of notification panel expanded state
2635      */
isNotificationPanelExpanded(Consumer<Boolean> callback)2636     public void isNotificationPanelExpanded(Consumer<Boolean> callback) {
2637         mSysuiProxy.isNotificationPanelExpand(expanded ->
2638                 mMainExecutor.execute(() -> callback.accept(expanded)));
2639     }
2640 
2641     /**
2642      * Show bubbles UI when triggered via shortcut.
2643      *
2644      * <p>When there are bubbles visible, expands the top-most bubble. When there are no bubbles
2645      * visible, opens the bubbles overflow UI.
2646      */
showBubblesFromShortcut()2647     public void showBubblesFromShortcut() {
2648         if (isStackExpanded()) {
2649             ProtoLog.v(WM_SHELL_BUBBLES, "showBubblesFromShortcut: stack visible, skip");
2650             return;
2651         }
2652         if (mBubbleData.getSelectedBubble() != null) {
2653             ProtoLog.v(WM_SHELL_BUBBLES, "showBubblesFromShortcut: open selected bubble");
2654             expandStackWithSelectedBubble();
2655             return;
2656         }
2657         BubbleViewProvider bubbleToSelect = CollectionUtils.firstOrNull(mBubbleData.getBubbles());
2658         if (bubbleToSelect == null) {
2659             ProtoLog.v(WM_SHELL_BUBBLES, "showBubblesFromShortcut: no bubbles");
2660             // make sure overflow bubbles are loaded
2661             loadOverflowBubblesFromDisk();
2662             bubbleToSelect = mBubbleData.getOverflow();
2663         }
2664         ProtoLog.v(WM_SHELL_BUBBLES, "showBubblesFromShortcut: select and open %s",
2665                 bubbleToSelect.getKey());
2666         mBubbleData.setSelectedBubbleAndExpandStack(bubbleToSelect);
2667     }
2668 
moveDraggedBubbleToFullscreen(String key, Point dropLocation)2669     private void moveDraggedBubbleToFullscreen(String key, Point dropLocation) {
2670         Bubble b = mBubbleData.getBubbleInStackWithKey(key);
2671         mBubbleTransitions.startDraggedBubbleIconToFullscreen(b, dropLocation);
2672     }
2673 
isDeviceLocked()2674     private boolean isDeviceLocked() {
2675         return !mIsStatusBarShade;
2676     }
2677 
2678     /**
2679      * Description of current bubble state.
2680      */
dump(PrintWriter pw, String prefix)2681     private void dump(PrintWriter pw, String prefix) {
2682         pw.print(prefix); pw.println("BubbleController state:");
2683         pw.print(prefix); pw.println("  currentUserId= " + mCurrentUserId);
2684         pw.print(prefix); pw.println("  isStatusBarShade= " + mIsStatusBarShade);
2685         pw.print(prefix); pw.println("  isShowingAsBubbleBar= " + isShowingAsBubbleBar());
2686         pw.print(prefix); pw.println("  isImeVisible= " + mBubblePositioner.isImeVisible());
2687         pw.println();
2688 
2689         mBubbleData.dump(pw);
2690         pw.println();
2691 
2692         if (mStackView != null) {
2693             mStackView.dump(pw);
2694         }
2695         pw.println();
2696 
2697         mImpl.mCachedState.dump(pw);
2698     }
2699 
2700     /**
2701      * Whether an intent is properly configured to display in a
2702      * {@link TaskView}.
2703      *
2704      * Keep checks in sync with BubbleExtractor#canLaunchInTaskView. Typically
2705      * that should filter out any invalid bubbles, but should protect SysUI side just in case.
2706      *
2707      * @param context the context to use.
2708      * @param entry   the entry to bubble.
2709      */
canLaunchInTaskView(Context context, BubbleEntry entry)2710     boolean canLaunchInTaskView(Context context, BubbleEntry entry) {
2711         if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) return true;
2712         PendingIntent intent = entry.getBubbleMetadata() != null
2713                 ? entry.getBubbleMetadata().getIntent()
2714                 : null;
2715         if (entry.getBubbleMetadata() != null
2716                 && entry.getBubbleMetadata().getShortcutId() != null) {
2717             return true;
2718         }
2719         if (intent == null) {
2720             Log.w(TAG, "Unable to create bubble -- no intent: " + entry.getKey());
2721             return false;
2722         }
2723         PackageManager packageManager = getPackageManagerForUser(
2724                 context, entry.getStatusBarNotification().getUser().getIdentifier());
2725         return mResizabilityChecker.isResizableActivity(intent.getIntent(), packageManager,
2726                 entry.getKey());
2727     }
2728 
getPackageManagerForUser(Context context, int userId)2729     static PackageManager getPackageManagerForUser(Context context, int userId) {
2730         Context contextForUser = context;
2731         // UserHandle defines special userId as negative values, e.g. USER_ALL
2732         if (userId >= 0) {
2733             try {
2734                 // Create a context for the correct user so if a package isn't installed
2735                 // for user 0 we can still load information about the package.
2736                 contextForUser =
2737                         context.createPackageContextAsUser(context.getPackageName(),
2738                                 Context.CONTEXT_RESTRICTED,
2739                                 new UserHandle(userId));
2740             } catch (PackageManager.NameNotFoundException e) {
2741                 // Shouldn't fail to find the package name for system ui.
2742             }
2743         }
2744         return contextForUser.getPackageManager();
2745     }
2746 
2747     /** {@link ImeListener} that dispatches IME visibility updates to the stack. */
2748     private class BubblesImeListener extends ImeListener implements
2749             DisplayImeController.ImePositionProcessor {
2750 
BubblesImeListener(DisplayController displayController, int displayId)2751         BubblesImeListener(DisplayController displayController, int displayId) {
2752             super(displayController, displayId);
2753         }
2754 
2755         @Override
onImeVisibilityChanged(boolean imeVisible, int imeHeight)2756         protected void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
2757             if (getDisplayId() != mContext.getDisplayId()) {
2758                 return;
2759             }
2760             // the imeHeight here is actually the ime inset; it only includes the part of the ime
2761             // that overlaps with the Bubbles window. adjust it to include the bottom screen inset,
2762             // so we have the total height of the ime.
2763             int totalImeHeight = imeHeight + mBubblePositioner.getInsets().bottom;
2764             mBubblePositioner.setImeVisible(imeVisible, totalImeHeight);
2765             if (mStackView != null) {
2766                 mStackView.setImeVisible(imeVisible);
2767                 if (!imeVisible && mOnImeHidden != null) {
2768                     mOnImeHidden.run();
2769                     mOnImeHidden = null;
2770                 }
2771             }
2772         }
2773 
2774         @Override
onImeStartPositioning(int displayId, int hiddenTop, int shownTop, boolean showing, boolean isFloating, SurfaceControl.Transaction t)2775         public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
2776                 boolean showing, boolean isFloating, SurfaceControl.Transaction t) {
2777             if (mContext.getDisplayId() != displayId) {
2778                 return IME_ANIMATION_DEFAULT;
2779             }
2780 
2781             if (showing) {
2782                 mBubblePositioner.setImeVisible(true, hiddenTop - shownTop);
2783             } else {
2784                 mBubblePositioner.setImeVisible(false, 0);
2785             }
2786             if (mStackView != null) {
2787                 mStackView.setImeVisible(showing);
2788             }
2789 
2790             return IME_ANIMATION_DEFAULT;
2791         }
2792 
2793         @Override
onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t)2794         public void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) {
2795             if (mContext.getDisplayId() != displayId) {
2796                 return;
2797             }
2798             if (mLayerView != null) {
2799                 mLayerView.onImeTopChanged(imeTop);
2800             }
2801         }
2802     }
2803 
2804     /**
2805      * The interface for calls from outside the host process.
2806      */
2807     @BinderThread
2808     private class IBubblesImpl extends IBubbles.Stub implements ExternalInterfaceBinder {
2809         private BubbleController mController;
2810         private final SingleInstanceRemoteListener<BubbleController, IBubblesListener> mListener;
2811         private final Bubbles.BubbleStateListener mBubbleListener =
2812                 new Bubbles.BubbleStateListener() {
2813                     @Override
2814                     public void onBubbleStateChange(BubbleBarUpdate update) {
2815                         Bundle b = new Bundle();
2816                         b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
2817                         b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
2818                         mListener.call(l -> l.onBubbleStateChange(b));
2819                     }
2820 
2821                     @Override
2822                     public void animateBubbleBarLocation(BubbleBarLocation location) {
2823                         mListener.call(l -> l.animateBubbleBarLocation(location));
2824                     }
2825 
2826                     @Override
2827                     public void onDragItemOverBubbleBarDragZone(
2828                             @NonNull BubbleBarLocation location) {
2829                         mListener.call(l -> l.onDragItemOverBubbleBarDragZone(location));
2830                     }
2831 
2832                     @Override
2833                     public void onItemDraggedOutsideBubbleBarDropZone() {
2834                         mListener.call(IBubblesListener::onItemDraggedOutsideBubbleBarDropZone);
2835                     }
2836                 };
2837 
IBubblesImpl(BubbleController controller)2838         IBubblesImpl(BubbleController controller) {
2839             mController = controller;
2840             mListener = new SingleInstanceRemoteListener<>(mController,
2841                     c -> c.registerBubbleStateListener(mBubbleListener),
2842                     c -> c.unregisterBubbleStateListener());
2843         }
2844 
2845         /**
2846          * Invalidates this instance, preventing future calls from updating the controller.
2847          */
2848         @Override
invalidate()2849         public void invalidate() {
2850             mController = null;
2851             // Unregister the listeners to ensure any binder death recipients are unlinked
2852             mListener.unregister();
2853         }
2854 
2855         @Override
registerBubbleListener(IBubblesListener listener)2856         public void registerBubbleListener(IBubblesListener listener) {
2857             mMainExecutor.execute(() -> mListener.register(listener));
2858         }
2859 
2860         @Override
unregisterBubbleListener(IBubblesListener listener)2861         public void unregisterBubbleListener(IBubblesListener listener) {
2862             mMainExecutor.execute(mListener::unregister);
2863         }
2864 
2865         @Override
showShortcutBubble(ShortcutInfo info, @Nullable BubbleBarLocation location)2866         public void showShortcutBubble(ShortcutInfo info, @Nullable BubbleBarLocation location) {
2867             mMainExecutor.execute(() -> mController
2868                     .expandStackAndSelectBubble(info, location));
2869         }
2870 
2871         @Override
showAppBubble(Intent intent, UserHandle user, @Nullable BubbleBarLocation location)2872         public void showAppBubble(Intent intent, UserHandle user,
2873                 @Nullable BubbleBarLocation location) {
2874             mMainExecutor.execute(
2875                     () -> mController.expandStackAndSelectBubble(intent, user, location));
2876         }
2877 
2878         @Override
showBubble(String key, int topOnScreen)2879         public void showBubble(String key, int topOnScreen) {
2880             mMainExecutor.execute(
2881                     () -> mController.expandStackAndSelectBubbleFromLauncher(key, topOnScreen));
2882         }
2883 
2884         @Override
removeAllBubbles()2885         public void removeAllBubbles() {
2886             mMainExecutor.execute(() -> mController.removeAllBubbles(Bubbles.DISMISS_USER_GESTURE));
2887         }
2888 
2889         @Override
collapseBubbles()2890         public void collapseBubbles() {
2891             mMainExecutor.execute(() -> {
2892                 if (mBubbleData.getSelectedBubble() instanceof Bubble) {
2893                     if (((Bubble) mBubbleData.getSelectedBubble()).getPreparingTransition()
2894                             != null) {
2895                         // Currently preparing a transition which will, itself, collapse the bubble.
2896                         // For transition preparation, the timing of bubble-collapse must be in
2897                         // sync with the rest of the set-up.
2898                         return;
2899                     }
2900                 }
2901                 mController.collapseStack();
2902             });
2903         }
2904 
2905         @Override
startBubbleDrag(String bubbleKey)2906         public void startBubbleDrag(String bubbleKey) {
2907             mMainExecutor.execute(() -> mController.startBubbleDrag(bubbleKey));
2908         }
2909 
2910         @Override
stopBubbleDrag(BubbleBarLocation location, int topOnScreen)2911         public void stopBubbleDrag(BubbleBarLocation location, int topOnScreen) {
2912             mMainExecutor.execute(() -> mController.stopBubbleDrag(location, topOnScreen));
2913         }
2914 
2915         @Override
dragBubbleToDismiss(String key, long timestamp)2916         public void dragBubbleToDismiss(String key, long timestamp) {
2917             mMainExecutor.execute(() -> mController.dragBubbleToDismiss(key, timestamp));
2918         }
2919 
2920         @Override
showUserEducation(int positionX, int positionY)2921         public void showUserEducation(int positionX, int positionY) {
2922             mMainExecutor.execute(() ->
2923                     mController.showUserEducation(new Point(positionX, positionY)));
2924         }
2925 
2926         @Override
setBubbleBarLocation(BubbleBarLocation location, @UpdateSource int source)2927         public void setBubbleBarLocation(BubbleBarLocation location,
2928                 @UpdateSource int source) {
2929             mMainExecutor.execute(() ->
2930                     mController.setBubbleBarLocation(location, source));
2931         }
2932 
2933         @Override
updateBubbleBarTopOnScreen(int topOnScreen)2934         public void updateBubbleBarTopOnScreen(int topOnScreen) {
2935             mMainExecutor.execute(() -> {
2936                 mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen);
2937                 if (mLayerView != null) mLayerView.updateExpandedView();
2938             });
2939         }
2940 
2941         @Override
showExpandedView()2942         public void showExpandedView() {
2943             mMainExecutor.execute(() -> {
2944                 if (mLayerView != null) {
2945                     showExpandedViewForBubbleBar();
2946                 }
2947             });
2948         }
2949 
2950         @Override
showDropTarget(boolean show, BubbleBarLocation location)2951         public void showDropTarget(boolean show, BubbleBarLocation location) {
2952             mMainExecutor.execute(() -> {
2953                 if (show) {
2954                     showBubbleBarExpandedViewDropTarget(location);
2955                 } else {
2956                     hideBubbleBarExpandedViewDropTarget();
2957                 }
2958             });
2959         }
2960 
2961         @Override
moveDraggedBubbleToFullscreen(String key, Point dropLocation)2962         public void moveDraggedBubbleToFullscreen(String key, Point dropLocation) {
2963             mMainExecutor.execute(
2964                     () -> mController.moveDraggedBubbleToFullscreen(key, dropLocation));
2965         }
2966     }
2967 
2968     private class BubblesImpl implements Bubbles {
2969         // Up-to-date cached state of bubbles data for SysUI to query from the calling thread
2970         @VisibleForTesting
2971         public class CachedState {
2972             private boolean mIsStackExpanded;
2973             private String mSelectedBubbleKey;
2974             private HashSet<String> mSuppressedBubbleKeys = new HashSet<>();
2975             private HashMap<String, String> mSuppressedGroupToNotifKeys = new HashMap<>();
2976             private HashMap<String, Bubble> mShortcutIdToBubble = new HashMap<>();
2977 
2978             private HashMap<String, Integer> mNoteBubbleTaskIds = new HashMap();
2979 
2980             private ArrayList<Bubble> mTmpBubbles = new ArrayList<>();
2981 
2982             /**
2983              * Updates the cached state based on the last full BubbleData change.
2984              */
update(BubbleData.Update update)2985             synchronized void update(BubbleData.Update update) {
2986                 if (update.selectionChanged) {
2987                     mSelectedBubbleKey = update.selectedBubble != null
2988                             ? update.selectedBubble.getKey()
2989                             : null;
2990                 }
2991                 if (update.expandedChanged) {
2992                     mIsStackExpanded = update.expanded;
2993                 }
2994                 if (update.suppressedSummaryChanged) {
2995                     String summaryKey =
2996                             mBubbleData.getSummaryKey(update.suppressedSummaryGroup);
2997                     if (summaryKey != null) {
2998                         mSuppressedGroupToNotifKeys.put(update.suppressedSummaryGroup, summaryKey);
2999                     } else {
3000                         mSuppressedGroupToNotifKeys.remove(update.suppressedSummaryGroup);
3001                     }
3002                 }
3003 
3004                 mTmpBubbles.clear();
3005                 mTmpBubbles.addAll(update.bubbles);
3006                 mTmpBubbles.addAll(update.overflowBubbles);
3007 
3008                 mSuppressedBubbleKeys.clear();
3009                 mShortcutIdToBubble.clear();
3010                 mNoteBubbleTaskIds.clear();
3011                 for (Bubble b : mTmpBubbles) {
3012                     mShortcutIdToBubble.put(b.getShortcutId(), b);
3013                     updateBubbleSuppressedState(b);
3014 
3015                     if (b.isNote()) {
3016                         mNoteBubbleTaskIds.put(b.getKey(), b.getTaskId());
3017                     }
3018                 }
3019             }
3020 
3021             /** Sets the note bubble's taskId which is cached for SysUI. */
setNoteBubbleTaskId(String key, int taskId)3022             synchronized void setNoteBubbleTaskId(String key, int taskId) {
3023                 mNoteBubbleTaskIds.put(key, taskId);
3024             }
3025 
3026             /**
3027              * Updates a specific bubble suppressed state.  This is used mainly because notification
3028              * suppression changes don't go through the same BubbleData update mechanism.
3029              */
updateBubbleSuppressedState(Bubble b)3030             synchronized void updateBubbleSuppressedState(Bubble b) {
3031                 if (!b.showInShade()) {
3032                     mSuppressedBubbleKeys.add(b.getKey());
3033                 } else {
3034                     mSuppressedBubbleKeys.remove(b.getKey());
3035                 }
3036             }
3037 
isStackExpanded()3038             public synchronized boolean isStackExpanded() {
3039                 return mIsStackExpanded;
3040             }
3041 
isBubbleExpanded(String key)3042             public synchronized boolean isBubbleExpanded(String key) {
3043                 return mIsStackExpanded && key.equals(mSelectedBubbleKey);
3044             }
3045 
isBubbleNotificationSuppressedFromShade(String key, String groupKey)3046             public synchronized boolean isBubbleNotificationSuppressedFromShade(String key,
3047                     String groupKey) {
3048                 return mSuppressedBubbleKeys.contains(key)
3049                         || (mSuppressedGroupToNotifKeys.containsKey(groupKey)
3050                         && key.equals(mSuppressedGroupToNotifKeys.get(groupKey)));
3051             }
3052 
3053             @Nullable
getBubbleWithShortcutId(String id)3054             public synchronized Bubble getBubbleWithShortcutId(String id) {
3055                 return mShortcutIdToBubble.get(id);
3056             }
3057 
dump(PrintWriter pw)3058             synchronized void dump(PrintWriter pw) {
3059                 pw.println("BubbleImpl.CachedState state:");
3060 
3061                 pw.println("mIsStackExpanded: " + mIsStackExpanded);
3062                 pw.println("mSelectedBubbleKey: " + mSelectedBubbleKey);
3063 
3064                 pw.println("mSuppressedBubbleKeys: " + mSuppressedBubbleKeys.size());
3065                 for (String key : mSuppressedBubbleKeys) {
3066                     pw.println("   suppressing: " + key);
3067                 }
3068 
3069                 pw.print("mSuppressedGroupToNotifKeys: ");
3070                 pw.println(mSuppressedGroupToNotifKeys.size());
3071                 for (String key : mSuppressedGroupToNotifKeys.keySet()) {
3072                     pw.println("   suppressing: " + key);
3073                 }
3074 
3075                 pw.println("mNoteBubbleTaskIds: " + mNoteBubbleTaskIds.values());
3076             }
3077         }
3078 
3079         private CachedState mCachedState = new CachedState();
3080 
3081         @Override
isBubbleNotificationSuppressedFromShade(String key, String groupKey)3082         public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
3083             return mCachedState.isBubbleNotificationSuppressedFromShade(key, groupKey);
3084         }
3085 
3086         @Override
isBubbleExpanded(String key)3087         public boolean isBubbleExpanded(String key) {
3088             return mCachedState.isBubbleExpanded(key);
3089         }
3090 
3091         @Override
3092         @Nullable
getBubbleWithShortcutId(String shortcutId)3093         public Bubble getBubbleWithShortcutId(String shortcutId) {
3094             return mCachedState.getBubbleWithShortcutId(shortcutId);
3095         }
3096 
3097         @Override
collapseStack()3098         public void collapseStack() {
3099             mMainExecutor.execute(() -> {
3100                 BubbleController.this.collapseStack();
3101             });
3102         }
3103 
3104         @Override
expandStackAndSelectBubble(BubbleEntry entry)3105         public void expandStackAndSelectBubble(BubbleEntry entry) {
3106             mMainExecutor.execute(() -> {
3107                 BubbleController.this.expandStackAndSelectBubble(entry);
3108             });
3109         }
3110 
3111         @Override
expandStackAndSelectBubble(ShortcutInfo info)3112         public void expandStackAndSelectBubble(ShortcutInfo info) {
3113             mMainExecutor.execute(() ->
3114                     BubbleController.this
3115                             .expandStackAndSelectBubble(info, /* bubbleBarLocation = */ null)
3116             );
3117         }
3118 
3119         @Override
expandStackAndSelectBubble(Bubble bubble)3120         public void expandStackAndSelectBubble(Bubble bubble) {
3121             mMainExecutor.execute(() -> {
3122                 BubbleController.this.expandStackAndSelectBubble(bubble);
3123             });
3124         }
3125 
3126         @Override
showOrHideNoteBubble(Intent intent, UserHandle user, @Nullable Icon icon)3127         public void showOrHideNoteBubble(Intent intent, UserHandle user, @Nullable Icon icon) {
3128             mMainExecutor.execute(
3129                     () -> BubbleController.this.showOrHideNotesBubble(intent, user, icon));
3130         }
3131 
3132         @Override
isNoteBubbleTaskId(int taskId)3133         public boolean isNoteBubbleTaskId(int taskId) {
3134             return mCachedState.mNoteBubbleTaskIds.values().contains(taskId);
3135         }
3136 
3137         @Override
3138         @Nullable
getScreenshotExcludingBubble(int displayId)3139         public SynchronousScreenCaptureListener getScreenshotExcludingBubble(int displayId) {
3140             SynchronousScreenCaptureListener screenCaptureListener =
3141                     ScreenCapture.createSyncCaptureListener();
3142 
3143             mMainExecutor.execute(
3144                     () -> BubbleController.this.getScreenshotExcludingBubble(displayId,
3145                             screenCaptureListener));
3146 
3147             return screenCaptureListener;
3148         }
3149 
3150         @Override
handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children, IntConsumer removeCallback, Executor callbackExecutor)3151         public boolean handleDismissalInterception(BubbleEntry entry,
3152                 @Nullable List<BubbleEntry> children, IntConsumer removeCallback,
3153                 Executor callbackExecutor) {
3154             IntConsumer cb = removeCallback != null
3155                     ? (index) -> callbackExecutor.execute(() -> removeCallback.accept(index))
3156                     : null;
3157             return mMainExecutor.executeBlockingForResult(() -> {
3158                 return BubbleController.this.handleDismissalInterception(entry, children, cb);
3159             }, Boolean.class);
3160         }
3161 
3162         @Override
setSysuiProxy(SysuiProxy proxy)3163         public void setSysuiProxy(SysuiProxy proxy) {
3164             mMainExecutor.execute(() -> {
3165                 BubbleController.this.setSysuiProxy(proxy);
3166             });
3167         }
3168 
3169         @Override
setExpandListener(BubbleExpandListener listener)3170         public void setExpandListener(BubbleExpandListener listener) {
3171             mMainExecutor.execute(() -> {
3172                 BubbleController.this.setExpandListener(listener);
3173             });
3174         }
3175 
3176         @Override
onEntryAdded(BubbleEntry entry)3177         public void onEntryAdded(BubbleEntry entry) {
3178             mMainExecutor.execute(() -> {
3179                 BubbleController.this.onEntryAdded(entry);
3180             });
3181         }
3182 
3183         @Override
onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem)3184         public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem) {
3185             mMainExecutor.execute(() -> {
3186                 BubbleController.this.onEntryUpdated(entry, shouldBubbleUp, fromSystem);
3187             });
3188         }
3189 
3190         @Override
onEntryRemoved(BubbleEntry entry)3191         public void onEntryRemoved(BubbleEntry entry) {
3192             mMainExecutor.execute(() -> {
3193                 BubbleController.this.onEntryRemoved(entry);
3194             });
3195         }
3196 
3197         @Override
onRankingUpdated(RankingMap rankingMap, HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey)3198         public void onRankingUpdated(RankingMap rankingMap,
3199                 HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
3200             mMainExecutor.execute(() -> {
3201                 BubbleController.this.onRankingUpdated(rankingMap, entryDataByKey);
3202             });
3203         }
3204 
3205         @Override
onNotificationChannelModified(String pkg, UserHandle user, NotificationChannel channel, int modificationType)3206         public void onNotificationChannelModified(String pkg,
3207                 UserHandle user, NotificationChannel channel, int modificationType) {
3208             // Bubbles only cares about updates or deletions.
3209             if (modificationType == NOTIFICATION_CHANNEL_OR_GROUP_UPDATED
3210                     || modificationType == NOTIFICATION_CHANNEL_OR_GROUP_DELETED) {
3211                 mMainExecutor.execute(() -> {
3212                     BubbleController.this.onNotificationChannelModified(pkg, user, channel,
3213                             modificationType);
3214                 });
3215             }
3216         }
3217 
3218         @Override
onStatusBarVisibilityChanged(boolean visible)3219         public void onStatusBarVisibilityChanged(boolean visible) {
3220             mMainExecutor.execute(() -> {
3221                 BubbleController.this.onStatusBarVisibilityChanged(visible);
3222             });
3223         }
3224 
3225         @Override
onZenStateChanged()3226         public void onZenStateChanged() {
3227             mMainExecutor.execute(() -> {
3228                 BubbleController.this.onZenStateChanged();
3229             });
3230         }
3231 
3232         @Override
onStatusBarStateChanged(boolean isShade)3233         public void onStatusBarStateChanged(boolean isShade) {
3234             mMainExecutor.execute(() -> {
3235                 BubbleController.this.onStatusBarStateChanged(isShade);
3236             });
3237         }
3238 
3239         @Override
onUserChanged(int newUserId)3240         public void onUserChanged(int newUserId) {
3241             mMainExecutor.execute(() -> {
3242                 BubbleController.this.onUserChanged(newUserId);
3243             });
3244         }
3245 
3246         @Override
onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles)3247         public void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) {
3248             mMainExecutor.execute(() -> {
3249                 BubbleController.this.onCurrentProfilesChanged(currentProfiles);
3250             });
3251         }
3252 
3253         @Override
onUserRemoved(int removedUserId)3254         public void onUserRemoved(int removedUserId) {
3255             mMainExecutor.execute(() -> {
3256                 BubbleController.this.onUserRemoved(removedUserId);
3257             });
3258         }
3259 
3260         @Override
onNotificationPanelExpandedChanged(boolean expanded)3261         public void onNotificationPanelExpandedChanged(boolean expanded) {
3262             mMainExecutor.execute(
3263                     () -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
3264         }
3265 
3266         @Override
onSensitiveNotificationProtectionStateChanged( boolean sensitiveNotificationProtectionActive)3267         public void onSensitiveNotificationProtectionStateChanged(
3268                 boolean sensitiveNotificationProtectionActive) {
3269             mMainExecutor.execute(
3270                     () -> BubbleController.this.onSensitiveNotificationProtectionStateChanged(
3271                             sensitiveNotificationProtectionActive));
3272         }
3273 
3274         @Override
canShowBubbleNotification()3275         public boolean canShowBubbleNotification() {
3276             // in bubble bar mode, when the IME is visible we can't animate new bubbles.
3277             if (BubbleController.this.isShowingAsBubbleBar()) {
3278                 return !BubbleController.this.mBubblePositioner.isImeVisible();
3279             }
3280             return true;
3281         }
3282     }
3283 
3284     /**
3285      * Bubble data that is stored per user.
3286      * Used to store and restore active bubbles during user switching.
3287      */
3288     private static class UserBubbleData {
3289         private final Map<String, Boolean> mKeyToShownInShadeMap = new HashMap<>();
3290 
3291         /**
3292          * Add bubble key and whether it should be shown in notification shade
3293          */
add(String key, boolean shownInShade)3294         void add(String key, boolean shownInShade) {
3295             mKeyToShownInShadeMap.put(key, shownInShade);
3296         }
3297 
3298         /**
3299          * Get all bubble keys stored for this user
3300          */
getKeys()3301         Set<String> getKeys() {
3302             return mKeyToShownInShadeMap.keySet();
3303         }
3304 
3305         /**
3306          * Check if this bubble with the given key should be shown in the notification shade
3307          */
isShownInShade(String key)3308         boolean isShownInShade(String key) {
3309             return mKeyToShownInShadeMap.get(key);
3310         }
3311     }
3312 
3313     private class BubbleTaskViewController implements TaskViewController {
3314         private final TaskViewTransitions mBaseTransitions;
3315 
BubbleTaskViewController(TaskViewTransitions baseTransitions)3316         BubbleTaskViewController(TaskViewTransitions baseTransitions) {
3317             mBaseTransitions = baseTransitions;
3318         }
3319 
3320         @Override
registerTaskView(TaskViewTaskController tv)3321         public void registerTaskView(TaskViewTaskController tv) {
3322             mBaseTransitions.registerTaskView(tv);
3323         }
3324 
3325         @Override
unregisterTaskView(TaskViewTaskController tv)3326         public void unregisterTaskView(TaskViewTaskController tv) {
3327             mBaseTransitions.unregisterTaskView(tv);
3328         }
3329 
3330         @Override
startShortcutActivity(@onNull TaskViewTaskController destination, @NonNull ShortcutInfo shortcut, @NonNull ActivityOptions options, @Nullable Rect launchBounds)3331         public void startShortcutActivity(@NonNull TaskViewTaskController destination,
3332                 @NonNull ShortcutInfo shortcut, @NonNull ActivityOptions options,
3333                 @Nullable Rect launchBounds) {
3334             mBaseTransitions.startShortcutActivity(destination, shortcut, options, launchBounds);
3335         }
3336 
3337         @Override
startActivity(@onNull TaskViewTaskController destination, @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, @NonNull ActivityOptions options, @Nullable Rect launchBounds)3338         public void startActivity(@NonNull TaskViewTaskController destination,
3339                 @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
3340                 @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
3341             mBaseTransitions.startActivity(destination, pendingIntent, fillInIntent,
3342                     options, launchBounds);
3343         }
3344 
3345         @Override
startRootTask(@onNull TaskViewTaskController destination, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, @Nullable WindowContainerTransaction wct)3346         public void startRootTask(@NonNull TaskViewTaskController destination,
3347                 ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
3348                 @Nullable WindowContainerTransaction wct) {
3349             mBaseTransitions.startRootTask(destination, taskInfo, leash, wct);
3350         }
3351 
3352         @Override
removeTaskView(@onNull TaskViewTaskController taskView, @Nullable WindowContainerToken taskToken)3353         public void removeTaskView(@NonNull TaskViewTaskController taskView,
3354                 @Nullable WindowContainerToken taskToken) {
3355             mBaseTransitions.removeTaskView(taskView, taskToken);
3356         }
3357 
3358         @Override
moveTaskViewToFullscreen(@onNull TaskViewTaskController taskView)3359         public void moveTaskViewToFullscreen(@NonNull TaskViewTaskController taskView) {
3360             final TaskInfo tinfo = taskView.getTaskInfo();
3361             if (tinfo == null) {
3362                 return;
3363             }
3364             Bubble bub = null;
3365             for (Bubble b : mBubbleData.getBubbles()) {
3366                 if (b.getTaskId() == tinfo.taskId) {
3367                     bub = b;
3368                     break;
3369                 }
3370             }
3371             if (bub == null) {
3372                 return;
3373             }
3374             mBubbleTransitions.startConvertFromBubble(bub, tinfo);
3375         }
3376 
3377         @Override
setTaskViewVisible(TaskViewTaskController taskView, boolean visible)3378         public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) {
3379             mBaseTransitions.setTaskViewVisible(taskView, visible);
3380         }
3381 
3382         @Override
setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen)3383         public void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
3384             mBaseTransitions.setTaskBounds(taskView, boundsOnScreen);
3385         }
3386 
3387         @Override
isUsingShellTransitions()3388         public boolean isUsingShellTransitions() {
3389             return mBaseTransitions.isUsingShellTransitions();
3390         }
3391     }
3392 }
3393