blob: ed21e141c12dcb2c4e03fd8188289efbc68c65b5 [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 Mellor22f2f072019-04-18 13:26:18 -070019import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
Mady Mellor3a0a1b42019-05-23 06:40:21 -070020import static android.app.Notification.FLAG_BUBBLE;
Mady Mellorc2ff0112019-03-28 14:18:06 -070021import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
22import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
23import static android.service.notification.NotificationListenerService.REASON_CANCEL;
24import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
Mady Mellor22f2f072019-04-18 13:26:18 -070025import static android.service.notification.NotificationListenerService.REASON_CLICK;
26import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
Mady Mellor390bff42019-04-05 15:09:01 -070027import static android.view.Display.DEFAULT_DISPLAY;
28import static android.view.Display.INVALID_DISPLAY;
Mady Mellord1c78b262018-11-06 18:04:40 -080029import static android.view.View.INVISIBLE;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080030import static android.view.View.VISIBLE;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080031import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080032
Issei Suzukia8d07312019-06-07 12:56:19 +020033import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
Mady Mellorff076eb2019-11-13 10:12:06 -080034import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_EXPERIMENTS;
Issei Suzukia8d07312019-06-07 12:56:19 +020035import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
36import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080037import static com.android.systemui.statusbar.StatusBarState.SHADE;
Mady Mellor1a4e86f2019-05-03 16:07:23 -070038import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080039
Mark Renoufba5ab512019-05-02 15:21:01 -040040import static java.lang.annotation.ElementType.FIELD;
41import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
42import static java.lang.annotation.ElementType.PARAMETER;
Mark Renouf08bc42a2019-03-07 13:01:59 -050043import static java.lang.annotation.RetentionPolicy.SOURCE;
44
Mark Renoufc19b4732019-06-26 12:08:33 -040045import android.annotation.UserIdInt;
Mark Renoufc808f062019-02-07 15:20:37 -050046import android.app.ActivityManager.RunningTaskInfo;
Mady Mellor66efd5e2019-05-15 13:38:11 -070047import android.app.NotificationManager;
Mady Mellorca0c24c2019-05-16 16:14:32 -070048import android.app.PendingIntent;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080049import android.content.Context;
Mady Mellorca0c24c2019-05-16 16:14:32 -070050import android.content.pm.ActivityInfo;
Mady Mellorf3b9fab2019-11-13 17:27:32 -080051import android.content.pm.PackageManager;
Joshua Tsujif418f9e2019-04-04 17:09:53 -040052import android.content.res.Configuration;
Mady Mellord1c78b262018-11-06 18:04:40 -080053import android.graphics.Rect;
Mark Renoufcecc77b2019-01-30 16:32:24 -050054import android.os.RemoteException;
Mady Mellorb4991e62019-01-10 15:14:51 -080055import android.os.ServiceManager;
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;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080072import com.android.systemui.R;
Beverly8fdb5332019-02-04 14:29:49 -050073import com.android.systemui.plugins.statusbar.StatusBarStateController;
Mark Renoufcecc77b2019-01-30 16:32:24 -050074import com.android.systemui.shared.system.ActivityManagerWrapper;
Hongwei Wang43a752b2019-09-17 20:20:30 +000075import com.android.systemui.shared.system.PinnedStackListenerForwarder;
Mark Renoufcecc77b2019-01-30 16:32:24 -050076import com.android.systemui.shared.system.TaskStackChangeListener;
Joshua Tsujia19515f2019-02-13 18:02:29 -050077import com.android.systemui.shared.system.WindowManagerWrapper;
Mark Renoufc19b4732019-06-26 12:08:33 -040078import com.android.systemui.statusbar.NotificationLockscreenUserManager;
Mady Mellorc2ff0112019-03-28 14:18:06 -070079import com.android.systemui.statusbar.NotificationRemoveInterceptor;
Ned Burns01e38212019-01-03 16:32:52 -050080import com.android.systemui.statusbar.notification.NotificationEntryListener;
81import com.android.systemui.statusbar.notification.NotificationEntryManager;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080082import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
Ned Burnsf81c4c42019-01-07 14:10:43 -050083import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Mady Mellor22f2f072019-04-18 13:26:18 -070084import com.android.systemui.statusbar.phone.NotificationGroupManager;
Mady Mellor7f234902019-10-20 12:06:29 -070085import com.android.systemui.statusbar.phone.ShadeController;
Mady Mellorf3b9fab2019-11-13 17:27:32 -080086import com.android.systemui.statusbar.phone.StatusBar;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080087import com.android.systemui.statusbar.phone.StatusBarWindowController;
Lyn Hanf1c9b8b2019-03-14 16:49:48 -070088import com.android.systemui.statusbar.policy.ConfigurationController;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -040089import com.android.systemui.statusbar.policy.ZenModeController;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080090
Mady Mellor70cba7bb2019-07-02 15:06:07 -070091import java.io.FileDescriptor;
92import java.io.PrintWriter;
Mark Renouf08bc42a2019-03-07 13:01:59 -050093import java.lang.annotation.Retention;
Mark Renoufba5ab512019-05-02 15:21:01 -040094import java.lang.annotation.Target;
Mady Mellor22f2f072019-04-18 13:26:18 -070095import java.util.ArrayList;
Mady Mellorff076eb2019-11-13 10:12:06 -080096import java.util.HashSet;
Mady Mellore80930e2019-03-21 16:00:45 -070097import java.util.List;
Mark Renouf08bc42a2019-03-07 13:01:59 -050098
Jason Monk27d01a622018-12-10 15:57:09 -050099import javax.inject.Inject;
100import javax.inject.Singleton;
101
Mady Mellor7f234902019-10-20 12:06:29 -0700102import dagger.Lazy;
103
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800104/**
105 * Bubbles are a special type of content that can "float" on top of other apps or System UI.
106 * Bubbles can be expanded to show more content.
107 *
108 * The controller manages addition, removal, and visible state of bubbles on screen.
109 */
Jason Monk27d01a622018-12-10 15:57:09 -0500110@Singleton
Mark Renouf71a3af62019-04-08 15:02:54 -0400111public class BubbleController implements ConfigurationController.ConfigurationListener {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800112
Issei Suzukia8d07312019-06-07 12:56:19 +0200113 private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800114
Mark Renouf08bc42a2019-03-07 13:01:59 -0500115 @Retention(SOURCE)
116 @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
Mark Renoufc19b4732019-06-26 12:08:33 -0400117 DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
Mady Mellor8454ddf2019-08-15 11:16:23 -0700118 DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT})
Mark Renoufba5ab512019-05-02 15:21:01 -0400119 @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
Mark Renouf08bc42a2019-03-07 13:01:59 -0500120 @interface DismissReason {}
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700121
Mark Renouf08bc42a2019-03-07 13:01:59 -0500122 static final int DISMISS_USER_GESTURE = 1;
123 static final int DISMISS_AGED = 2;
124 static final int DISMISS_TASK_FINISHED = 3;
125 static final int DISMISS_BLOCKED = 4;
126 static final int DISMISS_NOTIF_CANCEL = 5;
127 static final int DISMISS_ACCESSIBILITY_ACTION = 6;
Mady Melloraa8fef22019-04-11 13:36:40 -0700128 static final int DISMISS_NO_LONGER_BUBBLE = 7;
Mark Renoufc19b4732019-06-26 12:08:33 -0400129 static final int DISMISS_USER_CHANGED = 8;
Mady Mellor22f2f072019-04-18 13:26:18 -0700130 static final int DISMISS_GROUP_CANCELLED = 9;
Mady Mellor8454ddf2019-08-15 11:16:23 -0700131 static final int DISMISS_INVALID_INTENT = 10;
Mark Renouf08bc42a2019-03-07 13:01:59 -0500132
Ned Burns01e38212019-01-03 16:32:52 -0500133 private final Context mContext;
134 private final NotificationEntryManager mNotificationEntryManager;
Mark Renoufcecc77b2019-01-30 16:32:24 -0500135 private final BubbleTaskStackListener mTaskStackListener;
Mady Mellord1c78b262018-11-06 18:04:40 -0800136 private BubbleStateChangeListener mStateChangeListener;
Mady Mellorcd9b1302018-11-06 18:08:04 -0800137 private BubbleExpandListener mExpandListener;
Issei Suzukic0387542019-03-08 17:31:14 +0100138 @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
Mady Mellor22f2f072019-04-18 13:26:18 -0700139 private final NotificationGroupManager mNotificationGroupManager;
Mady Mellor7f234902019-10-20 12:06:29 -0700140 private final Lazy<ShadeController> mShadeController;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800141
Mady Mellor3dff9e62019-02-05 18:12:53 -0800142 private BubbleData mBubbleData;
Joshua Tsujic650a142019-05-22 11:31:19 -0400143 @Nullable private BubbleStackView mStackView;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800144
Mark Renoufc19b4732019-06-26 12:08:33 -0400145 // Tracks the id of the current (foreground) user.
146 private int mCurrentUserId;
147 // Saves notification keys of active bubbles when users are switched.
148 private final SparseSetArray<String> mSavedBubbleKeysPerUser;
149
Mady Mellorff076eb2019-11-13 10:12:06 -0800150 // Saves notification keys of user created "fake" bubbles so that we can allow notifications
151 // like these to bubble by default. Doesn't persist across reboots, not a long-term solution.
152 private final HashSet<String> mUserCreatedBubbles;
153
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800154 // Bubbles get added to the status bar view
Ned Burns01e38212019-01-03 16:32:52 -0500155 private final StatusBarWindowController mStatusBarWindowController;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400156 private final ZenModeController mZenModeController;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800157 private StatusBarStateListener mStatusBarStateListener;
158
Mady Melloraa8fef22019-04-11 13:36:40 -0700159 private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
Mady Mellora54e9fa2019-04-18 13:26:18 -0700160 private IStatusBarService mBarService;
Mady Mellorb4991e62019-01-10 15:14:51 -0800161
Mady Mellord1c78b262018-11-06 18:04:40 -0800162 // Used for determining view rect for touch interaction
163 private Rect mTempRect = new Rect();
164
Mark Renoufc19b4732019-06-26 12:08:33 -0400165 // Listens to user switch so bubbles can be saved and restored.
166 private final NotificationLockscreenUserManager mNotifUserManager;
167
Joshua Tsujif418f9e2019-04-04 17:09:53 -0400168 /** Last known orientation, used to detect orientation changes in {@link #onConfigChanged}. */
169 private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
170
Mady Mellor5549dd22018-11-06 18:07:34 -0800171 /**
Mady Mellord1c78b262018-11-06 18:04:40 -0800172 * Listener to be notified when some states of the bubbles change.
173 */
174 public interface BubbleStateChangeListener {
175 /**
176 * Called when the stack has bubbles or no longer has bubbles.
177 */
178 void onHasBubblesChanged(boolean hasBubbles);
179 }
180
Mady Mellorcd9b1302018-11-06 18:08:04 -0800181 /**
182 * Listener to find out about stack expansion / collapse events.
183 */
184 public interface BubbleExpandListener {
185 /**
186 * Called when the expansion state of the bubble stack changes.
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700187 *
Mady Mellorcd9b1302018-11-06 18:08:04 -0800188 * @param isExpanding whether it's expanding or collapsing
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800189 * @param key the notification key associated with bubble being expanded
Mady Mellorcd9b1302018-11-06 18:08:04 -0800190 */
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800191 void onBubbleExpandChanged(boolean isExpanding, String key);
192 }
193
194 /**
195 * Listens for the current state of the status bar and updates the visibility state
196 * of bubbles as needed.
197 */
198 private class StatusBarStateListener implements StatusBarStateController.StateListener {
199 private int mState;
200 /**
201 * Returns the current status bar state.
202 */
203 public int getCurrentState() {
204 return mState;
205 }
206
207 @Override
208 public void onStateChanged(int newState) {
209 mState = newState;
Mark Renouf71a3af62019-04-08 15:02:54 -0400210 boolean shouldCollapse = (mState != SHADE);
211 if (shouldCollapse) {
212 collapseStack();
213 }
Lyn Han6c40fe72019-05-08 14:06:33 -0700214 updateStack();
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800215 }
Mady Mellorcd9b1302018-11-06 18:08:04 -0800216 }
217
Jason Monk27d01a622018-12-10 15:57:09 -0500218 @Inject
Mady Mellor7f234902019-10-20 12:06:29 -0700219 public BubbleController(Context context,
220 StatusBarWindowController statusBarWindowController,
221 StatusBarStateController statusBarStateController,
222 Lazy<ShadeController> shadeController,
223 BubbleData data,
Mady Melloraa8fef22019-04-11 13:36:40 -0700224 ConfigurationController configurationController,
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400225 NotificationInterruptionStateProvider interruptionStateProvider,
Mark Renoufc19b4732019-06-26 12:08:33 -0400226 ZenModeController zenModeController,
Mady Mellor22f2f072019-04-18 13:26:18 -0700227 NotificationLockscreenUserManager notifUserManager,
Mady Mellor7f234902019-10-20 12:06:29 -0700228 NotificationGroupManager groupManager,
229 NotificationEntryManager entryManager) {
230 this(context, statusBarWindowController, statusBarStateController, shadeController,
231 data, null /* synchronizer */, configurationController, interruptionStateProvider,
232 zenModeController, notifUserManager, groupManager, entryManager);
233 }
234
235 public BubbleController(Context context,
236 StatusBarWindowController statusBarWindowController,
237 StatusBarStateController statusBarStateController,
238 Lazy<ShadeController> shadeController,
239 BubbleData data,
240 @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
241 ConfigurationController configurationController,
242 NotificationInterruptionStateProvider interruptionStateProvider,
243 ZenModeController zenModeController,
244 NotificationLockscreenUserManager notifUserManager,
245 NotificationGroupManager groupManager,
246 NotificationEntryManager entryManager) {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800247 mContext = context;
Mady Melloraa8fef22019-04-11 13:36:40 -0700248 mNotificationInterruptionStateProvider = interruptionStateProvider;
Mark Renoufc19b4732019-06-26 12:08:33 -0400249 mNotifUserManager = notifUserManager;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400250 mZenModeController = zenModeController;
251 mZenModeController.addCallback(new ZenModeController.Callback() {
252 @Override
253 public void onZenChanged(int zen) {
Mady Mellordf48d0a2019-06-25 18:26:46 -0700254 if (mStackView != null) {
255 mStackView.updateDots();
256 }
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400257 }
258
259 @Override
260 public void onConfigChanged(ZenModeConfig config) {
Mady Mellordf48d0a2019-06-25 18:26:46 -0700261 if (mStackView != null) {
262 mStackView.updateDots();
263 }
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400264 }
265 });
Mady Melloraa8fef22019-04-11 13:36:40 -0700266
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700267 configurationController.addCallback(this /* configurationListener */);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800268
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400269 mBubbleData = data;
270 mBubbleData.setListener(mBubbleDataListener);
271
Mady Mellor7f234902019-10-20 12:06:29 -0700272 mNotificationEntryManager = entryManager;
Ned Burns01e38212019-01-03 16:32:52 -0500273 mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
Mady Mellorc2ff0112019-03-28 14:18:06 -0700274 mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
Mady Mellor22f2f072019-04-18 13:26:18 -0700275 mNotificationGroupManager = groupManager;
Mady Mellor740d85d2019-07-09 15:26:47 -0700276 mNotificationGroupManager.addOnGroupChangeListener(
277 new NotificationGroupManager.OnGroupChangeListener() {
278 @Override
279 public void onGroupSuppressionChanged(
280 NotificationGroupManager.NotificationGroup group,
281 boolean suppressed) {
282 // More notifications could be added causing summary to no longer
283 // be suppressed -- in this case need to remove the key.
284 final String groupKey = group.summary != null
Ned Burns00b4b2d2019-10-17 22:09:27 -0400285 ? group.summary.getSbn().getGroupKey()
Mady Mellor740d85d2019-07-09 15:26:47 -0700286 : null;
287 if (!suppressed && groupKey != null
288 && mBubbleData.isSummarySuppressed(groupKey)) {
289 mBubbleData.removeSuppressedSummary(groupKey);
290 }
291 }
292 });
Mady Mellorb4991e62019-01-10 15:14:51 -0800293
Mady Mellor7f234902019-10-20 12:06:29 -0700294 mShadeController = shadeController;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800295 mStatusBarWindowController = statusBarWindowController;
296 mStatusBarStateListener = new StatusBarStateListener();
Mady Mellor7f234902019-10-20 12:06:29 -0700297 statusBarStateController.addCallback(mStatusBarStateListener);
Mark Renoufcecc77b2019-01-30 16:32:24 -0500298
Mark Renoufcecc77b2019-01-30 16:32:24 -0500299 mTaskStackListener = new BubbleTaskStackListener();
300 ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800301
Joshua Tsujia19515f2019-02-13 18:02:29 -0500302 try {
303 WindowManagerWrapper.getInstance().addPinnedStackListener(new BubblesImeListener());
304 } catch (RemoteException e) {
305 e.printStackTrace();
306 }
Issei Suzukic0387542019-03-08 17:31:14 +0100307 mSurfaceSynchronizer = synchronizer;
Mady Mellora54e9fa2019-04-18 13:26:18 -0700308
309 mBarService = IStatusBarService.Stub.asInterface(
310 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
Mark Renoufc19b4732019-06-26 12:08:33 -0400311
312 mSavedBubbleKeysPerUser = new SparseSetArray<>();
313 mCurrentUserId = mNotifUserManager.getCurrentUserId();
314 mNotifUserManager.addUserChangedListener(
315 newUserId -> {
316 saveBubbles(mCurrentUserId);
317 mBubbleData.dismissAll(DISMISS_USER_CHANGED);
318 restoreBubbles(newUserId);
319 mCurrentUserId = newUserId;
320 });
Mady Mellorff076eb2019-11-13 10:12:06 -0800321
322 mUserCreatedBubbles = new HashSet<>();
Mady Mellor5549dd22018-11-06 18:07:34 -0800323 }
324
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400325 /**
326 * BubbleStackView is lazily created by this method the first time a Bubble is added. This
327 * method initializes the stack view and adds it to the StatusBar just above the scrim.
328 */
329 private void ensureStackViewCreated() {
330 if (mStackView == null) {
331 mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer);
332 ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
Lyn Hanbde48202019-05-29 19:18:29 -0700333 int bubbleScrimIndex = sbv.indexOfChild(sbv.findViewById(R.id.scrim_for_bubble));
334 int stackIndex = bubbleScrimIndex + 1; // Show stack above bubble scrim.
335 sbv.addView(mStackView, stackIndex,
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400336 new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
337 if (mExpandListener != null) {
338 mStackView.setExpandListener(mExpandListener);
339 }
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400340 }
341 }
342
Mark Renoufc19b4732019-06-26 12:08:33 -0400343 /**
344 * Records the notification key for any active bubbles. These are used to restore active
345 * bubbles when the user returns to the foreground.
346 *
347 * @param userId the id of the user
348 */
349 private void saveBubbles(@UserIdInt int userId) {
350 // First clear any existing keys that might be stored.
351 mSavedBubbleKeysPerUser.remove(userId);
352 // Add in all active bubbles for the current user.
353 for (Bubble bubble: mBubbleData.getBubbles()) {
354 mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
355 }
356 }
357
358 /**
359 * Promotes existing notifications to Bubbles if they were previously bubbles.
360 *
361 * @param userId the id of the user
362 */
363 private void restoreBubbles(@UserIdInt int userId) {
Mark Renoufc19b4732019-06-26 12:08:33 -0400364 ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId);
365 if (savedBubbleKeys == null) {
366 // There were no bubbles saved for this used.
367 return;
368 }
Evan Laird181de622019-10-24 09:53:02 -0400369 for (NotificationEntry e :
370 mNotificationEntryManager.getActiveNotificationsForCurrentUser()) {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400371 if (savedBubbleKeys.contains(e.getKey())
Mark Renoufc19b4732019-06-26 12:08:33 -0400372 && mNotificationInterruptionStateProvider.shouldBubbleUp(e)
373 && canLaunchInActivityView(mContext, e)) {
374 updateBubble(e, /* suppressFlyout= */ true);
375 }
376 }
377 // Finally, remove the entries for this user now that bubbles are restored.
378 mSavedBubbleKeysPerUser.remove(mCurrentUserId);
379 }
380
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700381 @Override
382 public void onUiModeChanged() {
383 if (mStackView != null) {
Lyn Han02cca812019-04-02 16:27:32 -0700384 mStackView.onThemeChanged();
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700385 }
386 }
387
388 @Override
389 public void onOverlayChanged() {
390 if (mStackView != null) {
Lyn Han02cca812019-04-02 16:27:32 -0700391 mStackView.onThemeChanged();
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700392 }
393 }
394
Joshua Tsujif418f9e2019-04-04 17:09:53 -0400395 @Override
396 public void onConfigChanged(Configuration newConfig) {
397 if (mStackView != null && newConfig != null && newConfig.orientation != mOrientation) {
Joshua Tsujif418f9e2019-04-04 17:09:53 -0400398 mOrientation = newConfig.orientation;
Lyn Hanf4730312019-06-18 11:18:58 -0700399 mStackView.onOrientationChanged(newConfig.orientation);
Joshua Tsujif418f9e2019-04-04 17:09:53 -0400400 }
401 }
402
Mady Mellor5549dd22018-11-06 18:07:34 -0800403 /**
Mady Mellord1c78b262018-11-06 18:04:40 -0800404 * Set a listener to be notified when some states of the bubbles change.
405 */
406 public void setBubbleStateChangeListener(BubbleStateChangeListener listener) {
407 mStateChangeListener = listener;
408 }
409
410 /**
Mady Mellorcd9b1302018-11-06 18:08:04 -0800411 * Set a listener to be notified of bubble expand events.
412 */
413 public void setExpandListener(BubbleExpandListener listener) {
Issei Suzukiac9fcb72019-02-04 17:45:57 +0100414 mExpandListener = ((isExpanding, key) -> {
415 if (listener != null) {
416 listener.onBubbleExpandChanged(isExpanding, key);
417 }
418 mStatusBarWindowController.setBubbleExpanded(isExpanding);
419 });
Mady Mellorcd9b1302018-11-06 18:08:04 -0800420 if (mStackView != null) {
421 mStackView.setExpandListener(mExpandListener);
422 }
423 }
424
425 /**
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800426 * Whether or not there are bubbles present, regardless of them being visible on the
427 * screen (e.g. if on AOD).
428 */
429 public boolean hasBubbles() {
Mady Mellor3dff9e62019-02-05 18:12:53 -0800430 if (mStackView == null) {
431 return false;
432 }
Mark Renouf71a3af62019-04-08 15:02:54 -0400433 return mBubbleData.hasBubbles();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800434 }
435
436 /**
437 * Whether the stack of bubbles is expanded or not.
438 */
439 public boolean isStackExpanded() {
Mark Renouf71a3af62019-04-08 15:02:54 -0400440 return mBubbleData.isExpanded();
441 }
442
443 /**
444 * Tell the stack of bubbles to expand.
445 */
446 public void expandStack() {
447 mBubbleData.setExpanded(true);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800448 }
449
450 /**
451 * Tell the stack of bubbles to collapse.
452 */
453 public void collapseStack() {
Mark Renouf71a3af62019-04-08 15:02:54 -0400454 mBubbleData.setExpanded(false /* expanded */);
455 }
456
Mady Mellorce23c462019-06-17 17:30:07 -0700457 /**
Mady Mellore28fe102019-07-09 15:33:32 -0700458 * True if either:
459 * (1) There is a bubble associated with the provided key and if its notification is hidden
460 * from the shade.
461 * (2) There is a group summary associated with the provided key that is hidden from the shade
462 * because it has been dismissed but still has child bubbles active.
Mady Mellorce23c462019-06-17 17:30:07 -0700463 *
Mady Mellore28fe102019-07-09 15:33:32 -0700464 * False otherwise.
Mady Mellorce23c462019-06-17 17:30:07 -0700465 */
466 public boolean isBubbleNotificationSuppressedFromShade(String key) {
Mady Mellore28fe102019-07-09 15:33:32 -0700467 boolean isBubbleAndSuppressed = mBubbleData.hasBubbleWithKey(key)
Mady Mellorce23c462019-06-17 17:30:07 -0700468 && !mBubbleData.getBubbleWithKey(key).showInShadeWhenBubble();
Evan Laird181de622019-10-24 09:53:02 -0400469 NotificationEntry entry = mNotificationEntryManager.getActiveNotificationUnfiltered(key);
Ned Burns00b4b2d2019-10-17 22:09:27 -0400470 String groupKey = entry != null ? entry.getSbn().getGroupKey() : null;
Mady Mellore28fe102019-07-09 15:33:32 -0700471 boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey);
Mady Mellore4348272019-07-29 17:43:36 -0700472 boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey));
473 return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed;
Mady Mellorce23c462019-06-17 17:30:07 -0700474 }
475
Mark Renouf71a3af62019-04-08 15:02:54 -0400476 void selectBubble(Bubble bubble) {
477 mBubbleData.setSelectedBubble(bubble);
478 }
479
480 @VisibleForTesting
481 void selectBubble(String key) {
482 Bubble bubble = mBubbleData.getBubbleWithKey(key);
483 selectBubble(bubble);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800484 }
485
486 /**
Mark Renouffec45da2019-03-13 13:24:27 -0400487 * Request the stack expand if needed, then select the specified Bubble as current.
488 *
489 * @param notificationKey the notification key for the bubble to be selected
490 */
491 public void expandStackAndSelectBubble(String notificationKey) {
Mark Renouf71a3af62019-04-08 15:02:54 -0400492 Bubble bubble = mBubbleData.getBubbleWithKey(notificationKey);
493 if (bubble != null) {
494 mBubbleData.setSelectedBubble(bubble);
495 mBubbleData.setExpanded(true);
Mark Renouffec45da2019-03-13 13:24:27 -0400496 }
497 }
498
499 /**
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800500 * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
501 */
Mark Renouf08bc42a2019-03-07 13:01:59 -0500502 void dismissStack(@DismissReason int reason) {
Mark Renouf71a3af62019-04-08 15:02:54 -0400503 mBubbleData.dismissAll(reason);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800504 }
505
506 /**
Mark Renouf041d7262019-02-06 12:09:41 -0500507 * Directs a back gesture at the bubble stack. When opened, the current expanded bubble
508 * is forwarded a back key down/up pair.
509 */
510 public void performBackPressIfNeeded() {
511 if (mStackView != null) {
512 mStackView.performBackPressIfNeeded();
513 }
514 }
515
516 /**
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800517 * Adds or updates a bubble associated with the provided notification entry.
518 *
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400519 * @param notif the notification associated with this bubble.
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800520 */
Mark Renouff97ed462019-04-05 13:46:24 -0400521 void updateBubble(NotificationEntry notif) {
Mady Mellor7f234902019-10-20 12:06:29 -0700522 updateBubble(notif, false /* suppressFlyout */);
Mark Renoufc19b4732019-06-26 12:08:33 -0400523 }
524
525 void updateBubble(NotificationEntry notif, boolean suppressFlyout) {
Mady Mellor7f234902019-10-20 12:06:29 -0700526 updateBubble(notif, suppressFlyout, true /* showInShade */);
527 }
528
529 void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {
Mady Mellor66efd5e2019-05-15 13:38:11 -0700530 // If this is an interruptive notif, mark that it's interrupted
Ned Burns60e94592019-09-06 14:47:25 -0400531 if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
Mady Mellor66efd5e2019-05-15 13:38:11 -0700532 notif.setInterruption();
533 }
Mady Mellor7f234902019-10-20 12:06:29 -0700534 mBubbleData.notificationEntryUpdated(notif, suppressFlyout, showInShade);
535 }
536
537 /**
538 * Called when a user has indicated that an active notification should be shown as a bubble.
539 * <p>
540 * This method will collapse the shade, create the bubble without a flyout or dot, and suppress
541 * the notification from appearing in the shade.
542 *
543 * @param entry the notification to show as a bubble.
544 */
545 public void onUserCreatedBubbleFromNotification(NotificationEntry entry) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800546 if (DEBUG_EXPERIMENTS || DEBUG_BUBBLE_CONTROLLER) {
547 Log.d(TAG, "onUserCreatedBubble: " + entry.getKey());
548 }
Mady Mellor7f234902019-10-20 12:06:29 -0700549 mShadeController.get().collapsePanel(true);
550 entry.setFlagBubble(true);
551 updateBubble(entry, true /* suppressFlyout */, false /* showInShade */);
Mady Mellorff076eb2019-11-13 10:12:06 -0800552 mUserCreatedBubbles.add(entry.getKey());
Mady Mellor7f234902019-10-20 12:06:29 -0700553 }
554
555 /**
556 * Called when a user has indicated that an active notification appearing as a bubble should
557 * no longer be shown as a bubble.
558 *
559 * @param entry the notification to no longer show as a bubble.
560 */
561 public void onUserDemotedBubbleFromNotification(NotificationEntry entry) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800562 if (DEBUG_EXPERIMENTS || DEBUG_BUBBLE_CONTROLLER) {
563 Log.d(TAG, "onUserDemotedBubble: " + entry.getKey());
564 }
Mady Mellor7f234902019-10-20 12:06:29 -0700565 entry.setFlagBubble(false);
566 removeBubble(entry.getKey(), DISMISS_BLOCKED);
Mady Mellorff076eb2019-11-13 10:12:06 -0800567 mUserCreatedBubbles.remove(entry.getKey());
568 }
569
570 /**
571 * Whether this bubble was explicitly created by the user via a SysUI affordance.
572 */
573 boolean isUserCreatedBubble(String key) {
574 return mUserCreatedBubbles.contains(key);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800575 }
576
577 /**
578 * Removes the bubble associated with the {@param uri}.
Mark Renouf658c6bc2019-01-30 10:26:54 -0500579 * <p>
580 * Must be called from the main thread.
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800581 */
Mark Renouf658c6bc2019-01-30 10:26:54 -0500582 @MainThread
Mark Renouf08bc42a2019-03-07 13:01:59 -0500583 void removeBubble(String key, int reason) {
Mark Renouf71a3af62019-04-08 15:02:54 -0400584 // TEMP: refactor to change this to pass entry
585 Bubble bubble = mBubbleData.getBubbleWithKey(key);
586 if (bubble != null) {
Mady Mellored99c272019-06-13 15:58:30 -0700587 mBubbleData.notificationEntryRemoved(bubble.getEntry(), reason);
Mady Mellore8e07712019-01-23 12:45:33 -0800588 }
589 }
590
Ned Burns01e38212019-01-03 16:32:52 -0500591 @SuppressWarnings("FieldCanBeLocal")
Mady Mellorc2ff0112019-03-28 14:18:06 -0700592 private final NotificationRemoveInterceptor mRemoveInterceptor =
593 new NotificationRemoveInterceptor() {
594 @Override
595 public boolean onNotificationRemoveRequested(String key, int reason) {
Evan Laird181de622019-10-24 09:53:02 -0400596 NotificationEntry entry =
597 mNotificationEntryManager.getActiveNotificationUnfiltered(key);
Ned Burns00b4b2d2019-10-17 22:09:27 -0400598 String groupKey = entry != null ? entry.getSbn().getGroupKey() : null;
Mady Mellor22f2f072019-04-18 13:26:18 -0700599 ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
600
601 boolean inBubbleData = mBubbleData.hasBubbleWithKey(key);
Mady Mellore28fe102019-07-09 15:33:32 -0700602 boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
603 && mBubbleData.getSummaryKey(groupKey).equals(key));
Mady Mellor22f2f072019-04-18 13:26:18 -0700604 boolean isSummary = entry != null
Ned Burns00b4b2d2019-10-17 22:09:27 -0400605 && entry.getSbn().getNotification().isGroupSummary();
Mady Mellore28fe102019-07-09 15:33:32 -0700606 boolean isSummaryOfBubbles = (isSuppressedSummary || isSummary)
607 && bubbleChildren != null && !bubbleChildren.isEmpty();
Mady Mellor22f2f072019-04-18 13:26:18 -0700608
609 if (!inBubbleData && !isSummaryOfBubbles) {
Mady Mellorc2ff0112019-03-28 14:18:06 -0700610 return false;
611 }
Mady Mellorc2ff0112019-03-28 14:18:06 -0700612
613 final boolean isClearAll = reason == REASON_CANCEL_ALL;
Mady Mellor22f2f072019-04-18 13:26:18 -0700614 final boolean isUserDimiss = reason == REASON_CANCEL || reason == REASON_CLICK;
Mady Mellorc2ff0112019-03-28 14:18:06 -0700615 final boolean isAppCancel = reason == REASON_APP_CANCEL
616 || reason == REASON_APP_CANCEL_ALL;
Mady Mellor22f2f072019-04-18 13:26:18 -0700617 final boolean isSummaryCancel = reason == REASON_GROUP_SUMMARY_CANCELED;
Mady Mellorc2ff0112019-03-28 14:18:06 -0700618
619 // Need to check for !appCancel here because the notification may have
620 // previously been dismissed & entry.isRowDismissed would still be true
Mady Mellorca184aae2019-09-17 16:07:12 -0700621 boolean userRemovedNotif = (entry != null && entry.isRowDismissed() && !isAppCancel)
Mady Mellor22f2f072019-04-18 13:26:18 -0700622 || isClearAll || isUserDimiss || isSummaryCancel;
623
624 if (isSummaryOfBubbles) {
625 return handleSummaryRemovalInterception(entry, userRemovedNotif);
626 }
Mady Mellorc2ff0112019-03-28 14:18:06 -0700627
628 // The bubble notification sticks around in the data as long as the bubble is
629 // not dismissed and the app hasn't cancelled the notification.
Mady Mellor22f2f072019-04-18 13:26:18 -0700630 Bubble bubble = mBubbleData.getBubbleWithKey(key);
Mady Mellorca184aae2019-09-17 16:07:12 -0700631 boolean bubbleExtended = entry != null && entry.isBubble() && userRemovedNotif;
Mady Mellorc2ff0112019-03-28 14:18:06 -0700632 if (bubbleExtended) {
Mady Mellorce23c462019-06-17 17:30:07 -0700633 bubble.setShowInShadeWhenBubble(false);
Mady Mellordf48d0a2019-06-25 18:26:46 -0700634 bubble.setShowBubbleDot(false);
Mady Mellorc2ff0112019-03-28 14:18:06 -0700635 if (mStackView != null) {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400636 mStackView.updateDotVisibility(entry.getKey());
Mady Mellorc2ff0112019-03-28 14:18:06 -0700637 }
Beverly85d4c192019-09-30 11:40:39 -0400638 mNotificationEntryManager.updateNotifications(
639 "BubbleController.onNotificationRemoveRequested");
Mady Mellorc2ff0112019-03-28 14:18:06 -0700640 return true;
Mady Mellorff076eb2019-11-13 10:12:06 -0800641 } else if (!userRemovedNotif && entry != null
642 && !isUserCreatedBubble(bubble.getKey())) {
Mady Mellorc2ff0112019-03-28 14:18:06 -0700643 // This wasn't a user removal so we should remove the bubble as well
644 mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
645 return false;
646 }
647 return false;
648 }
649 };
650
Mady Mellor22f2f072019-04-18 13:26:18 -0700651 private boolean handleSummaryRemovalInterception(NotificationEntry summary,
652 boolean userRemovedNotif) {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400653 String groupKey = summary.getSbn().getGroupKey();
Mady Mellor22f2f072019-04-18 13:26:18 -0700654 ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
655
656 if (userRemovedNotif) {
657 // If it's a user dismiss we mark the children to be hidden from the shade.
658 for (int i = 0; i < bubbleChildren.size(); i++) {
659 Bubble bubbleChild = bubbleChildren.get(i);
660 // As far as group manager is concerned, once a child is no longer shown
661 // in the shade, it is essentially removed.
662 mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
663 bubbleChild.setShowInShadeWhenBubble(false);
664 bubbleChild.setShowBubbleDot(false);
665 if (mStackView != null) {
666 mStackView.updateDotVisibility(bubbleChild.getKey());
667 }
668 }
669 // And since all children are removed, remove the summary.
670 mNotificationGroupManager.onEntryRemoved(summary);
671
672 // If the summary was auto-generated we don't need to keep that notification around
673 // because apps can't cancel it; so we only intercept & suppress real summaries.
Ned Burns00b4b2d2019-10-17 22:09:27 -0400674 boolean isAutogroupSummary = (summary.getSbn().getNotification().flags
Mady Mellor22f2f072019-04-18 13:26:18 -0700675 & FLAG_AUTOGROUP_SUMMARY) != 0;
Mady Mellore28fe102019-07-09 15:33:32 -0700676 if (!isAutogroupSummary) {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400677 mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(),
678 summary.getKey());
Mady Mellore28fe102019-07-09 15:33:32 -0700679 // Tell shade to update for the suppression
Beverly85d4c192019-09-30 11:40:39 -0400680 mNotificationEntryManager.updateNotifications(
681 "BubbleController.handleSummaryRemovalInterception");
Mady Mellore28fe102019-07-09 15:33:32 -0700682 }
Mady Mellor22f2f072019-04-18 13:26:18 -0700683 return !isAutogroupSummary;
684 } else {
Mady Mellore28fe102019-07-09 15:33:32 -0700685 // If it's not a user dismiss it's a cancel.
686 mBubbleData.removeSuppressedSummary(groupKey);
687
Mady Mellor22f2f072019-04-18 13:26:18 -0700688 // Remove any associated bubble children.
689 for (int i = 0; i < bubbleChildren.size(); i++) {
690 Bubble bubbleChild = bubbleChildren.get(i);
691 mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(),
692 DISMISS_GROUP_CANCELLED);
693 }
694 return false;
695 }
696 }
697
Mady Mellorc2ff0112019-03-28 14:18:06 -0700698 @SuppressWarnings("FieldCanBeLocal")
Ned Burns01e38212019-01-03 16:32:52 -0500699 private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
700 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -0500701 public void onPendingEntryAdded(NotificationEntry entry) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800702 boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
703 BubbleExperimentConfig.adjustForExperiments(mContext, entry, previouslyUserCreated);
Mady Mellor7f234902019-10-20 12:06:29 -0700704
Mady Mellorca0c24c2019-05-16 16:14:32 -0700705 if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
706 && canLaunchInActivityView(mContext, entry)) {
Mark Renouff97ed462019-04-05 13:46:24 -0400707 updateBubble(entry);
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800708 }
709 }
710
711 @Override
712 public void onPreEntryUpdated(NotificationEntry entry) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800713 boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
714 BubbleExperimentConfig.adjustForExperiments(mContext, entry, previouslyUserCreated);
Mady Mellor7f234902019-10-20 12:06:29 -0700715
Mady Mellorca0c24c2019-05-16 16:14:32 -0700716 boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
717 && canLaunchInActivityView(mContext, entry);
Ned Burns00b4b2d2019-10-17 22:09:27 -0400718 if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) {
Mady Melloraa8fef22019-04-11 13:36:40 -0700719 // It was previously a bubble but no longer a bubble -- lets remove it
Ned Burns00b4b2d2019-10-17 22:09:27 -0400720 removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE);
Mady Mellorff40e012019-05-03 15:34:41 -0700721 } else if (shouldBubble) {
Mark Renouff97ed462019-04-05 13:46:24 -0400722 updateBubble(entry);
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800723 }
724 }
Mark Renoufbbcf07f2019-05-09 10:42:43 -0400725
726 @Override
727 public void onNotificationRankingUpdated(RankingMap rankingMap) {
728 // Forward to BubbleData to block any bubbles which should no longer be shown
729 mBubbleData.notificationRankingUpdated(rankingMap);
730 }
Ned Burns01e38212019-01-03 16:32:52 -0500731 };
732
Mark Renouf71a3af62019-04-08 15:02:54 -0400733 @SuppressWarnings("FieldCanBeLocal")
Mark Renouf3bc5b362019-04-05 14:37:59 -0400734 private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {
Mark Renouf71a3af62019-04-08 15:02:54 -0400735
Mark Renouf3bc5b362019-04-05 14:37:59 -0400736 @Override
Mark Renouf82a40e62019-05-23 16:16:24 -0400737 public void applyUpdate(BubbleData.Update update) {
738 if (mStackView == null && update.addedBubble != null) {
739 // Lazy init stack view when the first bubble is added.
740 ensureStackViewCreated();
Mark Renouf71a3af62019-04-08 15:02:54 -0400741 }
Mark Renouf82a40e62019-05-23 16:16:24 -0400742
743 // If not yet initialized, ignore all other changes.
744 if (mStackView == null) {
745 return;
746 }
747
748 if (update.addedBubble != null) {
749 mStackView.addBubble(update.addedBubble);
750 }
751
752 // Collapsing? Do this first before remaining steps.
753 if (update.expandedChanged && !update.expanded) {
754 mStackView.setExpanded(false);
755 }
756
757 // Do removals, if any.
Mady Mellor22f2f072019-04-18 13:26:18 -0700758 ArrayList<Pair<Bubble, Integer>> removedBubbles =
759 new ArrayList<>(update.removedBubbles);
760 for (Pair<Bubble, Integer> removed : removedBubbles) {
Mark Renouf82a40e62019-05-23 16:16:24 -0400761 final Bubble bubble = removed.first;
762 @DismissReason final int reason = removed.second;
763 mStackView.removeBubble(bubble);
764
Mark Renoufc19b4732019-06-26 12:08:33 -0400765 // If the bubble is removed for user switching, leave the notification in place.
766 if (reason != DISMISS_USER_CHANGED) {
767 if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
768 && !bubble.showInShadeWhenBubble()) {
769 // The bubble is gone & the notification is gone, time to actually remove it
770 mNotificationEntryManager.performRemoveNotification(
Ned Burns00b4b2d2019-10-17 22:09:27 -0400771 bubble.getEntry().getSbn(), UNDEFINED_DISMISS_REASON);
Mark Renoufc19b4732019-06-26 12:08:33 -0400772 } else {
773 // Update the flag for SysUI
Ned Burns00b4b2d2019-10-17 22:09:27 -0400774 bubble.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
Mady Mellor3a0a1b42019-05-23 06:40:21 -0700775
Mark Renoufc19b4732019-06-26 12:08:33 -0400776 // Make sure NoMan knows it's not a bubble anymore so anyone querying it
777 // will get right result back
778 try {
779 mBarService.onNotificationBubbleChanged(bubble.getKey(),
780 false /* isBubble */);
781 } catch (RemoteException e) {
782 // Bad things have happened
783 }
Mark Renouf82a40e62019-05-23 16:16:24 -0400784 }
Mady Mellor22f2f072019-04-18 13:26:18 -0700785
Mady Mellore28fe102019-07-09 15:33:32 -0700786 // Check if removed bubble has an associated suppressed group summary that needs
787 // to be removed now.
Ned Burns00b4b2d2019-10-17 22:09:27 -0400788 final String groupKey = bubble.getEntry().getSbn().getGroupKey();
Mady Mellore28fe102019-07-09 15:33:32 -0700789 if (mBubbleData.isSummarySuppressed(groupKey)
790 && mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
791 // Time to actually remove the summary.
792 String notifKey = mBubbleData.getSummaryKey(groupKey);
793 mBubbleData.removeSuppressedSummary(groupKey);
794 NotificationEntry entry =
Evan Laird181de622019-10-24 09:53:02 -0400795 mNotificationEntryManager.getActiveNotificationUnfiltered(notifKey);
Mady Mellore28fe102019-07-09 15:33:32 -0700796 mNotificationEntryManager.performRemoveNotification(
Ned Burns00b4b2d2019-10-17 22:09:27 -0400797 entry.getSbn(), UNDEFINED_DISMISS_REASON);
Mady Mellore28fe102019-07-09 15:33:32 -0700798 }
799
Mady Mellor22f2f072019-04-18 13:26:18 -0700800 // Check if summary should be removed from NoManGroup
801 NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(
Ned Burns00b4b2d2019-10-17 22:09:27 -0400802 bubble.getEntry().getSbn());
Mady Mellor22f2f072019-04-18 13:26:18 -0700803 if (summary != null) {
804 ArrayList<NotificationEntry> summaryChildren =
Ned Burns00b4b2d2019-10-17 22:09:27 -0400805 mNotificationGroupManager.getLogicalChildren(summary.getSbn());
806 boolean isSummaryThisNotif = summary.getKey().equals(
807 bubble.getEntry().getKey());
Mady Mellore4348272019-07-29 17:43:36 -0700808 if (!isSummaryThisNotif
809 && (summaryChildren == null || summaryChildren.isEmpty())) {
Mady Mellor22f2f072019-04-18 13:26:18 -0700810 mNotificationEntryManager.performRemoveNotification(
Ned Burns00b4b2d2019-10-17 22:09:27 -0400811 summary.getSbn(), UNDEFINED_DISMISS_REASON);
Mady Mellor22f2f072019-04-18 13:26:18 -0700812 }
813 }
Mady Mellora54e9fa2019-04-18 13:26:18 -0700814 }
815 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400816
Mark Renouf82a40e62019-05-23 16:16:24 -0400817 if (update.updatedBubble != null) {
818 mStackView.updateBubble(update.updatedBubble);
Mark Renouf71a3af62019-04-08 15:02:54 -0400819 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400820
Mark Renouf82a40e62019-05-23 16:16:24 -0400821 if (update.orderChanged) {
822 mStackView.updateBubbleOrder(update.bubbles);
Mark Renoufba5ab512019-05-02 15:21:01 -0400823 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400824
Mark Renouf82a40e62019-05-23 16:16:24 -0400825 if (update.selectionChanged) {
826 mStackView.setSelectedBubble(update.selectedBubble);
Mady Mellor740d85d2019-07-09 15:26:47 -0700827 if (update.selectedBubble != null) {
828 mNotificationGroupManager.updateSuppression(
829 update.selectedBubble.getEntry());
830 }
Mark Renouf71a3af62019-04-08 15:02:54 -0400831 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400832
Mark Renouf82a40e62019-05-23 16:16:24 -0400833 // Expanding? Apply this last.
834 if (update.expandedChanged && update.expanded) {
835 mStackView.setExpanded(true);
Mark Renouf71a3af62019-04-08 15:02:54 -0400836 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400837
Beverly85d4c192019-09-30 11:40:39 -0400838 mNotificationEntryManager.updateNotifications(
839 "BubbleData.Listener.applyUpdate");
Lyn Han6c40fe72019-05-08 14:06:33 -0700840 updateStack();
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400841
Issei Suzukia8d07312019-06-07 12:56:19 +0200842 if (DEBUG_BUBBLE_CONTROLLER) {
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400843 Log.d(TAG, "[BubbleData]");
844 Log.d(TAG, formatBubblesString(mBubbleData.getBubbles(),
845 mBubbleData.getSelectedBubble()));
846
847 if (mStackView != null) {
848 Log.d(TAG, "[BubbleStackView]");
849 Log.d(TAG, formatBubblesString(mStackView.getBubblesOnScreen(),
850 mStackView.getExpandedBubble()));
851 }
852 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400853 }
854 };
855
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800856 /**
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400857 * Lets any listeners know if bubble state has changed.
Lyn Han6c40fe72019-05-08 14:06:33 -0700858 * Updates the visibility of the bubbles based on current state.
859 * Does not un-bubble, just hides or un-hides. Notifies any
860 * {@link BubbleStateChangeListener}s of visibility changes.
861 * Updates stack description for TalkBack focus.
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800862 */
Lyn Han6c40fe72019-05-08 14:06:33 -0700863 public void updateStack() {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800864 if (mStackView == null) {
865 return;
Mady Mellord1c78b262018-11-06 18:04:40 -0800866 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800867 if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) {
868 // Bubbles only appear in unlocked shade
869 mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE);
Mady Mellor698d9e82019-08-01 23:11:53 +0000870 } else if (mStackView != null) {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800871 mStackView.setVisibility(INVISIBLE);
Mady Mellor5549dd22018-11-06 18:07:34 -0800872 }
Lyn Han6c40fe72019-05-08 14:06:33 -0700873
Mady Mellor698d9e82019-08-01 23:11:53 +0000874 // Let listeners know if bubble state changed.
Lyn Han6c40fe72019-05-08 14:06:33 -0700875 boolean hadBubbles = mStatusBarWindowController.getBubblesShowing();
Mady Mellor698d9e82019-08-01 23:11:53 +0000876 boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE;
Mady Mellor88552b82019-08-05 22:38:59 +0000877 mStatusBarWindowController.setBubblesShowing(hasBubblesShowing);
Lyn Han6c40fe72019-05-08 14:06:33 -0700878 if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
879 mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
880 }
881
882 mStackView.updateContentDescription();
Mady Mellord1c78b262018-11-06 18:04:40 -0800883 }
884
885 /**
886 * Rect indicating the touchable region for the bubble stack / expanded stack.
887 */
888 public Rect getTouchableRegion() {
889 if (mStackView == null || mStackView.getVisibility() != VISIBLE) {
890 return null;
891 }
892 mStackView.getBoundsOnScreen(mTempRect);
893 return mTempRect;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800894 }
895
Mady Mellor390bff42019-04-05 15:09:01 -0700896 /**
897 * The display id of the expanded view, if the stack is expanded and not occluded by the
898 * status bar, otherwise returns {@link Display#INVALID_DISPLAY}.
899 */
900 public int getExpandedDisplayId(Context context) {
Issei Suzukicac2a502019-04-16 16:52:50 +0200901 final Bubble bubble = getExpandedBubble(context);
902 return bubble != null ? bubble.getDisplayId() : INVALID_DISPLAY;
903 }
904
905 @Nullable
906 private Bubble getExpandedBubble(Context context) {
Joel Galenson4071ddb2019-04-18 13:30:45 -0700907 if (mStackView == null) {
Issei Suzukicac2a502019-04-16 16:52:50 +0200908 return null;
Joel Galenson4071ddb2019-04-18 13:30:45 -0700909 }
Issei Suzukicac2a502019-04-16 16:52:50 +0200910 final boolean defaultDisplay = context.getDisplay() != null
Mady Mellor390bff42019-04-05 15:09:01 -0700911 && context.getDisplay().getDisplayId() == DEFAULT_DISPLAY;
Issei Suzukicac2a502019-04-16 16:52:50 +0200912 final Bubble expandedBubble = mStackView.getExpandedBubble();
913 if (defaultDisplay && expandedBubble != null && isStackExpanded()
Mady Mellor390bff42019-04-05 15:09:01 -0700914 && !mStatusBarWindowController.getPanelExpanded()) {
Issei Suzukicac2a502019-04-16 16:52:50 +0200915 return expandedBubble;
Mady Mellor390bff42019-04-05 15:09:01 -0700916 }
Issei Suzukicac2a502019-04-16 16:52:50 +0200917 return null;
Mady Mellor390bff42019-04-05 15:09:01 -0700918 }
919
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800920 @VisibleForTesting
921 BubbleStackView getStackView() {
922 return mStackView;
923 }
924
Mady Mellor70cba7bb2019-07-02 15:06:07 -0700925 /**
926 * Description of current bubble state.
927 */
928 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
929 pw.println("BubbleController state:");
930 mBubbleData.dump(fd, pw, args);
931 pw.println();
Joshua Tsuji395bcfe2019-07-02 19:23:23 -0400932 if (mStackView != null) {
933 mStackView.dump(fd, pw, args);
934 }
935 pw.println();
Mady Mellor70cba7bb2019-07-02 15:06:07 -0700936 }
937
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400938 static String formatBubblesString(List<Bubble> bubbles, Bubble selected) {
939 StringBuilder sb = new StringBuilder();
940 for (Bubble bubble : bubbles) {
941 if (bubble == null) {
942 sb.append(" <null> !!!!!\n");
943 } else {
944 boolean isSelected = (bubble == selected);
945 sb.append(String.format("%s Bubble{act=%12d, ongoing=%d, key=%s}\n",
946 ((isSelected) ? "->" : " "),
947 bubble.getLastActivity(),
948 (bubble.isOngoing() ? 1 : 0),
949 bubble.getKey()));
950 }
951 }
952 return sb.toString();
953 }
954
Mady Mellore80930e2019-03-21 16:00:45 -0700955 /**
Mark Renoufcecc77b2019-01-30 16:32:24 -0500956 * This task stack listener is responsible for responding to tasks moved to the front
957 * which are on the default (main) display. When this happens, expanded bubbles must be
958 * collapsed so the user may interact with the app which was just moved to the front.
959 * <p>
960 * This listener is registered with SystemUI's ActivityManagerWrapper which dispatches
961 * these calls via a main thread Handler.
962 */
963 @MainThread
964 private class BubbleTaskStackListener extends TaskStackChangeListener {
965
Mark Renoufcecc77b2019-01-30 16:32:24 -0500966 @Override
Mark Renoufc808f062019-02-07 15:20:37 -0500967 public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
968 if (mStackView != null && taskInfo.displayId == Display.DEFAULT_DISPLAY) {
Mady Mellor047e24e2019-08-05 11:35:40 -0700969 if (!mStackView.isExpansionAnimating()) {
970 mBubbleData.setExpanded(false);
971 }
Mark Renoufcecc77b2019-01-30 16:32:24 -0500972 }
973 }
974
Mark Renoufcecc77b2019-01-30 16:32:24 -0500975 @Override
Mark Renoufa12e8762019-03-07 15:43:01 -0500976 public void onActivityLaunchOnSecondaryDisplayRerouted() {
Mark Renoufcecc77b2019-01-30 16:32:24 -0500977 if (mStackView != null) {
Mark Renouf71a3af62019-04-08 15:02:54 -0400978 mBubbleData.setExpanded(false);
Mark Renoufcecc77b2019-01-30 16:32:24 -0500979 }
980 }
Mark Renouf446251d2019-04-26 10:22:41 -0400981
982 @Override
983 public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
984 if (mStackView != null && taskInfo.displayId == getExpandedDisplayId(mContext)) {
985 mBubbleData.setExpanded(false);
986 }
987 }
Issei Suzukicac2a502019-04-16 16:52:50 +0200988
989 @Override
990 public void onSingleTaskDisplayDrawn(int displayId) {
Mady Mellor5186b132019-09-16 17:55:48 -0700991 final Bubble expandedBubble = mStackView != null
992 ? mStackView.getExpandedBubble()
993 : null;
Issei Suzukicac2a502019-04-16 16:52:50 +0200994 if (expandedBubble != null && expandedBubble.getDisplayId() == displayId) {
995 expandedBubble.setContentVisibility(true);
996 }
997 }
Issei Suzuki734bc942019-06-05 13:59:52 +0200998
999 @Override
1000 public void onSingleTaskDisplayEmpty(int displayId) {
Mady Mellor5186b132019-09-16 17:55:48 -07001001 final Bubble expandedBubble = mStackView != null
1002 ? mStackView.getExpandedBubble()
1003 : null;
Mady Mellorca184aae2019-09-17 16:07:12 -07001004 int expandedId = expandedBubble != null ? expandedBubble.getDisplayId() : -1;
1005 if (mStackView != null && mStackView.isExpanded() && expandedId == displayId) {
Issei Suzuki734bc942019-06-05 13:59:52 +02001006 mBubbleData.setExpanded(false);
Issei Suzuki734bc942019-06-05 13:59:52 +02001007 }
Mady Mellorca184aae2019-09-17 16:07:12 -07001008 mBubbleData.notifyDisplayEmpty(displayId);
Issei Suzuki734bc942019-06-05 13:59:52 +02001009 }
Mark Renoufcecc77b2019-01-30 16:32:24 -05001010 }
1011
Mady Mellorca0c24c2019-05-16 16:14:32 -07001012 /**
1013 * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
1014 *
1015 * Keep checks in sync with NotificationManagerService#canLaunchInActivityView. Typically
1016 * that should filter out any invalid bubbles, but should protect SysUI side just in case.
1017 *
1018 * @param context the context to use.
1019 * @param entry the entry to bubble.
1020 */
1021 static boolean canLaunchInActivityView(Context context, NotificationEntry entry) {
1022 PendingIntent intent = entry.getBubbleMetadata() != null
1023 ? entry.getBubbleMetadata().getIntent()
1024 : null;
Mady Mellor7f234902019-10-20 12:06:29 -07001025 return canLaunchIntentInActivityView(context, entry, intent);
1026 }
1027
1028 static boolean canLaunchIntentInActivityView(Context context, NotificationEntry entry,
1029 PendingIntent intent) {
Mady Mellorca0c24c2019-05-16 16:14:32 -07001030 if (intent == null) {
Mady Mellor7f234902019-10-20 12:06:29 -07001031 Log.w(TAG, "Unable to create bubble -- no intent: " + entry.getKey());
Mady Mellorca0c24c2019-05-16 16:14:32 -07001032 return false;
1033 }
Mady Mellorf3b9fab2019-11-13 17:27:32 -08001034 PackageManager packageManager = StatusBar.getPackageManagerForUser(
1035 context, entry.getSbn().getUser().getIdentifier());
Mady Mellorca0c24c2019-05-16 16:14:32 -07001036 ActivityInfo info =
Mady Mellorf3b9fab2019-11-13 17:27:32 -08001037 intent.getIntent().resolveActivityInfo(packageManager, 0);
Mady Mellorca0c24c2019-05-16 16:14:32 -07001038 if (info == null) {
Mady Mellor7f234902019-10-20 12:06:29 -07001039 Log.w(TAG, "Unable to send as bubble, "
1040 + entry.getKey() + " couldn't find activity info for intent: "
Mady Mellorca0c24c2019-05-16 16:14:32 -07001041 + intent);
1042 return false;
1043 }
1044 if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
Mady Mellor7f234902019-10-20 12:06:29 -07001045 Log.w(TAG, "Unable to send as bubble, "
1046 + entry.getKey() + " activity is not resizable for intent: "
Mady Mellorca0c24c2019-05-16 16:14:32 -07001047 + intent);
1048 return false;
1049 }
Mady Mellorca0c24c2019-05-16 16:14:32 -07001050 return true;
1051 }
1052
Joshua Tsujia19515f2019-02-13 18:02:29 -05001053 /** PinnedStackListener that dispatches IME visibility updates to the stack. */
Hongwei Wang43a752b2019-09-17 20:20:30 +00001054 private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener {
Joshua Tsujia19515f2019-02-13 18:02:29 -05001055 @Override
Joshua Tsujid9422832019-03-05 13:32:37 -05001056 public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
1057 if (mStackView != null && mStackView.getBubbleCount() > 0) {
1058 mStackView.post(() -> mStackView.onImeVisibilityChanged(imeVisible, imeHeight));
Joshua Tsujia19515f2019-02-13 18:02:29 -05001059 }
1060 }
Joshua Tsujia19515f2019-02-13 18:02:29 -05001061 }
Mady Mellorc3d6f7d2018-11-07 09:36:56 -08001062}