blob: c9ce8a10cca7d530691faa2198ac423b890c7c1f [file] [log] [blame]
Mady Mellorc3d6f7d2018-11-07 09:36:56 -08001/*
2 * Copyright (C) 2018 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
17package com.android.systemui.bubbles;
18
Mady Mellor3a0a1b42019-05-23 06:40:21 -070019import static android.app.Notification.FLAG_BUBBLE;
Mady Mellorc2ff0112019-03-28 14:18:06 -070020import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
21import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
22import static android.service.notification.NotificationListenerService.REASON_CANCEL;
23import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
Mady Mellor22f2f072019-04-18 13:26:18 -070024import static android.service.notification.NotificationListenerService.REASON_CLICK;
25import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
Mady Mellor390bff42019-04-05 15:09:01 -070026import static android.view.Display.DEFAULT_DISPLAY;
27import static android.view.Display.INVALID_DISPLAY;
Mady Mellord1c78b262018-11-06 18:04:40 -080028import static android.view.View.INVISIBLE;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080029import static android.view.View.VISIBLE;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080030import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080031
Issei Suzukia8d07312019-06-07 12:56:19 +020032import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
Mady Mellorff076eb2019-11-13 10:12:06 -080033import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_EXPERIMENTS;
Issei Suzukia8d07312019-06-07 12:56:19 +020034import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
35import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080036import static com.android.systemui.statusbar.StatusBarState.SHADE;
Mady Mellor1a4e86f2019-05-03 16:07:23 -070037import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080038
Mark Renoufba5ab512019-05-02 15:21:01 -040039import static java.lang.annotation.ElementType.FIELD;
40import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
41import static java.lang.annotation.ElementType.PARAMETER;
Mark Renouf08bc42a2019-03-07 13:01:59 -050042import static java.lang.annotation.RetentionPolicy.SOURCE;
43
Mark Renoufc19b4732019-06-26 12:08:33 -040044import android.annotation.UserIdInt;
Mark Renoufc808f062019-02-07 15:20:37 -050045import android.app.ActivityManager.RunningTaskInfo;
Mady Mellor66efd5e2019-05-15 13:38:11 -070046import android.app.NotificationManager;
Mady Mellorca0c24c2019-05-16 16:14:32 -070047import android.app.PendingIntent;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080048import android.content.Context;
Mady Mellorca0c24c2019-05-16 16:14:32 -070049import android.content.pm.ActivityInfo;
Mady Mellorf3b9fab2019-11-13 17:27:32 -080050import android.content.pm.PackageManager;
Joshua Tsujif418f9e2019-04-04 17:09:53 -040051import android.content.res.Configuration;
Mady Mellord1c78b262018-11-06 18:04:40 -080052import android.graphics.Rect;
Mark Renoufcecc77b2019-01-30 16:32:24 -050053import android.os.RemoteException;
Mady Mellorb4991e62019-01-10 15:14:51 -080054import android.os.ServiceManager;
Mady Mellor56515c42020-02-18 17:58:36 -080055import android.service.notification.NotificationListenerService;
Mark Renoufbbcf07f2019-05-09 10:42:43 -040056import android.service.notification.NotificationListenerService.RankingMap;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -040057import android.service.notification.ZenModeConfig;
Mark Renoufc19b4732019-06-26 12:08:33 -040058import android.util.ArraySet;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040059import android.util.Log;
Mark Renouf82a40e62019-05-23 16:16:24 -040060import android.util.Pair;
Mark Renoufc19b4732019-06-26 12:08:33 -040061import android.util.SparseSetArray;
Mark Renoufcecc77b2019-01-30 16:32:24 -050062import android.view.Display;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080063import android.view.ViewGroup;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080064import android.widget.FrameLayout;
65
Mark Renouf08bc42a2019-03-07 13:01:59 -050066import androidx.annotation.IntDef;
Mark Renouf658c6bc2019-01-30 10:26:54 -050067import androidx.annotation.MainThread;
Joshua Tsujic650a142019-05-22 11:31:19 -040068import androidx.annotation.Nullable;
Mark Renouf658c6bc2019-01-30 10:26:54 -050069
Mady Mellorebdbbb92018-11-15 14:36:48 -080070import com.android.internal.annotations.VisibleForTesting;
Mady Mellora54e9fa2019-04-18 13:26:18 -070071import com.android.internal.statusbar.IStatusBarService;
Beverlya53fb0d2020-01-29 15:26:13 -050072import com.android.internal.statusbar.NotificationVisibility;
Beverlya53fb0d2020-01-29 15:26:13 -050073import com.android.systemui.Dumpable;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080074import com.android.systemui.R;
Sergey Nikolaienkov5cb6e522020-02-10 17:33:00 +010075import com.android.systemui.bubbles.dagger.BubbleModule;
Ned Burnsaaeb44b2020-02-12 23:48:26 -050076import com.android.systemui.dump.DumpManager;
Beverly8fdb5332019-02-04 14:29:49 -050077import com.android.systemui.plugins.statusbar.StatusBarStateController;
Mark Renoufcecc77b2019-01-30 16:32:24 -050078import com.android.systemui.shared.system.ActivityManagerWrapper;
Hongwei Wang43a752b2019-09-17 20:20:30 +000079import com.android.systemui.shared.system.PinnedStackListenerForwarder;
Mark Renoufcecc77b2019-01-30 16:32:24 -050080import com.android.systemui.shared.system.TaskStackChangeListener;
Joshua Tsujia19515f2019-02-13 18:02:29 -050081import com.android.systemui.shared.system.WindowManagerWrapper;
Beverlya53fb0d2020-01-29 15:26:13 -050082import com.android.systemui.statusbar.FeatureFlags;
Mark Renoufc19b4732019-06-26 12:08:33 -040083import com.android.systemui.statusbar.NotificationLockscreenUserManager;
Mady Mellorc2ff0112019-03-28 14:18:06 -070084import com.android.systemui.statusbar.NotificationRemoveInterceptor;
Ned Burns01e38212019-01-03 16:32:52 -050085import com.android.systemui.statusbar.notification.NotificationEntryListener;
86import com.android.systemui.statusbar.notification.NotificationEntryManager;
Beverlya53fb0d2020-01-29 15:26:13 -050087import com.android.systemui.statusbar.notification.collection.NotifCollection;
88import com.android.systemui.statusbar.notification.collection.NotifPipeline;
Ned Burnsf81c4c42019-01-07 14:10:43 -050089import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Beverlya53fb0d2020-01-29 15:26:13 -050090import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
Beverly Taid1e175c2020-03-10 16:37:04 +000091import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
Mady Mellor22f2f072019-04-18 13:26:18 -070092import com.android.systemui.statusbar.phone.NotificationGroupManager;
wilsonshihe8321942019-10-18 18:39:46 +080093import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
Mady Mellor7f234902019-10-20 12:06:29 -070094import com.android.systemui.statusbar.phone.ShadeController;
Mady Mellorf3b9fab2019-11-13 17:27:32 -080095import com.android.systemui.statusbar.phone.StatusBar;
Lyn Hanf1c9b8b2019-03-14 16:49:48 -070096import com.android.systemui.statusbar.policy.ConfigurationController;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -040097import com.android.systemui.statusbar.policy.ZenModeController;
Joshua Tsuji7155bf12020-02-13 16:14:29 -050098import com.android.systemui.util.FloatingContentCoordinator;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080099
Mady Mellor70cba7bb2019-07-02 15:06:07 -0700100import java.io.FileDescriptor;
101import java.io.PrintWriter;
Mark Renouf08bc42a2019-03-07 13:01:59 -0500102import java.lang.annotation.Retention;
Mark Renoufba5ab512019-05-02 15:21:01 -0400103import java.lang.annotation.Target;
Mady Mellor22f2f072019-04-18 13:26:18 -0700104import java.util.ArrayList;
Mady Mellorff076eb2019-11-13 10:12:06 -0800105import java.util.HashSet;
Lyn Hanb58c7562020-01-07 14:29:20 -0800106import java.util.List;
Mark Renouf08bc42a2019-03-07 13:01:59 -0500107
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800108/**
109 * Bubbles are a special type of content that can "float" on top of other apps or System UI.
110 * Bubbles can be expanded to show more content.
111 *
112 * The controller manages addition, removal, and visible state of bubbles on screen.
113 */
Beverlya53fb0d2020-01-29 15:26:13 -0500114public class BubbleController implements ConfigurationController.ConfigurationListener, Dumpable {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800115
Issei Suzukia8d07312019-06-07 12:56:19 +0200116 private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800117
Mark Renouf08bc42a2019-03-07 13:01:59 -0500118 @Retention(SOURCE)
119 @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
Mark Renoufc19b4732019-06-26 12:08:33 -0400120 DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
Mady Mellor8454ddf2019-08-15 11:16:23 -0700121 DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT})
Mark Renoufba5ab512019-05-02 15:21:01 -0400122 @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
Mark Renouf08bc42a2019-03-07 13:01:59 -0500123 @interface DismissReason {}
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700124
Mark Renouf08bc42a2019-03-07 13:01:59 -0500125 static final int DISMISS_USER_GESTURE = 1;
126 static final int DISMISS_AGED = 2;
127 static final int DISMISS_TASK_FINISHED = 3;
128 static final int DISMISS_BLOCKED = 4;
129 static final int DISMISS_NOTIF_CANCEL = 5;
130 static final int DISMISS_ACCESSIBILITY_ACTION = 6;
Mady Melloraa8fef22019-04-11 13:36:40 -0700131 static final int DISMISS_NO_LONGER_BUBBLE = 7;
Mark Renoufc19b4732019-06-26 12:08:33 -0400132 static final int DISMISS_USER_CHANGED = 8;
Mady Mellor22f2f072019-04-18 13:26:18 -0700133 static final int DISMISS_GROUP_CANCELLED = 9;
Mady Mellor8454ddf2019-08-15 11:16:23 -0700134 static final int DISMISS_INVALID_INTENT = 10;
Mark Renouf08bc42a2019-03-07 13:01:59 -0500135
Ned Burns01e38212019-01-03 16:32:52 -0500136 private final Context mContext;
137 private final NotificationEntryManager mNotificationEntryManager;
Beverlya53fb0d2020-01-29 15:26:13 -0500138 private final NotifPipeline mNotifPipeline;
Mark Renoufcecc77b2019-01-30 16:32:24 -0500139 private final BubbleTaskStackListener mTaskStackListener;
Mady Mellord1c78b262018-11-06 18:04:40 -0800140 private BubbleStateChangeListener mStateChangeListener;
Mady Mellorcd9b1302018-11-06 18:08:04 -0800141 private BubbleExpandListener mExpandListener;
Issei Suzukic0387542019-03-08 17:31:14 +0100142 @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
Mady Mellor22f2f072019-04-18 13:26:18 -0700143 private final NotificationGroupManager mNotificationGroupManager;
Heemin Seogba6337f2019-12-10 15:34:37 -0800144 private final ShadeController mShadeController;
Joshua Tsuji7155bf12020-02-13 16:14:29 -0500145 private final FloatingContentCoordinator mFloatingContentCoordinator;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800146
Mady Mellor3dff9e62019-02-05 18:12:53 -0800147 private BubbleData mBubbleData;
Joshua Tsujic650a142019-05-22 11:31:19 -0400148 @Nullable private BubbleStackView mStackView;
Mady Mellor247ca2c2019-12-02 16:18:59 -0800149 private BubbleIconFactory mBubbleIconFactory;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800150
Mark Renoufc19b4732019-06-26 12:08:33 -0400151 // Tracks the id of the current (foreground) user.
152 private int mCurrentUserId;
153 // Saves notification keys of active bubbles when users are switched.
154 private final SparseSetArray<String> mSavedBubbleKeysPerUser;
155
Mady Mellor56515c42020-02-18 17:58:36 -0800156 // Used when ranking updates occur and we check if things should bubble / unbubble
157 private NotificationListenerService.Ranking mTmpRanking;
158
Mady Mellorff076eb2019-11-13 10:12:06 -0800159 // Saves notification keys of user created "fake" bubbles so that we can allow notifications
160 // like these to bubble by default. Doesn't persist across reboots, not a long-term solution.
161 private final HashSet<String> mUserCreatedBubbles;
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800162 // If we're auto-bubbling bubbles via a whitelist, we need to track which notifs from that app
163 // have been "demoted" back to a notification so that we don't auto-bubbles those again.
164 // Doesn't persist across reboots, not a long-term solution.
165 private final HashSet<String> mUserBlockedBubbles;
Mady Mellorff076eb2019-11-13 10:12:06 -0800166
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800167 // Bubbles get added to the status bar view
wilsonshihe8321942019-10-18 18:39:46 +0800168 private final NotificationShadeWindowController mNotificationShadeWindowController;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400169 private final ZenModeController mZenModeController;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800170 private StatusBarStateListener mStatusBarStateListener;
Aran Inkaa4dfa72019-11-18 16:49:07 -0500171
Lyn Hanb58c7562020-01-07 14:29:20 -0800172 // Callback that updates BubbleOverflowActivity on data change.
173 @Nullable private Runnable mOverflowCallback = null;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800174
Beverly Taid1e175c2020-03-10 16:37:04 +0000175 private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
Mady Mellora54e9fa2019-04-18 13:26:18 -0700176 private IStatusBarService mBarService;
Mady Mellorb4991e62019-01-10 15:14:51 -0800177
Mady Mellord1c78b262018-11-06 18:04:40 -0800178 // Used for determining view rect for touch interaction
179 private Rect mTempRect = new Rect();
180
Mark Renoufc19b4732019-06-26 12:08:33 -0400181 // Listens to user switch so bubbles can be saved and restored.
182 private final NotificationLockscreenUserManager mNotifUserManager;
183
Joshua Tsujif418f9e2019-04-04 17:09:53 -0400184 /** Last known orientation, used to detect orientation changes in {@link #onConfigChanged}. */
185 private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
186
Mady Mellor3df7ab02019-12-09 15:07:10 -0800187 private boolean mInflateSynchronously;
188
Beverlyed8aea22020-01-22 16:52:47 -0500189 // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
190 private final List<NotifCallback> mCallbacks = new ArrayList<>();
191
Mady Mellor5549dd22018-11-06 18:07:34 -0800192 /**
Mady Mellord1c78b262018-11-06 18:04:40 -0800193 * Listener to be notified when some states of the bubbles change.
194 */
195 public interface BubbleStateChangeListener {
196 /**
197 * Called when the stack has bubbles or no longer has bubbles.
198 */
199 void onHasBubblesChanged(boolean hasBubbles);
200 }
201
Mady Mellorcd9b1302018-11-06 18:08:04 -0800202 /**
203 * Listener to find out about stack expansion / collapse events.
204 */
205 public interface BubbleExpandListener {
206 /**
207 * Called when the expansion state of the bubble stack changes.
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700208 *
Mady Mellorcd9b1302018-11-06 18:08:04 -0800209 * @param isExpanding whether it's expanding or collapsing
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800210 * @param key the notification key associated with bubble being expanded
Mady Mellorcd9b1302018-11-06 18:08:04 -0800211 */
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800212 void onBubbleExpandChanged(boolean isExpanding, String key);
213 }
214
215 /**
Mady Mellorf44b6832020-01-14 13:26:14 -0800216 * Listener to be notified when a bubbles' notification suppression state changes.
217 */
218 public interface NotificationSuppressionChangedListener {
219 /**
220 * Called when the notification suppression state of a bubble changes.
221 */
222 void onBubbleNotificationSuppressionChange(Bubble bubble);
Beverlyed8aea22020-01-22 16:52:47 -0500223 }
Mady Mellorf44b6832020-01-14 13:26:14 -0800224
Beverlyed8aea22020-01-22 16:52:47 -0500225 /**
226 * Callback for when the BubbleController wants to interact with the notification pipeline to:
227 * - Remove a previously bubbled notification
228 * - Update the notification shade since bubbled notification should/shouldn't be showing
229 */
230 public interface NotifCallback {
231 /**
Beverlya53fb0d2020-01-29 15:26:13 -0500232 * Called when a bubbled notification that was hidden from the shade is now being removed
233 * This can happen when an app cancels a bubbled notification or when the user dismisses a
234 * bubble.
Beverlyed8aea22020-01-22 16:52:47 -0500235 */
Beverlya53fb0d2020-01-29 15:26:13 -0500236 void removeNotification(NotificationEntry entry, int reason);
Beverlyed8aea22020-01-22 16:52:47 -0500237
238 /**
239 * Called when a bubbled notification has changed whether it should be
240 * filtered from the shade.
241 */
Beverlya53fb0d2020-01-29 15:26:13 -0500242 void invalidateNotifications(String reason);
Beverlyed8aea22020-01-22 16:52:47 -0500243
244 /**
245 * Called on a bubbled entry that has been removed when there are no longer
246 * bubbled entries in its group.
247 *
248 * Checks whether its group has any other (non-bubbled) children. If it doesn't,
249 * removes all remnants of the group's summary from the notification pipeline.
250 * TODO: (b/145659174) Only old pipeline needs this - delete post-migration.
251 */
252 void maybeCancelSummary(NotificationEntry entry);
Mady Mellorf44b6832020-01-14 13:26:14 -0800253 }
254
255 /**
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800256 * Listens for the current state of the status bar and updates the visibility state
257 * of bubbles as needed.
258 */
259 private class StatusBarStateListener implements StatusBarStateController.StateListener {
260 private int mState;
261 /**
262 * Returns the current status bar state.
263 */
264 public int getCurrentState() {
265 return mState;
266 }
267
268 @Override
269 public void onStateChanged(int newState) {
270 mState = newState;
Mark Renouf71a3af62019-04-08 15:02:54 -0400271 boolean shouldCollapse = (mState != SHADE);
272 if (shouldCollapse) {
273 collapseStack();
274 }
Lyn Han6c40fe72019-05-08 14:06:33 -0700275 updateStack();
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800276 }
Mady Mellorcd9b1302018-11-06 18:08:04 -0800277 }
278
Mady Mellor7f234902019-10-20 12:06:29 -0700279 public BubbleController(Context context,
wilsonshihe8321942019-10-18 18:39:46 +0800280 NotificationShadeWindowController notificationShadeWindowController,
Mady Mellor7f234902019-10-20 12:06:29 -0700281 StatusBarStateController statusBarStateController,
Heemin Seogba6337f2019-12-10 15:34:37 -0800282 ShadeController shadeController,
Mady Mellor7f234902019-10-20 12:06:29 -0700283 BubbleData data,
Mady Melloraa8fef22019-04-11 13:36:40 -0700284 ConfigurationController configurationController,
Beverly Taid1e175c2020-03-10 16:37:04 +0000285 NotificationInterruptStateProvider interruptionStateProvider,
Mark Renoufc19b4732019-06-26 12:08:33 -0400286 ZenModeController zenModeController,
Mady Mellor22f2f072019-04-18 13:26:18 -0700287 NotificationLockscreenUserManager notifUserManager,
Mady Mellor7f234902019-10-20 12:06:29 -0700288 NotificationGroupManager groupManager,
Beverlya53fb0d2020-01-29 15:26:13 -0500289 NotificationEntryManager entryManager,
290 NotifPipeline notifPipeline,
291 FeatureFlags featureFlags,
Ned Burnsaaeb44b2020-02-12 23:48:26 -0500292 DumpManager dumpManager,
Joshua Tsuji7155bf12020-02-13 16:14:29 -0500293 FloatingContentCoordinator floatingContentCoordinator) {
wilsonshihe8321942019-10-18 18:39:46 +0800294 this(context, notificationShadeWindowController, statusBarStateController, shadeController,
Mady Mellor7f234902019-10-20 12:06:29 -0700295 data, null /* synchronizer */, configurationController, interruptionStateProvider,
Beverlya53fb0d2020-01-29 15:26:13 -0500296 zenModeController, notifUserManager, groupManager, entryManager,
Ned Burnsaaeb44b2020-02-12 23:48:26 -0500297 notifPipeline, featureFlags, dumpManager, floatingContentCoordinator);
Mady Mellor7f234902019-10-20 12:06:29 -0700298 }
299
Sergey Nikolaienkov5cb6e522020-02-10 17:33:00 +0100300 /**
301 * Injected constructor. See {@link BubbleModule}.
302 */
Mady Mellor7f234902019-10-20 12:06:29 -0700303 public BubbleController(Context context,
wilsonshihe8321942019-10-18 18:39:46 +0800304 NotificationShadeWindowController notificationShadeWindowController,
Mady Mellor7f234902019-10-20 12:06:29 -0700305 StatusBarStateController statusBarStateController,
Heemin Seogba6337f2019-12-10 15:34:37 -0800306 ShadeController shadeController,
Mady Mellor7f234902019-10-20 12:06:29 -0700307 BubbleData data,
308 @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
309 ConfigurationController configurationController,
Beverly Taid1e175c2020-03-10 16:37:04 +0000310 NotificationInterruptStateProvider interruptionStateProvider,
Mady Mellor7f234902019-10-20 12:06:29 -0700311 ZenModeController zenModeController,
312 NotificationLockscreenUserManager notifUserManager,
313 NotificationGroupManager groupManager,
Beverlya53fb0d2020-01-29 15:26:13 -0500314 NotificationEntryManager entryManager,
315 NotifPipeline notifPipeline,
316 FeatureFlags featureFlags,
Ned Burnsaaeb44b2020-02-12 23:48:26 -0500317 DumpManager dumpManager,
Joshua Tsuji7155bf12020-02-13 16:14:29 -0500318 FloatingContentCoordinator floatingContentCoordinator) {
Ned Burnsaaeb44b2020-02-12 23:48:26 -0500319 dumpManager.registerDumpable(TAG, this);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800320 mContext = context;
Heemin Seogba6337f2019-12-10 15:34:37 -0800321 mShadeController = shadeController;
Beverly Taid1e175c2020-03-10 16:37:04 +0000322 mNotificationInterruptStateProvider = interruptionStateProvider;
Mark Renoufc19b4732019-06-26 12:08:33 -0400323 mNotifUserManager = notifUserManager;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400324 mZenModeController = zenModeController;
Joshua Tsuji7155bf12020-02-13 16:14:29 -0500325 mFloatingContentCoordinator = floatingContentCoordinator;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400326 mZenModeController.addCallback(new ZenModeController.Callback() {
327 @Override
328 public void onZenChanged(int zen) {
Mady Mellorb8aaf972019-11-26 10:28:00 -0800329 for (Bubble b : mBubbleData.getBubbles()) {
330 b.setShowDot(b.showInShade(), true /* animate */);
Mady Mellordf48d0a2019-06-25 18:26:46 -0700331 }
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400332 }
333
334 @Override
335 public void onConfigChanged(ZenModeConfig config) {
Mady Mellorb8aaf972019-11-26 10:28:00 -0800336 for (Bubble b : mBubbleData.getBubbles()) {
337 b.setShowDot(b.showInShade(), true /* animate */);
Mady Mellordf48d0a2019-06-25 18:26:46 -0700338 }
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400339 }
340 });
Mady Melloraa8fef22019-04-11 13:36:40 -0700341
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700342 configurationController.addCallback(this /* configurationListener */);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800343
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400344 mBubbleData = data;
345 mBubbleData.setListener(mBubbleDataListener);
Mady Mellorf44b6832020-01-14 13:26:14 -0800346 mBubbleData.setSuppressionChangedListener(new NotificationSuppressionChangedListener() {
347 @Override
348 public void onBubbleNotificationSuppressionChange(Bubble bubble) {
349 // Make sure NoMan knows it's not showing in the shade anymore so anyone querying it
350 // can tell.
351 try {
352 mBarService.onBubbleNotificationSuppressionChanged(bubble.getKey(),
353 !bubble.showInShade());
354 } catch (RemoteException e) {
355 // Bad things have happened
356 }
357 }
358 });
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400359
Mady Mellor7f234902019-10-20 12:06:29 -0700360 mNotificationEntryManager = entryManager;
Mady Mellor22f2f072019-04-18 13:26:18 -0700361 mNotificationGroupManager = groupManager;
Beverlya53fb0d2020-01-29 15:26:13 -0500362 mNotifPipeline = notifPipeline;
363
364 if (!featureFlags.isNewNotifPipelineRenderingEnabled()) {
365 setupNEM();
366 } else {
367 setupNotifPipeline();
368 }
Mady Mellorb4991e62019-01-10 15:14:51 -0800369
wilsonshihe8321942019-10-18 18:39:46 +0800370 mNotificationShadeWindowController = notificationShadeWindowController;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800371 mStatusBarStateListener = new StatusBarStateListener();
Mady Mellor7f234902019-10-20 12:06:29 -0700372 statusBarStateController.addCallback(mStatusBarStateListener);
Mark Renoufcecc77b2019-01-30 16:32:24 -0500373
Mark Renoufcecc77b2019-01-30 16:32:24 -0500374 mTaskStackListener = new BubbleTaskStackListener();
375 ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800376
Joshua Tsujia19515f2019-02-13 18:02:29 -0500377 try {
378 WindowManagerWrapper.getInstance().addPinnedStackListener(new BubblesImeListener());
379 } catch (RemoteException e) {
380 e.printStackTrace();
381 }
Issei Suzukic0387542019-03-08 17:31:14 +0100382 mSurfaceSynchronizer = synchronizer;
Mady Mellora54e9fa2019-04-18 13:26:18 -0700383
384 mBarService = IStatusBarService.Stub.asInterface(
385 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
Mark Renoufc19b4732019-06-26 12:08:33 -0400386
387 mSavedBubbleKeysPerUser = new SparseSetArray<>();
388 mCurrentUserId = mNotifUserManager.getCurrentUserId();
389 mNotifUserManager.addUserChangedListener(
Steve Elliottb47f1c72019-12-19 12:39:26 -0500390 new NotificationLockscreenUserManager.UserChangedListener() {
391 @Override
392 public void onUserChanged(int newUserId) {
393 BubbleController.this.saveBubbles(mCurrentUserId);
394 mBubbleData.dismissAll(DISMISS_USER_CHANGED);
395 BubbleController.this.restoreBubbles(newUserId);
396 mCurrentUserId = newUserId;
397 }
Mark Renoufc19b4732019-06-26 12:08:33 -0400398 });
Mady Mellorff076eb2019-11-13 10:12:06 -0800399
400 mUserCreatedBubbles = new HashSet<>();
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800401 mUserBlockedBubbles = new HashSet<>();
Aran Inkaa4dfa72019-11-18 16:49:07 -0500402
Mady Mellor247ca2c2019-12-02 16:18:59 -0800403 mBubbleIconFactory = new BubbleIconFactory(context);
Mady Mellor5549dd22018-11-06 18:07:34 -0800404 }
405
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400406 /**
Beverlyed8aea22020-01-22 16:52:47 -0500407 * See {@link NotifCallback}.
408 */
409 public void addNotifCallback(NotifCallback callback) {
410 mCallbacks.add(callback);
411 }
412
413 private void setupNEM() {
414 mNotificationEntryManager.addNotificationEntryListener(
415 new NotificationEntryListener() {
416 @Override
Mady Mellorf9439ab2020-01-30 16:06:53 -0800417 public void onPendingEntryAdded(NotificationEntry entry) {
Beverlyed8aea22020-01-22 16:52:47 -0500418 onEntryAdded(entry);
419 }
420
421 @Override
422 public void onPreEntryUpdated(NotificationEntry entry) {
423 onEntryUpdated(entry);
424 }
425
426 @Override
Beverlya53fb0d2020-01-29 15:26:13 -0500427 public void onEntryRemoved(
428 NotificationEntry entry,
429 @android.annotation.Nullable NotificationVisibility visibility,
Julia Reynolds138111f2020-02-26 11:17:39 -0500430 boolean removedByUser,
431 int reason) {
Beverlya53fb0d2020-01-29 15:26:13 -0500432 BubbleController.this.onEntryRemoved(entry);
433 }
434
435 @Override
Beverlyed8aea22020-01-22 16:52:47 -0500436 public void onNotificationRankingUpdated(RankingMap rankingMap) {
437 onRankingUpdated(rankingMap);
438 }
439 });
440
Evan Laird04373662020-01-24 17:37:39 -0500441 mNotificationEntryManager.addNotificationRemoveInterceptor(
Beverlyed8aea22020-01-22 16:52:47 -0500442 new NotificationRemoveInterceptor() {
443 @Override
Evan Laird04373662020-01-24 17:37:39 -0500444 public boolean onNotificationRemoveRequested(
Beverlya53fb0d2020-01-29 15:26:13 -0500445 String key,
446 NotificationEntry entry,
447 int dismissReason) {
448 final boolean isClearAll = dismissReason == REASON_CANCEL_ALL;
449 final boolean isUserDimiss = dismissReason == REASON_CANCEL
450 || dismissReason == REASON_CLICK;
451 final boolean isAppCancel = dismissReason == REASON_APP_CANCEL
452 || dismissReason == REASON_APP_CANCEL_ALL;
453 final boolean isSummaryCancel =
454 dismissReason == REASON_GROUP_SUMMARY_CANCELED;
455
456 // Need to check for !appCancel here because the notification may have
457 // previously been dismissed & entry.isRowDismissed would still be true
458 boolean userRemovedNotif =
459 (entry != null && entry.isRowDismissed() && !isAppCancel)
460 || isClearAll || isUserDimiss || isSummaryCancel;
461
462 if (userRemovedNotif || isUserCreatedBubble(key)
463 || isSummaryOfUserCreatedBubble(entry)) {
464 return handleDismissalInterception(entry);
465 }
466
467 return false;
Beverlyed8aea22020-01-22 16:52:47 -0500468 }
469 });
470
471 mNotificationGroupManager.addOnGroupChangeListener(
472 new NotificationGroupManager.OnGroupChangeListener() {
473 @Override
474 public void onGroupSuppressionChanged(
475 NotificationGroupManager.NotificationGroup group,
476 boolean suppressed) {
477 // More notifications could be added causing summary to no longer
478 // be suppressed -- in this case need to remove the key.
479 final String groupKey = group.summary != null
480 ? group.summary.getSbn().getGroupKey()
481 : null;
482 if (!suppressed && groupKey != null
483 && mBubbleData.isSummarySuppressed(groupKey)) {
484 mBubbleData.removeSuppressedSummary(groupKey);
485 }
486 }
487 });
488
489 addNotifCallback(new NotifCallback() {
490 @Override
Beverlya53fb0d2020-01-29 15:26:13 -0500491 public void removeNotification(NotificationEntry entry, int reason) {
Beverlyed8aea22020-01-22 16:52:47 -0500492 mNotificationEntryManager.performRemoveNotification(entry.getSbn(),
Beverlya53fb0d2020-01-29 15:26:13 -0500493 reason);
Beverlyed8aea22020-01-22 16:52:47 -0500494 }
495
496 @Override
Beverlya53fb0d2020-01-29 15:26:13 -0500497 public void invalidateNotifications(String reason) {
Beverlyed8aea22020-01-22 16:52:47 -0500498 mNotificationEntryManager.updateNotifications(reason);
499 }
500
501 @Override
502 public void maybeCancelSummary(NotificationEntry entry) {
503 // Check if removed bubble has an associated suppressed group summary that needs
504 // to be removed now.
Beverlya53fb0d2020-01-29 15:26:13 -0500505 final String groupKey = entry.getSbn().getGroupKey();
Beverlyed8aea22020-01-22 16:52:47 -0500506 if (mBubbleData.isSummarySuppressed(groupKey)) {
Beverlya53fb0d2020-01-29 15:26:13 -0500507 mBubbleData.removeSuppressedSummary(groupKey);
Beverlyed8aea22020-01-22 16:52:47 -0500508
509 final NotificationEntry summary =
510 mNotificationEntryManager.getActiveNotificationUnfiltered(
511 mBubbleData.getSummaryKey(groupKey));
Beverlya53fb0d2020-01-29 15:26:13 -0500512 if (summary != null) {
513 mNotificationEntryManager.performRemoveNotification(summary.getSbn(),
514 UNDEFINED_DISMISS_REASON);
515 }
Beverlyed8aea22020-01-22 16:52:47 -0500516 }
517
Beverlya53fb0d2020-01-29 15:26:13 -0500518 // Check if we still need to remove the summary from NoManGroup because the summary
519 // may not be in the mBubbleData.mSuppressedGroupKeys list and removed above.
520 // For example:
521 // 1. Bubbled notifications (group) is posted to shade and are visible bubbles
522 // 2. User expands bubbles so now their respective notifications in the shade are
523 // hidden, including the group summary
524 // 3. User removes all bubbles
525 // 4. We expect all the removed bubbles AND the summary (note: the summary was
526 // never added to the suppressedSummary list in BubbleData, so we add this check)
Beverlyed8aea22020-01-22 16:52:47 -0500527 NotificationEntry summary =
528 mNotificationGroupManager.getLogicalGroupSummary(entry.getSbn());
529 if (summary != null) {
530 ArrayList<NotificationEntry> summaryChildren =
531 mNotificationGroupManager.getLogicalChildren(summary.getSbn());
532 boolean isSummaryThisNotif = summary.getKey().equals(entry.getKey());
533 if (!isSummaryThisNotif && (summaryChildren == null
534 || summaryChildren.isEmpty())) {
535 mNotificationEntryManager.performRemoveNotification(summary.getSbn(),
536 UNDEFINED_DISMISS_REASON);
537 }
538 }
539 }
540 });
541 }
542
Beverlya53fb0d2020-01-29 15:26:13 -0500543 private void setupNotifPipeline() {
544 mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
545 @Override
546 public void onEntryAdded(NotificationEntry entry) {
547 BubbleController.this.onEntryAdded(entry);
548 }
549
550 @Override
551 public void onEntryUpdated(NotificationEntry entry) {
552 BubbleController.this.onEntryUpdated(entry);
553 }
554
555 @Override
556 public void onRankingUpdate(RankingMap rankingMap) {
557 onRankingUpdated(rankingMap);
558 }
559
560 @Override
561 public void onEntryRemoved(NotificationEntry entry,
562 @NotifCollection.CancellationReason int reason) {
563 BubbleController.this.onEntryRemoved(entry);
564 }
565 });
566 }
567
Beverlyed8aea22020-01-22 16:52:47 -0500568 /**
Mady Mellor3df7ab02019-12-09 15:07:10 -0800569 * Sets whether to perform inflation on the same thread as the caller. This method should only
570 * be used in tests, not in production.
571 */
572 @VisibleForTesting
573 void setInflateSynchronously(boolean inflateSynchronously) {
574 mInflateSynchronously = inflateSynchronously;
Mady Mellor5549dd22018-11-06 18:07:34 -0800575 }
576
Lyn Hanb58c7562020-01-07 14:29:20 -0800577 void setOverflowCallback(Runnable updateOverflow) {
578 mOverflowCallback = updateOverflow;
579 }
580
581 /**
582 * @return Bubbles for updating overflow.
583 */
584 List<Bubble> getOverflowBubbles() {
585 return mBubbleData.getOverflowBubbles();
586 }
587
588
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400589 /**
590 * BubbleStackView is lazily created by this method the first time a Bubble is added. This
591 * method initializes the stack view and adds it to the StatusBar just above the scrim.
592 */
593 private void ensureStackViewCreated() {
594 if (mStackView == null) {
Joshua Tsuji7155bf12020-02-13 16:14:29 -0500595 mStackView = new BubbleStackView(
596 mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator);
wilsonshihe8321942019-10-18 18:39:46 +0800597 ViewGroup nsv = mNotificationShadeWindowController.getNotificationShadeView();
598 int bubbleScrimIndex = nsv.indexOfChild(nsv.findViewById(R.id.scrim_for_bubble));
Lyn Hanbde48202019-05-29 19:18:29 -0700599 int stackIndex = bubbleScrimIndex + 1; // Show stack above bubble scrim.
wilsonshihe8321942019-10-18 18:39:46 +0800600 nsv.addView(mStackView, stackIndex,
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400601 new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
602 if (mExpandListener != null) {
603 mStackView.setExpandListener(mExpandListener);
604 }
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400605 }
606 }
607
Mark Renoufc19b4732019-06-26 12:08:33 -0400608 /**
609 * Records the notification key for any active bubbles. These are used to restore active
610 * bubbles when the user returns to the foreground.
611 *
612 * @param userId the id of the user
613 */
614 private void saveBubbles(@UserIdInt int userId) {
615 // First clear any existing keys that might be stored.
616 mSavedBubbleKeysPerUser.remove(userId);
617 // Add in all active bubbles for the current user.
618 for (Bubble bubble: mBubbleData.getBubbles()) {
619 mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
620 }
621 }
622
623 /**
624 * Promotes existing notifications to Bubbles if they were previously bubbles.
625 *
626 * @param userId the id of the user
627 */
628 private void restoreBubbles(@UserIdInt int userId) {
Mark Renoufc19b4732019-06-26 12:08:33 -0400629 ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId);
630 if (savedBubbleKeys == null) {
631 // There were no bubbles saved for this used.
632 return;
633 }
Evan Laird181de622019-10-24 09:53:02 -0400634 for (NotificationEntry e :
635 mNotificationEntryManager.getActiveNotificationsForCurrentUser()) {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400636 if (savedBubbleKeys.contains(e.getKey())
Beverly Taid1e175c2020-03-10 16:37:04 +0000637 && mNotificationInterruptStateProvider.shouldBubbleUp(e)
Mark Renoufc19b4732019-06-26 12:08:33 -0400638 && canLaunchInActivityView(mContext, e)) {
639 updateBubble(e, /* suppressFlyout= */ true);
640 }
641 }
642 // Finally, remove the entries for this user now that bubbles are restored.
643 mSavedBubbleKeysPerUser.remove(mCurrentUserId);
644 }
645
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700646 @Override
647 public void onUiModeChanged() {
Mady Mellor247ca2c2019-12-02 16:18:59 -0800648 updateForThemeChanges();
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700649 }
650
651 @Override
652 public void onOverlayChanged() {
Mady Mellor247ca2c2019-12-02 16:18:59 -0800653 updateForThemeChanges();
654 }
655
656 private void updateForThemeChanges() {
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700657 if (mStackView != null) {
Lyn Han02cca812019-04-02 16:27:32 -0700658 mStackView.onThemeChanged();
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700659 }
Mady Mellor3df7ab02019-12-09 15:07:10 -0800660 mBubbleIconFactory = new BubbleIconFactory(mContext);
661 for (Bubble b: mBubbleData.getBubbles()) {
662 // Reload each bubble
663 b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory);
664 }
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700665 }
666
Joshua Tsujif418f9e2019-04-04 17:09:53 -0400667 @Override
668 public void onConfigChanged(Configuration newConfig) {
669 if (mStackView != null && newConfig != null && newConfig.orientation != mOrientation) {
Joshua Tsujif418f9e2019-04-04 17:09:53 -0400670 mOrientation = newConfig.orientation;
Lyn Hanf4730312019-06-18 11:18:58 -0700671 mStackView.onOrientationChanged(newConfig.orientation);
Joshua Tsujif418f9e2019-04-04 17:09:53 -0400672 }
673 }
674
Mady Mellor5549dd22018-11-06 18:07:34 -0800675 /**
Mady Mellord1c78b262018-11-06 18:04:40 -0800676 * Set a listener to be notified when some states of the bubbles change.
677 */
678 public void setBubbleStateChangeListener(BubbleStateChangeListener listener) {
679 mStateChangeListener = listener;
680 }
681
682 /**
Mady Mellorcd9b1302018-11-06 18:08:04 -0800683 * Set a listener to be notified of bubble expand events.
684 */
685 public void setExpandListener(BubbleExpandListener listener) {
Issei Suzukiac9fcb72019-02-04 17:45:57 +0100686 mExpandListener = ((isExpanding, key) -> {
687 if (listener != null) {
688 listener.onBubbleExpandChanged(isExpanding, key);
689 }
wilsonshihe8321942019-10-18 18:39:46 +0800690 mNotificationShadeWindowController.setBubbleExpanded(isExpanding);
Issei Suzukiac9fcb72019-02-04 17:45:57 +0100691 });
Mady Mellorcd9b1302018-11-06 18:08:04 -0800692 if (mStackView != null) {
693 mStackView.setExpandListener(mExpandListener);
694 }
695 }
696
697 /**
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800698 * Whether or not there are bubbles present, regardless of them being visible on the
699 * screen (e.g. if on AOD).
700 */
701 public boolean hasBubbles() {
Mady Mellor3dff9e62019-02-05 18:12:53 -0800702 if (mStackView == null) {
703 return false;
704 }
Mark Renouf71a3af62019-04-08 15:02:54 -0400705 return mBubbleData.hasBubbles();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800706 }
707
708 /**
709 * Whether the stack of bubbles is expanded or not.
710 */
711 public boolean isStackExpanded() {
Mark Renouf71a3af62019-04-08 15:02:54 -0400712 return mBubbleData.isExpanded();
713 }
714
715 /**
716 * Tell the stack of bubbles to expand.
717 */
718 public void expandStack() {
719 mBubbleData.setExpanded(true);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800720 }
721
722 /**
723 * Tell the stack of bubbles to collapse.
724 */
725 public void collapseStack() {
Mark Renouf71a3af62019-04-08 15:02:54 -0400726 mBubbleData.setExpanded(false /* expanded */);
727 }
728
Mady Mellorce23c462019-06-17 17:30:07 -0700729 /**
Mady Mellore28fe102019-07-09 15:33:32 -0700730 * True if either:
731 * (1) There is a bubble associated with the provided key and if its notification is hidden
732 * from the shade.
733 * (2) There is a group summary associated with the provided key that is hidden from the shade
734 * because it has been dismissed but still has child bubbles active.
Mady Mellorce23c462019-06-17 17:30:07 -0700735 *
Mady Mellore28fe102019-07-09 15:33:32 -0700736 * False otherwise.
Mady Mellorce23c462019-06-17 17:30:07 -0700737 */
Beverlyed8aea22020-01-22 16:52:47 -0500738 public boolean isBubbleNotificationSuppressedFromShade(NotificationEntry entry) {
739 String key = entry.getKey();
Mady Mellore28fe102019-07-09 15:33:32 -0700740 boolean isBubbleAndSuppressed = mBubbleData.hasBubbleWithKey(key)
Mady Mellorb8aaf972019-11-26 10:28:00 -0800741 && !mBubbleData.getBubbleWithKey(key).showInShade();
Beverlyed8aea22020-01-22 16:52:47 -0500742
743 String groupKey = entry.getSbn().getGroupKey();
Mady Mellore28fe102019-07-09 15:33:32 -0700744 boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey);
Mady Mellore4348272019-07-29 17:43:36 -0700745 boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey));
Beverlyed8aea22020-01-22 16:52:47 -0500746
Mady Mellore4348272019-07-29 17:43:36 -0700747 return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed;
Mady Mellorce23c462019-06-17 17:30:07 -0700748 }
749
Mark Renouf71a3af62019-04-08 15:02:54 -0400750 @VisibleForTesting
751 void selectBubble(String key) {
752 Bubble bubble = mBubbleData.getBubbleWithKey(key);
Mady Mellor247ca2c2019-12-02 16:18:59 -0800753 mBubbleData.setSelectedBubble(bubble);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800754 }
755
Lyn Hanb58c7562020-01-07 14:29:20 -0800756 void promoteBubbleFromOverflow(Bubble bubble) {
Lyn Han1e19d7f2020-02-05 19:10:58 -0800757 bubble.setInflateSynchronously(mInflateSynchronously);
758 mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
Lyn Hanb58c7562020-01-07 14:29:20 -0800759 }
760
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800761 /**
Mark Renouffec45da2019-03-13 13:24:27 -0400762 * Request the stack expand if needed, then select the specified Bubble as current.
763 *
764 * @param notificationKey the notification key for the bubble to be selected
765 */
766 public void expandStackAndSelectBubble(String notificationKey) {
Mark Renouf71a3af62019-04-08 15:02:54 -0400767 Bubble bubble = mBubbleData.getBubbleWithKey(notificationKey);
768 if (bubble != null) {
769 mBubbleData.setSelectedBubble(bubble);
770 mBubbleData.setExpanded(true);
Mark Renouffec45da2019-03-13 13:24:27 -0400771 }
772 }
773
774 /**
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800775 * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
776 */
Mark Renouf08bc42a2019-03-07 13:01:59 -0500777 void dismissStack(@DismissReason int reason) {
Mark Renouf71a3af62019-04-08 15:02:54 -0400778 mBubbleData.dismissAll(reason);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800779 }
780
781 /**
Mark Renouf041d7262019-02-06 12:09:41 -0500782 * Directs a back gesture at the bubble stack. When opened, the current expanded bubble
783 * is forwarded a back key down/up pair.
784 */
785 public void performBackPressIfNeeded() {
786 if (mStackView != null) {
787 mStackView.performBackPressIfNeeded();
788 }
789 }
790
791 /**
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800792 * Adds or updates a bubble associated with the provided notification entry.
793 *
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400794 * @param notif the notification associated with this bubble.
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800795 */
Mark Renouff97ed462019-04-05 13:46:24 -0400796 void updateBubble(NotificationEntry notif) {
Mady Mellor7f234902019-10-20 12:06:29 -0700797 updateBubble(notif, false /* suppressFlyout */);
Mark Renoufc19b4732019-06-26 12:08:33 -0400798 }
799
800 void updateBubble(NotificationEntry notif, boolean suppressFlyout) {
Mady Mellor7f234902019-10-20 12:06:29 -0700801 updateBubble(notif, suppressFlyout, true /* showInShade */);
802 }
803
804 void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {
Mady Mellor3df7ab02019-12-09 15:07:10 -0800805 if (mStackView == null) {
806 // Lazy init stack view when a bubble is created
807 ensureStackViewCreated();
808 }
Mady Mellor66efd5e2019-05-15 13:38:11 -0700809 // If this is an interruptive notif, mark that it's interrupted
Ned Burns60e94592019-09-06 14:47:25 -0400810 if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
Mady Mellor66efd5e2019-05-15 13:38:11 -0700811 notif.setInterruption();
812 }
Mady Mellor3df7ab02019-12-09 15:07:10 -0800813 Bubble bubble = mBubbleData.getOrCreateBubble(notif);
814 bubble.setInflateSynchronously(mInflateSynchronously);
815 bubble.inflate(
816 b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
817 mContext, mStackView, mBubbleIconFactory);
Mady Mellor7f234902019-10-20 12:06:29 -0700818 }
819
820 /**
821 * Called when a user has indicated that an active notification should be shown as a bubble.
822 * <p>
823 * This method will collapse the shade, create the bubble without a flyout or dot, and suppress
824 * the notification from appearing in the shade.
825 *
826 * @param entry the notification to show as a bubble.
827 */
828 public void onUserCreatedBubbleFromNotification(NotificationEntry entry) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800829 if (DEBUG_EXPERIMENTS || DEBUG_BUBBLE_CONTROLLER) {
830 Log.d(TAG, "onUserCreatedBubble: " + entry.getKey());
831 }
Heemin Seogba6337f2019-12-10 15:34:37 -0800832 mShadeController.collapsePanel(true);
Mady Mellor7f234902019-10-20 12:06:29 -0700833 entry.setFlagBubble(true);
834 updateBubble(entry, true /* suppressFlyout */, false /* showInShade */);
Mady Mellorff076eb2019-11-13 10:12:06 -0800835 mUserCreatedBubbles.add(entry.getKey());
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800836 mUserBlockedBubbles.remove(entry.getKey());
Mady Mellor7f234902019-10-20 12:06:29 -0700837 }
838
839 /**
840 * Called when a user has indicated that an active notification appearing as a bubble should
841 * no longer be shown as a bubble.
842 *
843 * @param entry the notification to no longer show as a bubble.
844 */
845 public void onUserDemotedBubbleFromNotification(NotificationEntry entry) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800846 if (DEBUG_EXPERIMENTS || DEBUG_BUBBLE_CONTROLLER) {
847 Log.d(TAG, "onUserDemotedBubble: " + entry.getKey());
848 }
Mady Mellor7f234902019-10-20 12:06:29 -0700849 entry.setFlagBubble(false);
Beverlya53fb0d2020-01-29 15:26:13 -0500850 removeBubble(entry, DISMISS_BLOCKED);
Mady Mellorff076eb2019-11-13 10:12:06 -0800851 mUserCreatedBubbles.remove(entry.getKey());
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800852 if (BubbleExperimentConfig.isPackageWhitelistedToAutoBubble(
853 mContext, entry.getSbn().getPackageName())) {
854 // This package is whitelist but user demoted the bubble, let's save it so we don't
855 // auto-bubble for the whitelist again.
856 mUserBlockedBubbles.add(entry.getKey());
857 }
Mady Mellorff076eb2019-11-13 10:12:06 -0800858 }
859
860 /**
861 * Whether this bubble was explicitly created by the user via a SysUI affordance.
862 */
863 boolean isUserCreatedBubble(String key) {
864 return mUserCreatedBubbles.contains(key);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800865 }
866
Beverlya53fb0d2020-01-29 15:26:13 -0500867 boolean isSummaryOfUserCreatedBubble(NotificationEntry entry) {
868 if (isSummaryOfBubbles(entry)) {
869 List<Bubble> bubbleChildren =
870 mBubbleData.getBubblesInGroup(entry.getSbn().getGroupKey());
871 for (int i = 0; i < bubbleChildren.size(); i++) {
872 // Check if any are user-created (i.e. experimental bubbles)
873 if (isUserCreatedBubble(bubbleChildren.get(i).getKey())) {
874 return true;
875 }
876 }
877 }
878 return false;
879 }
880
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800881 /**
Beverlya53fb0d2020-01-29 15:26:13 -0500882 * Removes the bubble with the given NotificationEntry.
Mark Renouf658c6bc2019-01-30 10:26:54 -0500883 * <p>
884 * Must be called from the main thread.
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800885 */
Mark Renouf658c6bc2019-01-30 10:26:54 -0500886 @MainThread
Beverlya53fb0d2020-01-29 15:26:13 -0500887 void removeBubble(NotificationEntry entry, int reason) {
888 if (mBubbleData.hasBubbleWithKey(entry.getKey())) {
889 mBubbleData.notificationEntryRemoved(entry, reason);
Mady Mellore8e07712019-01-23 12:45:33 -0800890 }
891 }
892
Beverlyed8aea22020-01-22 16:52:47 -0500893 private void onEntryAdded(NotificationEntry entry) {
894 boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
895 boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
896 boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
897 mContext, entry, previouslyUserCreated, userBlocked);
Mady Mellor22f2f072019-04-18 13:26:18 -0700898
Beverly Taid1e175c2020-03-10 16:37:04 +0000899 if (mNotificationInterruptStateProvider.shouldBubbleUp(entry)
Beverlyed8aea22020-01-22 16:52:47 -0500900 && (canLaunchInActivityView(mContext, entry) || wasAdjusted)) {
901 if (wasAdjusted && !previouslyUserCreated) {
902 // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated
903 mUserCreatedBubbles.add(entry.getKey());
Mady Mellorc2ff0112019-03-28 14:18:06 -0700904 }
Beverlyed8aea22020-01-22 16:52:47 -0500905 updateBubble(entry);
Mady Mellor22f2f072019-04-18 13:26:18 -0700906 }
907 }
908
Beverlyed8aea22020-01-22 16:52:47 -0500909 private void onEntryUpdated(NotificationEntry entry) {
910 boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
911 boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
912 boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
913 mContext, entry, previouslyUserCreated, userBlocked);
Mady Mellor7f234902019-10-20 12:06:29 -0700914
Beverly Taid1e175c2020-03-10 16:37:04 +0000915 boolean shouldBubble = mNotificationInterruptStateProvider.shouldBubbleUp(entry)
Beverlyed8aea22020-01-22 16:52:47 -0500916 && (canLaunchInActivityView(mContext, entry) || wasAdjusted);
917 if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) {
918 // It was previously a bubble but no longer a bubble -- lets remove it
Beverlya53fb0d2020-01-29 15:26:13 -0500919 removeBubble(entry, DISMISS_NO_LONGER_BUBBLE);
Beverlyed8aea22020-01-22 16:52:47 -0500920 } else if (shouldBubble) {
921 if (wasAdjusted && !previouslyUserCreated) {
922 // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated
923 mUserCreatedBubbles.add(entry.getKey());
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800924 }
Beverlyed8aea22020-01-22 16:52:47 -0500925 updateBubble(entry);
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800926 }
Beverlyed8aea22020-01-22 16:52:47 -0500927 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800928
Beverlya53fb0d2020-01-29 15:26:13 -0500929 private void onEntryRemoved(NotificationEntry entry) {
930 if (isSummaryOfBubbles(entry)) {
931 final String groupKey = entry.getSbn().getGroupKey();
932 mBubbleData.removeSuppressedSummary(groupKey);
933
934 // Remove any associated bubble children with the summary
935 final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
936 for (int i = 0; i < bubbleChildren.size(); i++) {
937 removeBubble(bubbleChildren.get(i).getEntry(), DISMISS_GROUP_CANCELLED);
938 }
939 } else {
940 removeBubble(entry, DISMISS_NOTIF_CANCEL);
941 }
942 }
943
Mady Mellor56515c42020-02-18 17:58:36 -0800944 /**
945 * Called when NotificationListener has received adjusted notification rank and reapplied
946 * filtering and sorting. This is used to dismiss or create bubbles based on changes in
947 * permissions on the notification channel or the global setting.
948 *
949 * @param rankingMap the updated ranking map from NotificationListenerService
950 */
Beverlyed8aea22020-01-22 16:52:47 -0500951 private void onRankingUpdated(RankingMap rankingMap) {
Mady Mellor56515c42020-02-18 17:58:36 -0800952 if (mTmpRanking == null) {
953 mTmpRanking = new NotificationListenerService.Ranking();
954 }
955 String[] orderedKeys = rankingMap.getOrderedKeys();
956 for (int i = 0; i < orderedKeys.length; i++) {
957 String key = orderedKeys[i];
958 NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key);
959 rankingMap.getRanking(key, mTmpRanking);
960 if (mBubbleData.hasBubbleWithKey(key) && !mTmpRanking.canBubble()) {
961 mBubbleData.notificationEntryRemoved(entry, BubbleController.DISMISS_BLOCKED);
962 } else if (entry != null && mTmpRanking.isBubble()) {
963 entry.setFlagBubble(true);
964 onEntryUpdated(entry);
965 }
966 }
Beverlyed8aea22020-01-22 16:52:47 -0500967 }
Ned Burns01e38212019-01-03 16:32:52 -0500968
Mark Renouf71a3af62019-04-08 15:02:54 -0400969 @SuppressWarnings("FieldCanBeLocal")
Mark Renouf3bc5b362019-04-05 14:37:59 -0400970 private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {
Mark Renouf71a3af62019-04-08 15:02:54 -0400971
Mark Renouf3bc5b362019-04-05 14:37:59 -0400972 @Override
Mark Renouf82a40e62019-05-23 16:16:24 -0400973 public void applyUpdate(BubbleData.Update update) {
Lyn Hanb58c7562020-01-07 14:29:20 -0800974 // Update bubbles in overflow.
975 if (mOverflowCallback != null) {
976 mOverflowCallback.run();
977 }
978
Mark Renouf82a40e62019-05-23 16:16:24 -0400979 // Collapsing? Do this first before remaining steps.
980 if (update.expandedChanged && !update.expanded) {
981 mStackView.setExpanded(false);
982 }
983
984 // Do removals, if any.
Mady Mellor22f2f072019-04-18 13:26:18 -0700985 ArrayList<Pair<Bubble, Integer>> removedBubbles =
986 new ArrayList<>(update.removedBubbles);
987 for (Pair<Bubble, Integer> removed : removedBubbles) {
Mark Renouf82a40e62019-05-23 16:16:24 -0400988 final Bubble bubble = removed.first;
989 @DismissReason final int reason = removed.second;
990 mStackView.removeBubble(bubble);
Mark Renoufc19b4732019-06-26 12:08:33 -0400991 // If the bubble is removed for user switching, leave the notification in place.
992 if (reason != DISMISS_USER_CHANGED) {
993 if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
Mady Mellorb8aaf972019-11-26 10:28:00 -0800994 && !bubble.showInShade()) {
Beverlyed8aea22020-01-22 16:52:47 -0500995 // The bubble is now gone & the notification is hidden from the shade, so
996 // time to actually remove it
997 for (NotifCallback cb : mCallbacks) {
Beverlya53fb0d2020-01-29 15:26:13 -0500998 cb.removeNotification(bubble.getEntry(), REASON_CANCEL);
Beverlyed8aea22020-01-22 16:52:47 -0500999 }
Mark Renoufc19b4732019-06-26 12:08:33 -04001000 } else {
1001 // Update the flag for SysUI
Ned Burns00b4b2d2019-10-17 22:09:27 -04001002 bubble.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
Mady Mellor3a0a1b42019-05-23 06:40:21 -07001003
Mark Renoufc19b4732019-06-26 12:08:33 -04001004 // Make sure NoMan knows it's not a bubble anymore so anyone querying it
1005 // will get right result back
1006 try {
1007 mBarService.onNotificationBubbleChanged(bubble.getKey(),
1008 false /* isBubble */);
1009 } catch (RemoteException e) {
1010 // Bad things have happened
1011 }
Mark Renouf82a40e62019-05-23 16:16:24 -04001012 }
Mady Mellor22f2f072019-04-18 13:26:18 -07001013
Ned Burns00b4b2d2019-10-17 22:09:27 -04001014 final String groupKey = bubble.getEntry().getSbn().getGroupKey();
Beverlyed8aea22020-01-22 16:52:47 -05001015 if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
1016 // Time to potentially remove the summary
1017 for (NotifCallback cb : mCallbacks) {
1018 cb.maybeCancelSummary(bubble.getEntry());
Mady Mellor22f2f072019-04-18 13:26:18 -07001019 }
1020 }
Mady Mellora54e9fa2019-04-18 13:26:18 -07001021 }
1022 }
Mark Renouf3bc5b362019-04-05 14:37:59 -04001023
Lyn Hanc47e1712020-01-28 21:43:34 -08001024 if (update.addedBubble != null) {
1025 mStackView.addBubble(update.addedBubble);
1026 }
1027
Mark Renouf82a40e62019-05-23 16:16:24 -04001028 if (update.updatedBubble != null) {
1029 mStackView.updateBubble(update.updatedBubble);
Mark Renouf71a3af62019-04-08 15:02:54 -04001030 }
Mark Renouf3bc5b362019-04-05 14:37:59 -04001031
Lyn Hanb58c7562020-01-07 14:29:20 -08001032 // At this point, the correct bubbles are inflated in the stack.
1033 // Make sure the order in bubble data is reflected in bubble row.
Mark Renouf82a40e62019-05-23 16:16:24 -04001034 if (update.orderChanged) {
1035 mStackView.updateBubbleOrder(update.bubbles);
Mark Renoufba5ab512019-05-02 15:21:01 -04001036 }
Mark Renouf3bc5b362019-04-05 14:37:59 -04001037
Mark Renouf82a40e62019-05-23 16:16:24 -04001038 if (update.selectionChanged) {
1039 mStackView.setSelectedBubble(update.selectedBubble);
Mady Mellor740d85d2019-07-09 15:26:47 -07001040 if (update.selectedBubble != null) {
1041 mNotificationGroupManager.updateSuppression(
1042 update.selectedBubble.getEntry());
1043 }
Mark Renouf71a3af62019-04-08 15:02:54 -04001044 }
Mark Renouf3bc5b362019-04-05 14:37:59 -04001045
Mark Renouf82a40e62019-05-23 16:16:24 -04001046 // Expanding? Apply this last.
1047 if (update.expandedChanged && update.expanded) {
1048 mStackView.setExpanded(true);
Mark Renouf71a3af62019-04-08 15:02:54 -04001049 }
Mark Renouf3bc5b362019-04-05 14:37:59 -04001050
Beverlyed8aea22020-01-22 16:52:47 -05001051 for (NotifCallback cb : mCallbacks) {
Beverlya53fb0d2020-01-29 15:26:13 -05001052 cb.invalidateNotifications("BubbleData.Listener.applyUpdate");
Beverlyed8aea22020-01-22 16:52:47 -05001053 }
Lyn Han6c40fe72019-05-08 14:06:33 -07001054 updateStack();
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001055
Issei Suzukia8d07312019-06-07 12:56:19 +02001056 if (DEBUG_BUBBLE_CONTROLLER) {
Lyn Hanb58c7562020-01-07 14:29:20 -08001057 Log.d(TAG, "\n[BubbleData] bubbles:");
Lyn Han767d70e2019-12-10 18:02:23 -08001058 Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getBubbles(),
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001059 mBubbleData.getSelectedBubble()));
1060
1061 if (mStackView != null) {
Lyn Hanb58c7562020-01-07 14:29:20 -08001062 Log.d(TAG, "\n[BubbleStackView]");
Lyn Han767d70e2019-12-10 18:02:23 -08001063 Log.d(TAG, BubbleDebugConfig.formatBubblesString(mStackView.getBubblesOnScreen(),
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001064 mStackView.getExpandedBubble()));
1065 }
Lyn Hanb58c7562020-01-07 14:29:20 -08001066 Log.d(TAG, "\n[BubbleData] overflow:");
1067 Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getOverflowBubbles(),
1068 null));
Mark Renouf9ba6cea2019-04-17 11:53:50 -04001069 }
Mark Renouf3bc5b362019-04-05 14:37:59 -04001070 }
1071 };
1072
Mady Mellor3f2efdb2018-11-21 11:30:45 -08001073 /**
Beverlya53fb0d2020-01-29 15:26:13 -05001074 * We intercept notification entries (including group summaries) dismissed by the user when
1075 * there is an active bubble associated with it. We do this so that developers can still
1076 * cancel it (and hence the bubbles associated with it). However, these intercepted
1077 * notifications should then be hidden from the shade since the user has cancelled them, so we
1078 * {@link Bubble#setSuppressNotification}. For the case of suppressed summaries, we also add
1079 * {@link BubbleData#addSummaryToSuppress}.
Beverlyed8aea22020-01-22 16:52:47 -05001080 *
Mady Mellor91b31e62020-01-30 17:40:48 -08001081 * @return true if we want to intercept the dismissal of the entry, else false.
Beverlyed8aea22020-01-22 16:52:47 -05001082 */
Beverlya53fb0d2020-01-29 15:26:13 -05001083 public boolean handleDismissalInterception(NotificationEntry entry) {
Beverlyed8aea22020-01-22 16:52:47 -05001084 if (entry == null) {
1085 return false;
1086 }
Beverlyed8aea22020-01-22 16:52:47 -05001087
Beverlya53fb0d2020-01-29 15:26:13 -05001088 final boolean interceptBubbleDismissal = mBubbleData.hasBubbleWithKey(entry.getKey())
1089 && entry.isBubble();
1090 final boolean interceptSummaryDismissal = isSummaryOfBubbles(entry);
Beverlyed8aea22020-01-22 16:52:47 -05001091
Beverlya53fb0d2020-01-29 15:26:13 -05001092 if (interceptSummaryDismissal) {
1093 handleSummaryDismissalInterception(entry);
1094 } else if (interceptBubbleDismissal) {
1095 Bubble bubble = mBubbleData.getBubbleWithKey(entry.getKey());
Beverlyed8aea22020-01-22 16:52:47 -05001096 bubble.setSuppressNotification(true);
1097 bubble.setShowDot(false /* show */, true /* animate */);
Beverlya53fb0d2020-01-29 15:26:13 -05001098 } else {
Beverlyed8aea22020-01-22 16:52:47 -05001099 return false;
1100 }
Beverlya53fb0d2020-01-29 15:26:13 -05001101
1102 // Update the shade
1103 for (NotifCallback cb : mCallbacks) {
1104 cb.invalidateNotifications("BubbleController.handleDismissalInterception");
1105 }
1106 return true;
Beverlyed8aea22020-01-22 16:52:47 -05001107 }
1108
Beverlya53fb0d2020-01-29 15:26:13 -05001109 private boolean isSummaryOfBubbles(NotificationEntry entry) {
1110 if (entry == null) {
Beverlyed8aea22020-01-22 16:52:47 -05001111 return false;
1112 }
Beverlya53fb0d2020-01-29 15:26:13 -05001113
1114 String groupKey = entry.getSbn().getGroupKey();
1115 ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
1116 boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
1117 && mBubbleData.getSummaryKey(groupKey).equals(entry.getKey()));
1118 boolean isSummary = entry.getSbn().getNotification().isGroupSummary();
1119 return (isSuppressedSummary || isSummary)
1120 && bubbleChildren != null
1121 && !bubbleChildren.isEmpty();
1122 }
1123
1124 private void handleSummaryDismissalInterception(NotificationEntry summary) {
1125 // current children in the row:
1126 final List<NotificationEntry> children = summary.getChildren();
1127 if (children != null) {
1128 for (int i = 0; i < children.size(); i++) {
1129 NotificationEntry child = children.get(i);
1130 if (mBubbleData.hasBubbleWithKey(child.getKey())) {
1131 // Suppress the bubbled child
1132 // As far as group manager is concerned, once a child is no longer shown
1133 // in the shade, it is essentially removed.
1134 Bubble bubbleChild = mBubbleData.getBubbleWithKey(child.getKey());
1135 mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
1136 bubbleChild.setSuppressNotification(true);
1137 bubbleChild.setShowDot(false /* show */, true /* animate */);
1138 } else {
1139 // non-bubbled children can be removed
1140 for (NotifCallback cb : mCallbacks) {
1141 cb.removeNotification(child, REASON_GROUP_SUMMARY_CANCELED);
1142 }
1143 }
1144 }
1145 }
1146
1147 // And since all children are removed, remove the summary.
1148 mNotificationGroupManager.onEntryRemoved(summary);
1149
1150 // TODO: (b/145659174) remove references to mSuppressedGroupKeys once fully migrated
1151 mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(),
1152 summary.getKey());
Beverlyed8aea22020-01-22 16:52:47 -05001153 }
1154
1155 /**
Joshua Tsujidd4d9f92019-05-13 13:57:38 -04001156 * Lets any listeners know if bubble state has changed.
Lyn Han6c40fe72019-05-08 14:06:33 -07001157 * Updates the visibility of the bubbles based on current state.
1158 * Does not un-bubble, just hides or un-hides. Notifies any
1159 * {@link BubbleStateChangeListener}s of visibility changes.
1160 * Updates stack description for TalkBack focus.
Mady Mellor3f2efdb2018-11-21 11:30:45 -08001161 */
Lyn Han6c40fe72019-05-08 14:06:33 -07001162 public void updateStack() {
Mady Mellor3f2efdb2018-11-21 11:30:45 -08001163 if (mStackView == null) {
1164 return;
Mady Mellord1c78b262018-11-06 18:04:40 -08001165 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -08001166 if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) {
1167 // Bubbles only appear in unlocked shade
1168 mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE);
Mady Mellor698d9e82019-08-01 23:11:53 +00001169 } else if (mStackView != null) {
Mady Mellor3f2efdb2018-11-21 11:30:45 -08001170 mStackView.setVisibility(INVISIBLE);
Mady Mellor5549dd22018-11-06 18:07:34 -08001171 }
Lyn Han6c40fe72019-05-08 14:06:33 -07001172
Mady Mellor698d9e82019-08-01 23:11:53 +00001173 // Let listeners know if bubble state changed.
wilsonshihe8321942019-10-18 18:39:46 +08001174 boolean hadBubbles = mNotificationShadeWindowController.getBubblesShowing();
Mady Mellor698d9e82019-08-01 23:11:53 +00001175 boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE;
wilsonshihe8321942019-10-18 18:39:46 +08001176 mNotificationShadeWindowController.setBubblesShowing(hasBubblesShowing);
Lyn Han6c40fe72019-05-08 14:06:33 -07001177 if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
1178 mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
1179 }
1180
1181 mStackView.updateContentDescription();
Mady Mellord1c78b262018-11-06 18:04:40 -08001182 }
1183
1184 /**
1185 * Rect indicating the touchable region for the bubble stack / expanded stack.
1186 */
1187 public Rect getTouchableRegion() {
1188 if (mStackView == null || mStackView.getVisibility() != VISIBLE) {
1189 return null;
1190 }
1191 mStackView.getBoundsOnScreen(mTempRect);
1192 return mTempRect;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -08001193 }
1194
Mady Mellor390bff42019-04-05 15:09:01 -07001195 /**
1196 * The display id of the expanded view, if the stack is expanded and not occluded by the
1197 * status bar, otherwise returns {@link Display#INVALID_DISPLAY}.
1198 */
1199 public int getExpandedDisplayId(Context context) {
Joel Galenson4071ddb2019-04-18 13:30:45 -07001200 if (mStackView == null) {
Lyn Han9f66c3b2020-03-05 23:59:29 -08001201 return INVALID_DISPLAY;
Joel Galenson4071ddb2019-04-18 13:30:45 -07001202 }
Issei Suzukicac2a502019-04-16 16:52:50 +02001203 final boolean defaultDisplay = context.getDisplay() != null
Mady Mellor390bff42019-04-05 15:09:01 -07001204 && context.getDisplay().getDisplayId() == DEFAULT_DISPLAY;
Lyn Han9f66c3b2020-03-05 23:59:29 -08001205 final BubbleViewProvider expandedViewProvider = mStackView.getExpandedBubble();
1206 if (defaultDisplay && expandedViewProvider != null && isStackExpanded()
wilsonshihe8321942019-10-18 18:39:46 +08001207 && !mNotificationShadeWindowController.getPanelExpanded()) {
Lyn Han9f66c3b2020-03-05 23:59:29 -08001208 return expandedViewProvider.getDisplayId();
Mady Mellor390bff42019-04-05 15:09:01 -07001209 }
Lyn Han9f66c3b2020-03-05 23:59:29 -08001210 return INVALID_DISPLAY;
Mady Mellor390bff42019-04-05 15:09:01 -07001211 }
1212
Mady Mellorf6e3ac02019-01-29 10:37:52 -08001213 @VisibleForTesting
1214 BubbleStackView getStackView() {
1215 return mStackView;
1216 }
1217
Mady Mellor70cba7bb2019-07-02 15:06:07 -07001218 /**
1219 * Description of current bubble state.
1220 */
1221 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1222 pw.println("BubbleController state:");
1223 mBubbleData.dump(fd, pw, args);
1224 pw.println();
Joshua Tsuji395bcfe2019-07-02 19:23:23 -04001225 if (mStackView != null) {
1226 mStackView.dump(fd, pw, args);
1227 }
1228 pw.println();
Mady Mellor70cba7bb2019-07-02 15:06:07 -07001229 }
1230
Mady Mellore80930e2019-03-21 16:00:45 -07001231 /**
Mark Renoufcecc77b2019-01-30 16:32:24 -05001232 * This task stack listener is responsible for responding to tasks moved to the front
1233 * which are on the default (main) display. When this happens, expanded bubbles must be
1234 * collapsed so the user may interact with the app which was just moved to the front.
1235 * <p>
1236 * This listener is registered with SystemUI's ActivityManagerWrapper which dispatches
1237 * these calls via a main thread Handler.
1238 */
1239 @MainThread
1240 private class BubbleTaskStackListener extends TaskStackChangeListener {
1241
Mark Renoufcecc77b2019-01-30 16:32:24 -05001242 @Override
Mark Renoufc808f062019-02-07 15:20:37 -05001243 public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
1244 if (mStackView != null && taskInfo.displayId == Display.DEFAULT_DISPLAY) {
Mady Mellor047e24e2019-08-05 11:35:40 -07001245 if (!mStackView.isExpansionAnimating()) {
1246 mBubbleData.setExpanded(false);
1247 }
Mark Renoufcecc77b2019-01-30 16:32:24 -05001248 }
1249 }
1250
Mark Renoufcecc77b2019-01-30 16:32:24 -05001251 @Override
Winson Chunge789ff62020-02-24 14:40:23 -08001252 public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
1253 boolean clearedTask) {
1254 for (Bubble b : mBubbleData.getBubbles()) {
1255 if (b.getDisplayId() == task.displayId) {
1256 expandStackAndSelectBubble(b.getKey());
1257 return;
1258 }
1259 }
1260 }
1261
1262 @Override
Mark Renoufa12e8762019-03-07 15:43:01 -05001263 public void onActivityLaunchOnSecondaryDisplayRerouted() {
Mark Renoufcecc77b2019-01-30 16:32:24 -05001264 if (mStackView != null) {
Mark Renouf71a3af62019-04-08 15:02:54 -04001265 mBubbleData.setExpanded(false);
Mark Renoufcecc77b2019-01-30 16:32:24 -05001266 }
1267 }
Mark Renouf446251d2019-04-26 10:22:41 -04001268
1269 @Override
1270 public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
1271 if (mStackView != null && taskInfo.displayId == getExpandedDisplayId(mContext)) {
1272 mBubbleData.setExpanded(false);
1273 }
1274 }
Issei Suzukicac2a502019-04-16 16:52:50 +02001275
1276 @Override
1277 public void onSingleTaskDisplayDrawn(int displayId) {
Lyn Hana0bb02e2020-01-28 17:57:27 -08001278 if (mStackView == null) {
1279 return;
Issei Suzukicac2a502019-04-16 16:52:50 +02001280 }
Lyn Hana0bb02e2020-01-28 17:57:27 -08001281 mStackView.showExpandedViewContents(displayId);
Issei Suzukicac2a502019-04-16 16:52:50 +02001282 }
Issei Suzuki734bc942019-06-05 13:59:52 +02001283
1284 @Override
1285 public void onSingleTaskDisplayEmpty(int displayId) {
Lyn Han9f66c3b2020-03-05 23:59:29 -08001286 final BubbleViewProvider expandedBubble = mStackView != null
Mady Mellor5186b132019-09-16 17:55:48 -07001287 ? mStackView.getExpandedBubble()
1288 : null;
Mady Mellorca184aae2019-09-17 16:07:12 -07001289 int expandedId = expandedBubble != null ? expandedBubble.getDisplayId() : -1;
1290 if (mStackView != null && mStackView.isExpanded() && expandedId == displayId) {
Issei Suzuki734bc942019-06-05 13:59:52 +02001291 mBubbleData.setExpanded(false);
Issei Suzuki734bc942019-06-05 13:59:52 +02001292 }
Mady Mellorca184aae2019-09-17 16:07:12 -07001293 mBubbleData.notifyDisplayEmpty(displayId);
Issei Suzuki734bc942019-06-05 13:59:52 +02001294 }
Mark Renoufcecc77b2019-01-30 16:32:24 -05001295 }
1296
Mady Mellorca0c24c2019-05-16 16:14:32 -07001297 /**
1298 * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
1299 *
1300 * Keep checks in sync with NotificationManagerService#canLaunchInActivityView. Typically
1301 * that should filter out any invalid bubbles, but should protect SysUI side just in case.
1302 *
1303 * @param context the context to use.
1304 * @param entry the entry to bubble.
1305 */
1306 static boolean canLaunchInActivityView(Context context, NotificationEntry entry) {
1307 PendingIntent intent = entry.getBubbleMetadata() != null
Mady Melloraa9ce172020-03-17 10:34:20 -07001308 ? entry.getBubbleMetadata().getIntent()
Mady Mellorca0c24c2019-05-16 16:14:32 -07001309 : null;
Mady Mellor2ac2d3a2020-01-08 17:18:54 -08001310 if (entry.getBubbleMetadata() != null
1311 && entry.getBubbleMetadata().getShortcutId() != null) {
1312 return true;
1313 }
Mady Mellorca0c24c2019-05-16 16:14:32 -07001314 if (intent == null) {
Mady Mellor7f234902019-10-20 12:06:29 -07001315 Log.w(TAG, "Unable to create bubble -- no intent: " + entry.getKey());
Mady Mellorca0c24c2019-05-16 16:14:32 -07001316 return false;
1317 }
Mady Mellorf3b9fab2019-11-13 17:27:32 -08001318 PackageManager packageManager = StatusBar.getPackageManagerForUser(
1319 context, entry.getSbn().getUser().getIdentifier());
Mady Mellorca0c24c2019-05-16 16:14:32 -07001320 ActivityInfo info =
Mady Mellorf3b9fab2019-11-13 17:27:32 -08001321 intent.getIntent().resolveActivityInfo(packageManager, 0);
Mady Mellorca0c24c2019-05-16 16:14:32 -07001322 if (info == null) {
Mady Mellor7f234902019-10-20 12:06:29 -07001323 Log.w(TAG, "Unable to send as bubble, "
1324 + entry.getKey() + " couldn't find activity info for intent: "
Mady Mellorca0c24c2019-05-16 16:14:32 -07001325 + intent);
1326 return false;
1327 }
1328 if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
Mady Mellor7f234902019-10-20 12:06:29 -07001329 Log.w(TAG, "Unable to send as bubble, "
1330 + entry.getKey() + " activity is not resizable for intent: "
Mady Mellorca0c24c2019-05-16 16:14:32 -07001331 + intent);
1332 return false;
1333 }
Mady Mellorca0c24c2019-05-16 16:14:32 -07001334 return true;
1335 }
1336
Joshua Tsujia19515f2019-02-13 18:02:29 -05001337 /** PinnedStackListener that dispatches IME visibility updates to the stack. */
Hongwei Wang43a752b2019-09-17 20:20:30 +00001338 private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener {
Joshua Tsujia19515f2019-02-13 18:02:29 -05001339 @Override
Joshua Tsujid9422832019-03-05 13:32:37 -05001340 public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
Joshua Tsujiff6b0f22020-03-09 14:55:19 -04001341 if (mStackView != null) {
Joshua Tsujid9422832019-03-05 13:32:37 -05001342 mStackView.post(() -> mStackView.onImeVisibilityChanged(imeVisible, imeHeight));
Joshua Tsujia19515f2019-02-13 18:02:29 -05001343 }
1344 }
Joshua Tsujia19515f2019-02-13 18:02:29 -05001345 }
Mady Mellorc3d6f7d2018-11-07 09:36:56 -08001346}