blob: 8c9946fcfc7bbb9d1ef015fb6fd6e061ebc99f98 [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;
Aran Inkaa4dfa72019-11-18 16:49:07 -050047import android.app.Notification;
Mady Mellor66efd5e2019-05-15 13:38:11 -070048import android.app.NotificationManager;
Mady Mellorca0c24c2019-05-16 16:14:32 -070049import android.app.PendingIntent;
Aran Inkaa4dfa72019-11-18 16:49:07 -050050import android.app.RemoteInput;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080051import android.content.Context;
Aran Inkaa4dfa72019-11-18 16:49:07 -050052import android.content.Intent;
Mady Mellorca0c24c2019-05-16 16:14:32 -070053import android.content.pm.ActivityInfo;
Mady Mellorf3b9fab2019-11-13 17:27:32 -080054import android.content.pm.PackageManager;
Aran Inkaa4dfa72019-11-18 16:49:07 -050055import android.content.pm.ShortcutManager;
Joshua Tsujif418f9e2019-04-04 17:09:53 -040056import android.content.res.Configuration;
Mady Mellord1c78b262018-11-06 18:04:40 -080057import android.graphics.Rect;
Aran Inkaa4dfa72019-11-18 16:49:07 -050058import android.net.Uri;
59import android.os.Handler;
Mark Renoufcecc77b2019-01-30 16:32:24 -050060import android.os.RemoteException;
Mady Mellorb4991e62019-01-10 15:14:51 -080061import android.os.ServiceManager;
Mark Renoufbbcf07f2019-05-09 10:42:43 -040062import android.service.notification.NotificationListenerService.RankingMap;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -040063import android.service.notification.ZenModeConfig;
Mark Renoufc19b4732019-06-26 12:08:33 -040064import android.util.ArraySet;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040065import android.util.Log;
Mark Renouf82a40e62019-05-23 16:16:24 -040066import android.util.Pair;
Mark Renoufc19b4732019-06-26 12:08:33 -040067import android.util.SparseSetArray;
Mark Renoufcecc77b2019-01-30 16:32:24 -050068import android.view.Display;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080069import android.view.ViewGroup;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080070import android.widget.FrameLayout;
71
Mark Renouf08bc42a2019-03-07 13:01:59 -050072import androidx.annotation.IntDef;
Mark Renouf658c6bc2019-01-30 10:26:54 -050073import androidx.annotation.MainThread;
Joshua Tsujic650a142019-05-22 11:31:19 -040074import androidx.annotation.Nullable;
Mark Renouf658c6bc2019-01-30 10:26:54 -050075
Mady Mellorebdbbb92018-11-15 14:36:48 -080076import com.android.internal.annotations.VisibleForTesting;
Mady Mellora54e9fa2019-04-18 13:26:18 -070077import com.android.internal.statusbar.IStatusBarService;
Aran Inkaa4dfa72019-11-18 16:49:07 -050078import com.android.internal.util.ScreenshotHelper;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080079import com.android.systemui.R;
Beverly8fdb5332019-02-04 14:29:49 -050080import com.android.systemui.plugins.statusbar.StatusBarStateController;
Mark Renoufcecc77b2019-01-30 16:32:24 -050081import com.android.systemui.shared.system.ActivityManagerWrapper;
Hongwei Wang43a752b2019-09-17 20:20:30 +000082import com.android.systemui.shared.system.PinnedStackListenerForwarder;
Mark Renoufcecc77b2019-01-30 16:32:24 -050083import com.android.systemui.shared.system.TaskStackChangeListener;
Joshua Tsujia19515f2019-02-13 18:02:29 -050084import com.android.systemui.shared.system.WindowManagerWrapper;
Mark Renoufc19b4732019-06-26 12:08:33 -040085import com.android.systemui.statusbar.NotificationLockscreenUserManager;
Mady Mellorc2ff0112019-03-28 14:18:06 -070086import com.android.systemui.statusbar.NotificationRemoveInterceptor;
Ned Burns01e38212019-01-03 16:32:52 -050087import com.android.systemui.statusbar.notification.NotificationEntryListener;
88import com.android.systemui.statusbar.notification.NotificationEntryManager;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080089import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
Ned Burnsf81c4c42019-01-07 14:10:43 -050090import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Mady Mellor22f2f072019-04-18 13:26:18 -070091import com.android.systemui.statusbar.phone.NotificationGroupManager;
wilsonshihe8321942019-10-18 18:39:46 +080092import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
Mady Mellor7f234902019-10-20 12:06:29 -070093import com.android.systemui.statusbar.phone.ShadeController;
Mady Mellorf3b9fab2019-11-13 17:27:32 -080094import com.android.systemui.statusbar.phone.StatusBar;
Lyn Hanf1c9b8b2019-03-14 16:49:48 -070095import com.android.systemui.statusbar.policy.ConfigurationController;
Aran Inkaa4dfa72019-11-18 16:49:07 -050096import com.android.systemui.statusbar.policy.RemoteInputUriController;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -040097import com.android.systemui.statusbar.policy.ZenModeController;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080098
Mady Mellor70cba7bb2019-07-02 15:06:07 -070099import java.io.FileDescriptor;
100import java.io.PrintWriter;
Mark Renouf08bc42a2019-03-07 13:01:59 -0500101import java.lang.annotation.Retention;
Mark Renoufba5ab512019-05-02 15:21:01 -0400102import java.lang.annotation.Target;
Mady Mellor22f2f072019-04-18 13:26:18 -0700103import java.util.ArrayList;
Aran Inkaa4dfa72019-11-18 16:49:07 -0500104import java.util.HashMap;
Mady Mellorff076eb2019-11-13 10:12:06 -0800105import java.util.HashSet;
Lyn Hanb58c7562020-01-07 14:29:20 -0800106import java.util.List;
Aran Inkaa4dfa72019-11-18 16:49:07 -0500107import java.util.function.Consumer;
Mark Renouf08bc42a2019-03-07 13:01:59 -0500108
Jason Monk27d01a622018-12-10 15:57:09 -0500109import javax.inject.Inject;
110import javax.inject.Singleton;
111
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800112/**
113 * Bubbles are a special type of content that can "float" on top of other apps or System UI.
114 * Bubbles can be expanded to show more content.
115 *
116 * The controller manages addition, removal, and visible state of bubbles on screen.
117 */
Jason Monk27d01a622018-12-10 15:57:09 -0500118@Singleton
Mark Renouf71a3af62019-04-08 15:02:54 -0400119public class BubbleController implements ConfigurationController.ConfigurationListener {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800120
Issei Suzukia8d07312019-06-07 12:56:19 +0200121 private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800122
Mark Renouf08bc42a2019-03-07 13:01:59 -0500123 @Retention(SOURCE)
124 @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
Mark Renoufc19b4732019-06-26 12:08:33 -0400125 DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
Mady Mellor8454ddf2019-08-15 11:16:23 -0700126 DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT})
Mark Renoufba5ab512019-05-02 15:21:01 -0400127 @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
Mark Renouf08bc42a2019-03-07 13:01:59 -0500128 @interface DismissReason {}
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700129
Mark Renouf08bc42a2019-03-07 13:01:59 -0500130 static final int DISMISS_USER_GESTURE = 1;
131 static final int DISMISS_AGED = 2;
132 static final int DISMISS_TASK_FINISHED = 3;
133 static final int DISMISS_BLOCKED = 4;
134 static final int DISMISS_NOTIF_CANCEL = 5;
135 static final int DISMISS_ACCESSIBILITY_ACTION = 6;
Mady Melloraa8fef22019-04-11 13:36:40 -0700136 static final int DISMISS_NO_LONGER_BUBBLE = 7;
Mark Renoufc19b4732019-06-26 12:08:33 -0400137 static final int DISMISS_USER_CHANGED = 8;
Mady Mellor22f2f072019-04-18 13:26:18 -0700138 static final int DISMISS_GROUP_CANCELLED = 9;
Mady Mellor8454ddf2019-08-15 11:16:23 -0700139 static final int DISMISS_INVALID_INTENT = 10;
Mark Renouf08bc42a2019-03-07 13:01:59 -0500140
Ned Burns01e38212019-01-03 16:32:52 -0500141 private final Context mContext;
142 private final NotificationEntryManager mNotificationEntryManager;
Mark Renoufcecc77b2019-01-30 16:32:24 -0500143 private final BubbleTaskStackListener mTaskStackListener;
Mady Mellord1c78b262018-11-06 18:04:40 -0800144 private BubbleStateChangeListener mStateChangeListener;
Mady Mellorcd9b1302018-11-06 18:08:04 -0800145 private BubbleExpandListener mExpandListener;
Issei Suzukic0387542019-03-08 17:31:14 +0100146 @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
Mady Mellor22f2f072019-04-18 13:26:18 -0700147 private final NotificationGroupManager mNotificationGroupManager;
Heemin Seogba6337f2019-12-10 15:34:37 -0800148 private final ShadeController mShadeController;
Aran Inkaa4dfa72019-11-18 16:49:07 -0500149 private final RemoteInputUriController mRemoteInputUriController;
150 private Handler mHandler = new Handler() {};
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800151
Mady Mellor3dff9e62019-02-05 18:12:53 -0800152 private BubbleData mBubbleData;
Joshua Tsujic650a142019-05-22 11:31:19 -0400153 @Nullable private BubbleStackView mStackView;
Mady Mellor247ca2c2019-12-02 16:18:59 -0800154 private BubbleIconFactory mBubbleIconFactory;
Lyn Hanb58c7562020-01-07 14:29:20 -0800155 private int mMaxBubbles;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800156
Mark Renoufc19b4732019-06-26 12:08:33 -0400157 // Tracks the id of the current (foreground) user.
158 private int mCurrentUserId;
159 // Saves notification keys of active bubbles when users are switched.
160 private final SparseSetArray<String> mSavedBubbleKeysPerUser;
161
Mady Mellorff076eb2019-11-13 10:12:06 -0800162 // Saves notification keys of user created "fake" bubbles so that we can allow notifications
163 // like these to bubble by default. Doesn't persist across reboots, not a long-term solution.
164 private final HashSet<String> mUserCreatedBubbles;
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800165 // If we're auto-bubbling bubbles via a whitelist, we need to track which notifs from that app
166 // have been "demoted" back to a notification so that we don't auto-bubbles those again.
167 // Doesn't persist across reboots, not a long-term solution.
168 private final HashSet<String> mUserBlockedBubbles;
Mady Mellorff076eb2019-11-13 10:12:06 -0800169
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800170 // Bubbles get added to the status bar view
wilsonshihe8321942019-10-18 18:39:46 +0800171 private final NotificationShadeWindowController mNotificationShadeWindowController;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400172 private final ZenModeController mZenModeController;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800173 private StatusBarStateListener mStatusBarStateListener;
Aran Inkaa4dfa72019-11-18 16:49:07 -0500174 private final ScreenshotHelper mScreenshotHelper;
175
Lyn Hanb58c7562020-01-07 14:29:20 -0800176 // Callback that updates BubbleOverflowActivity on data change.
177 @Nullable private Runnable mOverflowCallback = null;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800178
Mady Melloraa8fef22019-04-11 13:36:40 -0700179 private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
Mady Mellora54e9fa2019-04-18 13:26:18 -0700180 private IStatusBarService mBarService;
Mady Mellorb4991e62019-01-10 15:14:51 -0800181
Mady Mellord1c78b262018-11-06 18:04:40 -0800182 // Used for determining view rect for touch interaction
183 private Rect mTempRect = new Rect();
184
Mark Renoufc19b4732019-06-26 12:08:33 -0400185 // Listens to user switch so bubbles can be saved and restored.
186 private final NotificationLockscreenUserManager mNotifUserManager;
187
Joshua Tsujif418f9e2019-04-04 17:09:53 -0400188 /** Last known orientation, used to detect orientation changes in {@link #onConfigChanged}. */
189 private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
190
Mady Mellor3df7ab02019-12-09 15:07:10 -0800191 private boolean mInflateSynchronously;
192
Mady Mellor5549dd22018-11-06 18:07:34 -0800193 /**
Mady Mellord1c78b262018-11-06 18:04:40 -0800194 * Listener to be notified when some states of the bubbles change.
195 */
196 public interface BubbleStateChangeListener {
197 /**
198 * Called when the stack has bubbles or no longer has bubbles.
199 */
200 void onHasBubblesChanged(boolean hasBubbles);
201 }
202
Mady Mellorcd9b1302018-11-06 18:08:04 -0800203 /**
204 * Listener to find out about stack expansion / collapse events.
205 */
206 public interface BubbleExpandListener {
207 /**
208 * Called when the expansion state of the bubble stack changes.
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700209 *
Mady Mellorcd9b1302018-11-06 18:08:04 -0800210 * @param isExpanding whether it's expanding or collapsing
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800211 * @param key the notification key associated with bubble being expanded
Mady Mellorcd9b1302018-11-06 18:08:04 -0800212 */
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800213 void onBubbleExpandChanged(boolean isExpanding, String key);
214 }
215
216 /**
Aran Inkaa4dfa72019-11-18 16:49:07 -0500217 * Listener for handling bubble screenshot events.
218 */
219 public interface BubbleScreenshotListener {
220 /**
221 * Called to trigger taking a screenshot and sending the result to a bubble.
222 */
223 void onBubbleScreenshot(Bubble bubble);
224 }
225
226 /**
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800227 * Listens for the current state of the status bar and updates the visibility state
228 * of bubbles as needed.
229 */
230 private class StatusBarStateListener implements StatusBarStateController.StateListener {
231 private int mState;
232 /**
233 * Returns the current status bar state.
234 */
235 public int getCurrentState() {
236 return mState;
237 }
238
239 @Override
240 public void onStateChanged(int newState) {
241 mState = newState;
Mark Renouf71a3af62019-04-08 15:02:54 -0400242 boolean shouldCollapse = (mState != SHADE);
243 if (shouldCollapse) {
244 collapseStack();
245 }
Lyn Han6c40fe72019-05-08 14:06:33 -0700246 updateStack();
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800247 }
Mady Mellorcd9b1302018-11-06 18:08:04 -0800248 }
249
Jason Monk27d01a622018-12-10 15:57:09 -0500250 @Inject
Mady Mellor7f234902019-10-20 12:06:29 -0700251 public BubbleController(Context context,
wilsonshihe8321942019-10-18 18:39:46 +0800252 NotificationShadeWindowController notificationShadeWindowController,
Mady Mellor7f234902019-10-20 12:06:29 -0700253 StatusBarStateController statusBarStateController,
Heemin Seogba6337f2019-12-10 15:34:37 -0800254 ShadeController shadeController,
Mady Mellor7f234902019-10-20 12:06:29 -0700255 BubbleData data,
Mady Melloraa8fef22019-04-11 13:36:40 -0700256 ConfigurationController configurationController,
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400257 NotificationInterruptionStateProvider interruptionStateProvider,
Mark Renoufc19b4732019-06-26 12:08:33 -0400258 ZenModeController zenModeController,
Mady Mellor22f2f072019-04-18 13:26:18 -0700259 NotificationLockscreenUserManager notifUserManager,
Mady Mellor7f234902019-10-20 12:06:29 -0700260 NotificationGroupManager groupManager,
Aran Inkaa4dfa72019-11-18 16:49:07 -0500261 NotificationEntryManager entryManager,
262 RemoteInputUriController remoteInputUriController) {
wilsonshihe8321942019-10-18 18:39:46 +0800263 this(context, notificationShadeWindowController, statusBarStateController, shadeController,
Mady Mellor7f234902019-10-20 12:06:29 -0700264 data, null /* synchronizer */, configurationController, interruptionStateProvider,
Aran Inkaa4dfa72019-11-18 16:49:07 -0500265 zenModeController, notifUserManager, groupManager, entryManager,
266 remoteInputUriController);
Mady Mellor7f234902019-10-20 12:06:29 -0700267 }
268
269 public BubbleController(Context context,
wilsonshihe8321942019-10-18 18:39:46 +0800270 NotificationShadeWindowController notificationShadeWindowController,
Mady Mellor7f234902019-10-20 12:06:29 -0700271 StatusBarStateController statusBarStateController,
Heemin Seogba6337f2019-12-10 15:34:37 -0800272 ShadeController shadeController,
Mady Mellor7f234902019-10-20 12:06:29 -0700273 BubbleData data,
274 @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
275 ConfigurationController configurationController,
276 NotificationInterruptionStateProvider interruptionStateProvider,
277 ZenModeController zenModeController,
278 NotificationLockscreenUserManager notifUserManager,
279 NotificationGroupManager groupManager,
Aran Inkaa4dfa72019-11-18 16:49:07 -0500280 NotificationEntryManager entryManager,
281 RemoteInputUriController remoteInputUriController) {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800282 mContext = context;
Heemin Seogba6337f2019-12-10 15:34:37 -0800283 mShadeController = shadeController;
Mady Melloraa8fef22019-04-11 13:36:40 -0700284 mNotificationInterruptionStateProvider = interruptionStateProvider;
Mark Renoufc19b4732019-06-26 12:08:33 -0400285 mNotifUserManager = notifUserManager;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400286 mZenModeController = zenModeController;
Aran Inkaa4dfa72019-11-18 16:49:07 -0500287 mRemoteInputUriController = remoteInputUriController;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400288 mZenModeController.addCallback(new ZenModeController.Callback() {
289 @Override
290 public void onZenChanged(int zen) {
Mady Mellorb8aaf972019-11-26 10:28:00 -0800291 for (Bubble b : mBubbleData.getBubbles()) {
292 b.setShowDot(b.showInShade(), true /* animate */);
Mady Mellordf48d0a2019-06-25 18:26:46 -0700293 }
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400294 }
295
296 @Override
297 public void onConfigChanged(ZenModeConfig config) {
Mady Mellorb8aaf972019-11-26 10:28:00 -0800298 for (Bubble b : mBubbleData.getBubbles()) {
299 b.setShowDot(b.showInShade(), true /* animate */);
Mady Mellordf48d0a2019-06-25 18:26:46 -0700300 }
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400301 }
302 });
Mady Melloraa8fef22019-04-11 13:36:40 -0700303
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700304 configurationController.addCallback(this /* configurationListener */);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800305
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400306 mBubbleData = data;
307 mBubbleData.setListener(mBubbleDataListener);
Lyn Hanb58c7562020-01-07 14:29:20 -0800308 mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered);
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400309
Mady Mellor7f234902019-10-20 12:06:29 -0700310 mNotificationEntryManager = entryManager;
Ned Burns01e38212019-01-03 16:32:52 -0500311 mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
Mady Mellorc2ff0112019-03-28 14:18:06 -0700312 mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
Mady Mellor22f2f072019-04-18 13:26:18 -0700313 mNotificationGroupManager = groupManager;
Mady Mellor740d85d2019-07-09 15:26:47 -0700314 mNotificationGroupManager.addOnGroupChangeListener(
315 new NotificationGroupManager.OnGroupChangeListener() {
316 @Override
317 public void onGroupSuppressionChanged(
318 NotificationGroupManager.NotificationGroup group,
319 boolean suppressed) {
320 // More notifications could be added causing summary to no longer
321 // be suppressed -- in this case need to remove the key.
322 final String groupKey = group.summary != null
Ned Burns00b4b2d2019-10-17 22:09:27 -0400323 ? group.summary.getSbn().getGroupKey()
Mady Mellor740d85d2019-07-09 15:26:47 -0700324 : null;
325 if (!suppressed && groupKey != null
326 && mBubbleData.isSummarySuppressed(groupKey)) {
327 mBubbleData.removeSuppressedSummary(groupKey);
328 }
329 }
330 });
Mady Mellorb4991e62019-01-10 15:14:51 -0800331
wilsonshihe8321942019-10-18 18:39:46 +0800332 mNotificationShadeWindowController = notificationShadeWindowController;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800333 mStatusBarStateListener = new StatusBarStateListener();
Mady Mellor7f234902019-10-20 12:06:29 -0700334 statusBarStateController.addCallback(mStatusBarStateListener);
Mark Renoufcecc77b2019-01-30 16:32:24 -0500335
Mark Renoufcecc77b2019-01-30 16:32:24 -0500336 mTaskStackListener = new BubbleTaskStackListener();
337 ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800338
Joshua Tsujia19515f2019-02-13 18:02:29 -0500339 try {
340 WindowManagerWrapper.getInstance().addPinnedStackListener(new BubblesImeListener());
341 } catch (RemoteException e) {
342 e.printStackTrace();
343 }
Issei Suzukic0387542019-03-08 17:31:14 +0100344 mSurfaceSynchronizer = synchronizer;
Mady Mellora54e9fa2019-04-18 13:26:18 -0700345
346 mBarService = IStatusBarService.Stub.asInterface(
347 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
Mark Renoufc19b4732019-06-26 12:08:33 -0400348
349 mSavedBubbleKeysPerUser = new SparseSetArray<>();
350 mCurrentUserId = mNotifUserManager.getCurrentUserId();
351 mNotifUserManager.addUserChangedListener(
Steve Elliottb47f1c72019-12-19 12:39:26 -0500352 new NotificationLockscreenUserManager.UserChangedListener() {
353 @Override
354 public void onUserChanged(int newUserId) {
355 BubbleController.this.saveBubbles(mCurrentUserId);
356 mBubbleData.dismissAll(DISMISS_USER_CHANGED);
357 BubbleController.this.restoreBubbles(newUserId);
358 mCurrentUserId = newUserId;
359 }
Mark Renoufc19b4732019-06-26 12:08:33 -0400360 });
Mady Mellorff076eb2019-11-13 10:12:06 -0800361
362 mUserCreatedBubbles = new HashSet<>();
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800363 mUserBlockedBubbles = new HashSet<>();
Aran Inkaa4dfa72019-11-18 16:49:07 -0500364
365 mScreenshotHelper = new ScreenshotHelper(context);
Mady Mellor247ca2c2019-12-02 16:18:59 -0800366 mBubbleIconFactory = new BubbleIconFactory(context);
Mady Mellor5549dd22018-11-06 18:07:34 -0800367 }
368
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400369 /**
Mady Mellor3df7ab02019-12-09 15:07:10 -0800370 * Sets whether to perform inflation on the same thread as the caller. This method should only
371 * be used in tests, not in production.
372 */
373 @VisibleForTesting
374 void setInflateSynchronously(boolean inflateSynchronously) {
375 mInflateSynchronously = inflateSynchronously;
Mady Mellor5549dd22018-11-06 18:07:34 -0800376 }
377
Lyn Hanb58c7562020-01-07 14:29:20 -0800378 void setOverflowCallback(Runnable updateOverflow) {
379 mOverflowCallback = updateOverflow;
380 }
381
382 /**
383 * @return Bubbles for updating overflow.
384 */
385 List<Bubble> getOverflowBubbles() {
386 return mBubbleData.getOverflowBubbles();
387 }
388
389
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400390 /**
391 * BubbleStackView is lazily created by this method the first time a Bubble is added. This
392 * method initializes the stack view and adds it to the StatusBar just above the scrim.
393 */
394 private void ensureStackViewCreated() {
395 if (mStackView == null) {
396 mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer);
wilsonshihe8321942019-10-18 18:39:46 +0800397 ViewGroup nsv = mNotificationShadeWindowController.getNotificationShadeView();
398 int bubbleScrimIndex = nsv.indexOfChild(nsv.findViewById(R.id.scrim_for_bubble));
Lyn Hanbde48202019-05-29 19:18:29 -0700399 int stackIndex = bubbleScrimIndex + 1; // Show stack above bubble scrim.
wilsonshihe8321942019-10-18 18:39:46 +0800400 nsv.addView(mStackView, stackIndex,
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400401 new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
402 if (mExpandListener != null) {
403 mStackView.setExpandListener(mExpandListener);
404 }
Aran Inkaa4dfa72019-11-18 16:49:07 -0500405 if (mBubbleScreenshotListener != null) {
406 mStackView.setBubbleScreenshotListener(mBubbleScreenshotListener);
407 }
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400408 }
409 }
410
Mark Renoufc19b4732019-06-26 12:08:33 -0400411 /**
412 * Records the notification key for any active bubbles. These are used to restore active
413 * bubbles when the user returns to the foreground.
414 *
415 * @param userId the id of the user
416 */
417 private void saveBubbles(@UserIdInt int userId) {
418 // First clear any existing keys that might be stored.
419 mSavedBubbleKeysPerUser.remove(userId);
420 // Add in all active bubbles for the current user.
421 for (Bubble bubble: mBubbleData.getBubbles()) {
422 mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
423 }
424 }
425
426 /**
427 * Promotes existing notifications to Bubbles if they were previously bubbles.
428 *
429 * @param userId the id of the user
430 */
431 private void restoreBubbles(@UserIdInt int userId) {
Mark Renoufc19b4732019-06-26 12:08:33 -0400432 ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId);
433 if (savedBubbleKeys == null) {
434 // There were no bubbles saved for this used.
435 return;
436 }
Evan Laird181de622019-10-24 09:53:02 -0400437 for (NotificationEntry e :
438 mNotificationEntryManager.getActiveNotificationsForCurrentUser()) {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400439 if (savedBubbleKeys.contains(e.getKey())
Mark Renoufc19b4732019-06-26 12:08:33 -0400440 && mNotificationInterruptionStateProvider.shouldBubbleUp(e)
441 && canLaunchInActivityView(mContext, e)) {
442 updateBubble(e, /* suppressFlyout= */ true);
443 }
444 }
445 // Finally, remove the entries for this user now that bubbles are restored.
446 mSavedBubbleKeysPerUser.remove(mCurrentUserId);
447 }
448
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700449 @Override
450 public void onUiModeChanged() {
Mady Mellor247ca2c2019-12-02 16:18:59 -0800451 updateForThemeChanges();
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700452 }
453
454 @Override
455 public void onOverlayChanged() {
Mady Mellor247ca2c2019-12-02 16:18:59 -0800456 updateForThemeChanges();
457 }
458
459 private void updateForThemeChanges() {
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700460 if (mStackView != null) {
Lyn Han02cca812019-04-02 16:27:32 -0700461 mStackView.onThemeChanged();
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700462 }
Mady Mellor3df7ab02019-12-09 15:07:10 -0800463 mBubbleIconFactory = new BubbleIconFactory(mContext);
464 for (Bubble b: mBubbleData.getBubbles()) {
465 // Reload each bubble
466 b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory);
467 }
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700468 }
469
Joshua Tsujif418f9e2019-04-04 17:09:53 -0400470 @Override
471 public void onConfigChanged(Configuration newConfig) {
472 if (mStackView != null && newConfig != null && newConfig.orientation != mOrientation) {
Joshua Tsujif418f9e2019-04-04 17:09:53 -0400473 mOrientation = newConfig.orientation;
Lyn Hanf4730312019-06-18 11:18:58 -0700474 mStackView.onOrientationChanged(newConfig.orientation);
Joshua Tsujif418f9e2019-04-04 17:09:53 -0400475 }
476 }
477
Mady Mellor5549dd22018-11-06 18:07:34 -0800478 /**
Mady Mellord1c78b262018-11-06 18:04:40 -0800479 * Set a listener to be notified when some states of the bubbles change.
480 */
481 public void setBubbleStateChangeListener(BubbleStateChangeListener listener) {
482 mStateChangeListener = listener;
483 }
484
485 /**
Mady Mellorcd9b1302018-11-06 18:08:04 -0800486 * Set a listener to be notified of bubble expand events.
487 */
488 public void setExpandListener(BubbleExpandListener listener) {
Issei Suzukiac9fcb72019-02-04 17:45:57 +0100489 mExpandListener = ((isExpanding, key) -> {
490 if (listener != null) {
491 listener.onBubbleExpandChanged(isExpanding, key);
492 }
wilsonshihe8321942019-10-18 18:39:46 +0800493 mNotificationShadeWindowController.setBubbleExpanded(isExpanding);
Issei Suzukiac9fcb72019-02-04 17:45:57 +0100494 });
Mady Mellorcd9b1302018-11-06 18:08:04 -0800495 if (mStackView != null) {
496 mStackView.setExpandListener(mExpandListener);
497 }
498 }
499
500 /**
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800501 * Whether or not there are bubbles present, regardless of them being visible on the
502 * screen (e.g. if on AOD).
503 */
504 public boolean hasBubbles() {
Mady Mellor3dff9e62019-02-05 18:12:53 -0800505 if (mStackView == null) {
506 return false;
507 }
Mark Renouf71a3af62019-04-08 15:02:54 -0400508 return mBubbleData.hasBubbles();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800509 }
510
511 /**
512 * Whether the stack of bubbles is expanded or not.
513 */
514 public boolean isStackExpanded() {
Mark Renouf71a3af62019-04-08 15:02:54 -0400515 return mBubbleData.isExpanded();
516 }
517
518 /**
519 * Tell the stack of bubbles to expand.
520 */
521 public void expandStack() {
522 mBubbleData.setExpanded(true);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800523 }
524
525 /**
526 * Tell the stack of bubbles to collapse.
527 */
528 public void collapseStack() {
Mark Renouf71a3af62019-04-08 15:02:54 -0400529 mBubbleData.setExpanded(false /* expanded */);
530 }
531
Mady Mellorce23c462019-06-17 17:30:07 -0700532 /**
Mady Mellore28fe102019-07-09 15:33:32 -0700533 * True if either:
534 * (1) There is a bubble associated with the provided key and if its notification is hidden
535 * from the shade.
536 * (2) There is a group summary associated with the provided key that is hidden from the shade
537 * because it has been dismissed but still has child bubbles active.
Mady Mellorce23c462019-06-17 17:30:07 -0700538 *
Mady Mellore28fe102019-07-09 15:33:32 -0700539 * False otherwise.
Mady Mellorce23c462019-06-17 17:30:07 -0700540 */
541 public boolean isBubbleNotificationSuppressedFromShade(String key) {
Mady Mellore28fe102019-07-09 15:33:32 -0700542 boolean isBubbleAndSuppressed = mBubbleData.hasBubbleWithKey(key)
Mady Mellorb8aaf972019-11-26 10:28:00 -0800543 && !mBubbleData.getBubbleWithKey(key).showInShade();
Evan Laird181de622019-10-24 09:53:02 -0400544 NotificationEntry entry = mNotificationEntryManager.getActiveNotificationUnfiltered(key);
Ned Burns00b4b2d2019-10-17 22:09:27 -0400545 String groupKey = entry != null ? entry.getSbn().getGroupKey() : null;
Mady Mellore28fe102019-07-09 15:33:32 -0700546 boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey);
Mady Mellore4348272019-07-29 17:43:36 -0700547 boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey));
548 return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed;
Mady Mellorce23c462019-06-17 17:30:07 -0700549 }
550
Mark Renouf71a3af62019-04-08 15:02:54 -0400551 @VisibleForTesting
552 void selectBubble(String key) {
553 Bubble bubble = mBubbleData.getBubbleWithKey(key);
Mady Mellor247ca2c2019-12-02 16:18:59 -0800554 mBubbleData.setSelectedBubble(bubble);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800555 }
556
Lyn Hanb58c7562020-01-07 14:29:20 -0800557 void promoteBubbleFromOverflow(Bubble bubble) {
558 mBubbleData.promoteBubbleFromOverflow(bubble);
559 }
560
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800561 /**
Mark Renouffec45da2019-03-13 13:24:27 -0400562 * Request the stack expand if needed, then select the specified Bubble as current.
563 *
564 * @param notificationKey the notification key for the bubble to be selected
565 */
566 public void expandStackAndSelectBubble(String notificationKey) {
Mark Renouf71a3af62019-04-08 15:02:54 -0400567 Bubble bubble = mBubbleData.getBubbleWithKey(notificationKey);
568 if (bubble != null) {
569 mBubbleData.setSelectedBubble(bubble);
570 mBubbleData.setExpanded(true);
Mark Renouffec45da2019-03-13 13:24:27 -0400571 }
572 }
573
574 /**
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800575 * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
576 */
Mark Renouf08bc42a2019-03-07 13:01:59 -0500577 void dismissStack(@DismissReason int reason) {
Mark Renouf71a3af62019-04-08 15:02:54 -0400578 mBubbleData.dismissAll(reason);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800579 }
580
581 /**
Mark Renouf041d7262019-02-06 12:09:41 -0500582 * Directs a back gesture at the bubble stack. When opened, the current expanded bubble
583 * is forwarded a back key down/up pair.
584 */
585 public void performBackPressIfNeeded() {
586 if (mStackView != null) {
587 mStackView.performBackPressIfNeeded();
588 }
589 }
590
591 /**
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800592 * Adds or updates a bubble associated with the provided notification entry.
593 *
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400594 * @param notif the notification associated with this bubble.
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800595 */
Mark Renouff97ed462019-04-05 13:46:24 -0400596 void updateBubble(NotificationEntry notif) {
Mady Mellor7f234902019-10-20 12:06:29 -0700597 updateBubble(notif, false /* suppressFlyout */);
Mark Renoufc19b4732019-06-26 12:08:33 -0400598 }
599
600 void updateBubble(NotificationEntry notif, boolean suppressFlyout) {
Mady Mellor7f234902019-10-20 12:06:29 -0700601 updateBubble(notif, suppressFlyout, true /* showInShade */);
602 }
603
604 void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {
Mady Mellor3df7ab02019-12-09 15:07:10 -0800605 if (mStackView == null) {
606 // Lazy init stack view when a bubble is created
607 ensureStackViewCreated();
608 }
Mady Mellor66efd5e2019-05-15 13:38:11 -0700609 // If this is an interruptive notif, mark that it's interrupted
Ned Burns60e94592019-09-06 14:47:25 -0400610 if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
Mady Mellor66efd5e2019-05-15 13:38:11 -0700611 notif.setInterruption();
612 }
Mady Mellor3df7ab02019-12-09 15:07:10 -0800613 Bubble bubble = mBubbleData.getOrCreateBubble(notif);
614 bubble.setInflateSynchronously(mInflateSynchronously);
615 bubble.inflate(
616 b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
617 mContext, mStackView, mBubbleIconFactory);
Mady Mellor7f234902019-10-20 12:06:29 -0700618 }
619
620 /**
621 * Called when a user has indicated that an active notification should be shown as a bubble.
622 * <p>
623 * This method will collapse the shade, create the bubble without a flyout or dot, and suppress
624 * the notification from appearing in the shade.
625 *
626 * @param entry the notification to show as a bubble.
627 */
628 public void onUserCreatedBubbleFromNotification(NotificationEntry entry) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800629 if (DEBUG_EXPERIMENTS || DEBUG_BUBBLE_CONTROLLER) {
630 Log.d(TAG, "onUserCreatedBubble: " + entry.getKey());
631 }
Heemin Seogba6337f2019-12-10 15:34:37 -0800632 mShadeController.collapsePanel(true);
Mady Mellor7f234902019-10-20 12:06:29 -0700633 entry.setFlagBubble(true);
634 updateBubble(entry, true /* suppressFlyout */, false /* showInShade */);
Mady Mellorff076eb2019-11-13 10:12:06 -0800635 mUserCreatedBubbles.add(entry.getKey());
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800636 mUserBlockedBubbles.remove(entry.getKey());
Mady Mellor7f234902019-10-20 12:06:29 -0700637 }
638
639 /**
640 * Called when a user has indicated that an active notification appearing as a bubble should
641 * no longer be shown as a bubble.
642 *
643 * @param entry the notification to no longer show as a bubble.
644 */
645 public void onUserDemotedBubbleFromNotification(NotificationEntry entry) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800646 if (DEBUG_EXPERIMENTS || DEBUG_BUBBLE_CONTROLLER) {
647 Log.d(TAG, "onUserDemotedBubble: " + entry.getKey());
648 }
Mady Mellor7f234902019-10-20 12:06:29 -0700649 entry.setFlagBubble(false);
650 removeBubble(entry.getKey(), DISMISS_BLOCKED);
Mady Mellorff076eb2019-11-13 10:12:06 -0800651 mUserCreatedBubbles.remove(entry.getKey());
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800652 if (BubbleExperimentConfig.isPackageWhitelistedToAutoBubble(
653 mContext, entry.getSbn().getPackageName())) {
654 // This package is whitelist but user demoted the bubble, let's save it so we don't
655 // auto-bubble for the whitelist again.
656 mUserBlockedBubbles.add(entry.getKey());
657 }
Mady Mellorff076eb2019-11-13 10:12:06 -0800658 }
659
660 /**
661 * Whether this bubble was explicitly created by the user via a SysUI affordance.
662 */
663 boolean isUserCreatedBubble(String key) {
664 return mUserCreatedBubbles.contains(key);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800665 }
666
667 /**
668 * Removes the bubble associated with the {@param uri}.
Mark Renouf658c6bc2019-01-30 10:26:54 -0500669 * <p>
670 * Must be called from the main thread.
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800671 */
Mark Renouf658c6bc2019-01-30 10:26:54 -0500672 @MainThread
Mark Renouf08bc42a2019-03-07 13:01:59 -0500673 void removeBubble(String key, int reason) {
Mark Renouf71a3af62019-04-08 15:02:54 -0400674 // TEMP: refactor to change this to pass entry
675 Bubble bubble = mBubbleData.getBubbleWithKey(key);
676 if (bubble != null) {
Mady Mellored99c272019-06-13 15:58:30 -0700677 mBubbleData.notificationEntryRemoved(bubble.getEntry(), reason);
Mady Mellore8e07712019-01-23 12:45:33 -0800678 }
679 }
680
Ned Burns01e38212019-01-03 16:32:52 -0500681 @SuppressWarnings("FieldCanBeLocal")
Mady Mellorc2ff0112019-03-28 14:18:06 -0700682 private final NotificationRemoveInterceptor mRemoveInterceptor =
683 new NotificationRemoveInterceptor() {
684 @Override
685 public boolean onNotificationRemoveRequested(String key, int reason) {
Evan Laird181de622019-10-24 09:53:02 -0400686 NotificationEntry entry =
687 mNotificationEntryManager.getActiveNotificationUnfiltered(key);
Ned Burns00b4b2d2019-10-17 22:09:27 -0400688 String groupKey = entry != null ? entry.getSbn().getGroupKey() : null;
Mady Mellor22f2f072019-04-18 13:26:18 -0700689 ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
690
691 boolean inBubbleData = mBubbleData.hasBubbleWithKey(key);
Mady Mellore28fe102019-07-09 15:33:32 -0700692 boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
693 && mBubbleData.getSummaryKey(groupKey).equals(key));
Mady Mellor22f2f072019-04-18 13:26:18 -0700694 boolean isSummary = entry != null
Ned Burns00b4b2d2019-10-17 22:09:27 -0400695 && entry.getSbn().getNotification().isGroupSummary();
Mady Mellore28fe102019-07-09 15:33:32 -0700696 boolean isSummaryOfBubbles = (isSuppressedSummary || isSummary)
697 && bubbleChildren != null && !bubbleChildren.isEmpty();
Mady Mellor22f2f072019-04-18 13:26:18 -0700698
699 if (!inBubbleData && !isSummaryOfBubbles) {
Mady Mellorc2ff0112019-03-28 14:18:06 -0700700 return false;
701 }
Mady Mellorc2ff0112019-03-28 14:18:06 -0700702
703 final boolean isClearAll = reason == REASON_CANCEL_ALL;
Mady Mellor22f2f072019-04-18 13:26:18 -0700704 final boolean isUserDimiss = reason == REASON_CANCEL || reason == REASON_CLICK;
Mady Mellorc2ff0112019-03-28 14:18:06 -0700705 final boolean isAppCancel = reason == REASON_APP_CANCEL
706 || reason == REASON_APP_CANCEL_ALL;
Mady Mellor22f2f072019-04-18 13:26:18 -0700707 final boolean isSummaryCancel = reason == REASON_GROUP_SUMMARY_CANCELED;
Mady Mellorc2ff0112019-03-28 14:18:06 -0700708
709 // Need to check for !appCancel here because the notification may have
710 // previously been dismissed & entry.isRowDismissed would still be true
Mady Mellorca184aae2019-09-17 16:07:12 -0700711 boolean userRemovedNotif = (entry != null && entry.isRowDismissed() && !isAppCancel)
Mady Mellor22f2f072019-04-18 13:26:18 -0700712 || isClearAll || isUserDimiss || isSummaryCancel;
713
714 if (isSummaryOfBubbles) {
715 return handleSummaryRemovalInterception(entry, userRemovedNotif);
716 }
Mady Mellorc2ff0112019-03-28 14:18:06 -0700717
718 // The bubble notification sticks around in the data as long as the bubble is
719 // not dismissed and the app hasn't cancelled the notification.
Mady Mellor22f2f072019-04-18 13:26:18 -0700720 Bubble bubble = mBubbleData.getBubbleWithKey(key);
Mady Mellorca184aae2019-09-17 16:07:12 -0700721 boolean bubbleExtended = entry != null && entry.isBubble() && userRemovedNotif;
Mady Mellorc2ff0112019-03-28 14:18:06 -0700722 if (bubbleExtended) {
Mady Mellorb8aaf972019-11-26 10:28:00 -0800723 bubble.setShowInShade(false);
724 bubble.setShowDot(false /* show */, true /* animate */);
Beverly85d4c192019-09-30 11:40:39 -0400725 mNotificationEntryManager.updateNotifications(
726 "BubbleController.onNotificationRemoveRequested");
Mady Mellorc2ff0112019-03-28 14:18:06 -0700727 return true;
Mady Mellorff076eb2019-11-13 10:12:06 -0800728 } else if (!userRemovedNotif && entry != null
729 && !isUserCreatedBubble(bubble.getKey())) {
Mady Mellorc2ff0112019-03-28 14:18:06 -0700730 // This wasn't a user removal so we should remove the bubble as well
731 mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
732 return false;
733 }
734 return false;
735 }
736 };
737
Mady Mellor22f2f072019-04-18 13:26:18 -0700738 private boolean handleSummaryRemovalInterception(NotificationEntry summary,
739 boolean userRemovedNotif) {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400740 String groupKey = summary.getSbn().getGroupKey();
Mady Mellor22f2f072019-04-18 13:26:18 -0700741 ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
742
743 if (userRemovedNotif) {
744 // If it's a user dismiss we mark the children to be hidden from the shade.
745 for (int i = 0; i < bubbleChildren.size(); i++) {
746 Bubble bubbleChild = bubbleChildren.get(i);
747 // As far as group manager is concerned, once a child is no longer shown
748 // in the shade, it is essentially removed.
749 mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
Mady Mellorb8aaf972019-11-26 10:28:00 -0800750 bubbleChild.setShowInShade(false);
751 bubbleChild.setShowDot(false /* show */, true /* animate */);
Mady Mellor22f2f072019-04-18 13:26:18 -0700752 }
753 // And since all children are removed, remove the summary.
754 mNotificationGroupManager.onEntryRemoved(summary);
755
756 // If the summary was auto-generated we don't need to keep that notification around
757 // because apps can't cancel it; so we only intercept & suppress real summaries.
Ned Burns00b4b2d2019-10-17 22:09:27 -0400758 boolean isAutogroupSummary = (summary.getSbn().getNotification().flags
Mady Mellor22f2f072019-04-18 13:26:18 -0700759 & FLAG_AUTOGROUP_SUMMARY) != 0;
Mady Mellore28fe102019-07-09 15:33:32 -0700760 if (!isAutogroupSummary) {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400761 mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(),
762 summary.getKey());
Mady Mellore28fe102019-07-09 15:33:32 -0700763 // Tell shade to update for the suppression
Beverly85d4c192019-09-30 11:40:39 -0400764 mNotificationEntryManager.updateNotifications(
765 "BubbleController.handleSummaryRemovalInterception");
Mady Mellore28fe102019-07-09 15:33:32 -0700766 }
Mady Mellor22f2f072019-04-18 13:26:18 -0700767 return !isAutogroupSummary;
768 } else {
Mady Mellore28fe102019-07-09 15:33:32 -0700769 // If it's not a user dismiss it's a cancel.
Mady Mellor84094222019-12-17 15:04:22 -0800770 for (int i = 0; i < bubbleChildren.size(); i++) {
771 // First check if any of these are user-created (i.e. experimental bubbles)
772 if (mUserCreatedBubbles.contains(bubbleChildren.get(i).getKey())) {
773 // Experimental bubble! Intercept the removal.
774 return true;
775 }
776 }
777 // Not an experimental bubble, safe to remove.
Mady Mellore28fe102019-07-09 15:33:32 -0700778 mBubbleData.removeSuppressedSummary(groupKey);
Mady Mellor84094222019-12-17 15:04:22 -0800779 // Remove any associated bubble children with the summary.
Mady Mellor22f2f072019-04-18 13:26:18 -0700780 for (int i = 0; i < bubbleChildren.size(); i++) {
781 Bubble bubbleChild = bubbleChildren.get(i);
782 mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(),
783 DISMISS_GROUP_CANCELLED);
784 }
785 return false;
786 }
787 }
788
Mady Mellorc2ff0112019-03-28 14:18:06 -0700789 @SuppressWarnings("FieldCanBeLocal")
Ned Burns01e38212019-01-03 16:32:52 -0500790 private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
791 @Override
Mady Mellor58dc5192019-12-16 13:49:56 -0800792 public void onNotificationAdded(NotificationEntry entry) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800793 boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800794 boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
Mady Mellor30672942019-12-04 15:43:19 -0800795 boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800796 mContext, entry, previouslyUserCreated, userBlocked);
Mady Mellor7f234902019-10-20 12:06:29 -0700797
Mady Mellorca0c24c2019-05-16 16:14:32 -0700798 if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
Mady Mellor30672942019-12-04 15:43:19 -0800799 && (canLaunchInActivityView(mContext, entry) || wasAdjusted)) {
Mady Mellorea13b232019-12-05 15:55:46 -0800800 if (wasAdjusted && !previouslyUserCreated) {
801 // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated
802 mUserCreatedBubbles.add(entry.getKey());
803 }
Mark Renouff97ed462019-04-05 13:46:24 -0400804 updateBubble(entry);
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800805 }
806 }
807
808 @Override
809 public void onPreEntryUpdated(NotificationEntry entry) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800810 boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800811 boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
Mady Mellor30672942019-12-04 15:43:19 -0800812 boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800813 mContext, entry, previouslyUserCreated, userBlocked);
Mady Mellor7f234902019-10-20 12:06:29 -0700814
Mady Mellorca0c24c2019-05-16 16:14:32 -0700815 boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
Mady Mellor30672942019-12-04 15:43:19 -0800816 && (canLaunchInActivityView(mContext, entry) || wasAdjusted);
Ned Burns00b4b2d2019-10-17 22:09:27 -0400817 if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) {
Mady Melloraa8fef22019-04-11 13:36:40 -0700818 // It was previously a bubble but no longer a bubble -- lets remove it
Ned Burns00b4b2d2019-10-17 22:09:27 -0400819 removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE);
Mady Mellorff40e012019-05-03 15:34:41 -0700820 } else if (shouldBubble) {
Mady Mellorea13b232019-12-05 15:55:46 -0800821 if (wasAdjusted && !previouslyUserCreated) {
822 // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated
823 mUserCreatedBubbles.add(entry.getKey());
824 }
Mark Renouff97ed462019-04-05 13:46:24 -0400825 updateBubble(entry);
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800826 }
827 }
Mark Renoufbbcf07f2019-05-09 10:42:43 -0400828
829 @Override
830 public void onNotificationRankingUpdated(RankingMap rankingMap) {
831 // Forward to BubbleData to block any bubbles which should no longer be shown
832 mBubbleData.notificationRankingUpdated(rankingMap);
833 }
Ned Burns01e38212019-01-03 16:32:52 -0500834 };
835
Mark Renouf71a3af62019-04-08 15:02:54 -0400836 @SuppressWarnings("FieldCanBeLocal")
Mark Renouf3bc5b362019-04-05 14:37:59 -0400837 private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {
Mark Renouf71a3af62019-04-08 15:02:54 -0400838
Mark Renouf3bc5b362019-04-05 14:37:59 -0400839 @Override
Mark Renouf82a40e62019-05-23 16:16:24 -0400840 public void applyUpdate(BubbleData.Update update) {
Lyn Hanb58c7562020-01-07 14:29:20 -0800841 // Update bubbles in overflow.
842 if (mOverflowCallback != null) {
843 mOverflowCallback.run();
844 }
845
Mark Renouf82a40e62019-05-23 16:16:24 -0400846 if (update.addedBubble != null) {
847 mStackView.addBubble(update.addedBubble);
848 }
849
850 // Collapsing? Do this first before remaining steps.
851 if (update.expandedChanged && !update.expanded) {
852 mStackView.setExpanded(false);
853 }
854
855 // Do removals, if any.
Mady Mellor22f2f072019-04-18 13:26:18 -0700856 ArrayList<Pair<Bubble, Integer>> removedBubbles =
857 new ArrayList<>(update.removedBubbles);
858 for (Pair<Bubble, Integer> removed : removedBubbles) {
Mark Renouf82a40e62019-05-23 16:16:24 -0400859 final Bubble bubble = removed.first;
860 @DismissReason final int reason = removed.second;
861 mStackView.removeBubble(bubble);
862
Mark Renoufc19b4732019-06-26 12:08:33 -0400863 // If the bubble is removed for user switching, leave the notification in place.
864 if (reason != DISMISS_USER_CHANGED) {
865 if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
Mady Mellorb8aaf972019-11-26 10:28:00 -0800866 && !bubble.showInShade()) {
Mark Renoufc19b4732019-06-26 12:08:33 -0400867 // The bubble is gone & the notification is gone, time to actually remove it
868 mNotificationEntryManager.performRemoveNotification(
Ned Burns00b4b2d2019-10-17 22:09:27 -0400869 bubble.getEntry().getSbn(), UNDEFINED_DISMISS_REASON);
Mark Renoufc19b4732019-06-26 12:08:33 -0400870 } else {
871 // Update the flag for SysUI
Ned Burns00b4b2d2019-10-17 22:09:27 -0400872 bubble.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
Mady Mellor3a0a1b42019-05-23 06:40:21 -0700873
Mark Renoufc19b4732019-06-26 12:08:33 -0400874 // Make sure NoMan knows it's not a bubble anymore so anyone querying it
875 // will get right result back
876 try {
877 mBarService.onNotificationBubbleChanged(bubble.getKey(),
878 false /* isBubble */);
879 } catch (RemoteException e) {
880 // Bad things have happened
881 }
Mark Renouf82a40e62019-05-23 16:16:24 -0400882 }
Mady Mellor22f2f072019-04-18 13:26:18 -0700883
Mady Mellore28fe102019-07-09 15:33:32 -0700884 // Check if removed bubble has an associated suppressed group summary that needs
885 // to be removed now.
Ned Burns00b4b2d2019-10-17 22:09:27 -0400886 final String groupKey = bubble.getEntry().getSbn().getGroupKey();
Mady Mellore28fe102019-07-09 15:33:32 -0700887 if (mBubbleData.isSummarySuppressed(groupKey)
888 && mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
889 // Time to actually remove the summary.
890 String notifKey = mBubbleData.getSummaryKey(groupKey);
891 mBubbleData.removeSuppressedSummary(groupKey);
892 NotificationEntry entry =
Evan Laird181de622019-10-24 09:53:02 -0400893 mNotificationEntryManager.getActiveNotificationUnfiltered(notifKey);
Mady Mellore28fe102019-07-09 15:33:32 -0700894 mNotificationEntryManager.performRemoveNotification(
Ned Burns00b4b2d2019-10-17 22:09:27 -0400895 entry.getSbn(), UNDEFINED_DISMISS_REASON);
Mady Mellore28fe102019-07-09 15:33:32 -0700896 }
897
Mady Mellor22f2f072019-04-18 13:26:18 -0700898 // Check if summary should be removed from NoManGroup
899 NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(
Ned Burns00b4b2d2019-10-17 22:09:27 -0400900 bubble.getEntry().getSbn());
Mady Mellor22f2f072019-04-18 13:26:18 -0700901 if (summary != null) {
902 ArrayList<NotificationEntry> summaryChildren =
Ned Burns00b4b2d2019-10-17 22:09:27 -0400903 mNotificationGroupManager.getLogicalChildren(summary.getSbn());
904 boolean isSummaryThisNotif = summary.getKey().equals(
905 bubble.getEntry().getKey());
Mady Mellore4348272019-07-29 17:43:36 -0700906 if (!isSummaryThisNotif
907 && (summaryChildren == null || summaryChildren.isEmpty())) {
Mady Mellor22f2f072019-04-18 13:26:18 -0700908 mNotificationEntryManager.performRemoveNotification(
Ned Burns00b4b2d2019-10-17 22:09:27 -0400909 summary.getSbn(), UNDEFINED_DISMISS_REASON);
Mady Mellor22f2f072019-04-18 13:26:18 -0700910 }
911 }
Mady Mellora54e9fa2019-04-18 13:26:18 -0700912 }
913 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400914
Mark Renouf82a40e62019-05-23 16:16:24 -0400915 if (update.updatedBubble != null) {
916 mStackView.updateBubble(update.updatedBubble);
Mark Renouf71a3af62019-04-08 15:02:54 -0400917 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400918
Lyn Hanb58c7562020-01-07 14:29:20 -0800919 // At this point, the correct bubbles are inflated in the stack.
920 // Make sure the order in bubble data is reflected in bubble row.
Mark Renouf82a40e62019-05-23 16:16:24 -0400921 if (update.orderChanged) {
922 mStackView.updateBubbleOrder(update.bubbles);
Mark Renoufba5ab512019-05-02 15:21:01 -0400923 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400924
Mark Renouf82a40e62019-05-23 16:16:24 -0400925 if (update.selectionChanged) {
926 mStackView.setSelectedBubble(update.selectedBubble);
Mady Mellor740d85d2019-07-09 15:26:47 -0700927 if (update.selectedBubble != null) {
928 mNotificationGroupManager.updateSuppression(
929 update.selectedBubble.getEntry());
930 }
Mark Renouf71a3af62019-04-08 15:02:54 -0400931 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400932
Mark Renouf82a40e62019-05-23 16:16:24 -0400933 // Expanding? Apply this last.
934 if (update.expandedChanged && update.expanded) {
935 mStackView.setExpanded(true);
Mark Renouf71a3af62019-04-08 15:02:54 -0400936 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400937
Beverly85d4c192019-09-30 11:40:39 -0400938 mNotificationEntryManager.updateNotifications(
939 "BubbleData.Listener.applyUpdate");
Lyn Han6c40fe72019-05-08 14:06:33 -0700940 updateStack();
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400941
Issei Suzukia8d07312019-06-07 12:56:19 +0200942 if (DEBUG_BUBBLE_CONTROLLER) {
Lyn Hanb58c7562020-01-07 14:29:20 -0800943 Log.d(TAG, "\n[BubbleData] bubbles:");
Lyn Han767d70e2019-12-10 18:02:23 -0800944 Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getBubbles(),
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400945 mBubbleData.getSelectedBubble()));
946
947 if (mStackView != null) {
Lyn Hanb58c7562020-01-07 14:29:20 -0800948 Log.d(TAG, "\n[BubbleStackView]");
Lyn Han767d70e2019-12-10 18:02:23 -0800949 Log.d(TAG, BubbleDebugConfig.formatBubblesString(mStackView.getBubblesOnScreen(),
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400950 mStackView.getExpandedBubble()));
951 }
Lyn Hanb58c7562020-01-07 14:29:20 -0800952 Log.d(TAG, "\n[BubbleData] overflow:");
953 Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getOverflowBubbles(),
954 null));
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400955 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400956 }
957 };
958
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800959 /**
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400960 * Lets any listeners know if bubble state has changed.
Lyn Han6c40fe72019-05-08 14:06:33 -0700961 * Updates the visibility of the bubbles based on current state.
962 * Does not un-bubble, just hides or un-hides. Notifies any
963 * {@link BubbleStateChangeListener}s of visibility changes.
964 * Updates stack description for TalkBack focus.
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800965 */
Lyn Han6c40fe72019-05-08 14:06:33 -0700966 public void updateStack() {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800967 if (mStackView == null) {
968 return;
Mady Mellord1c78b262018-11-06 18:04:40 -0800969 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800970 if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) {
971 // Bubbles only appear in unlocked shade
972 mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE);
Mady Mellor698d9e82019-08-01 23:11:53 +0000973 } else if (mStackView != null) {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800974 mStackView.setVisibility(INVISIBLE);
Mady Mellor5549dd22018-11-06 18:07:34 -0800975 }
Lyn Han6c40fe72019-05-08 14:06:33 -0700976
Mady Mellor698d9e82019-08-01 23:11:53 +0000977 // Let listeners know if bubble state changed.
wilsonshihe8321942019-10-18 18:39:46 +0800978 boolean hadBubbles = mNotificationShadeWindowController.getBubblesShowing();
Mady Mellor698d9e82019-08-01 23:11:53 +0000979 boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE;
wilsonshihe8321942019-10-18 18:39:46 +0800980 mNotificationShadeWindowController.setBubblesShowing(hasBubblesShowing);
Lyn Han6c40fe72019-05-08 14:06:33 -0700981 if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
982 mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
983 }
984
985 mStackView.updateContentDescription();
Mady Mellord1c78b262018-11-06 18:04:40 -0800986 }
987
988 /**
989 * Rect indicating the touchable region for the bubble stack / expanded stack.
990 */
991 public Rect getTouchableRegion() {
992 if (mStackView == null || mStackView.getVisibility() != VISIBLE) {
993 return null;
994 }
995 mStackView.getBoundsOnScreen(mTempRect);
996 return mTempRect;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800997 }
998
Mady Mellor390bff42019-04-05 15:09:01 -0700999 /**
1000 * The display id of the expanded view, if the stack is expanded and not occluded by the
1001 * status bar, otherwise returns {@link Display#INVALID_DISPLAY}.
1002 */
1003 public int getExpandedDisplayId(Context context) {
Issei Suzukicac2a502019-04-16 16:52:50 +02001004 final Bubble bubble = getExpandedBubble(context);
1005 return bubble != null ? bubble.getDisplayId() : INVALID_DISPLAY;
1006 }
1007
1008 @Nullable
1009 private Bubble getExpandedBubble(Context context) {
Joel Galenson4071ddb2019-04-18 13:30:45 -07001010 if (mStackView == null) {
Issei Suzukicac2a502019-04-16 16:52:50 +02001011 return null;
Joel Galenson4071ddb2019-04-18 13:30:45 -07001012 }
Issei Suzukicac2a502019-04-16 16:52:50 +02001013 final boolean defaultDisplay = context.getDisplay() != null
Mady Mellor390bff42019-04-05 15:09:01 -07001014 && context.getDisplay().getDisplayId() == DEFAULT_DISPLAY;
Issei Suzukicac2a502019-04-16 16:52:50 +02001015 final Bubble expandedBubble = mStackView.getExpandedBubble();
1016 if (defaultDisplay && expandedBubble != null && isStackExpanded()
wilsonshihe8321942019-10-18 18:39:46 +08001017 && !mNotificationShadeWindowController.getPanelExpanded()) {
Issei Suzukicac2a502019-04-16 16:52:50 +02001018 return expandedBubble;
Mady Mellor390bff42019-04-05 15:09:01 -07001019 }
Issei Suzukicac2a502019-04-16 16:52:50 +02001020 return null;
Mady Mellor390bff42019-04-05 15:09:01 -07001021 }
1022
Mady Mellorf6e3ac02019-01-29 10:37:52 -08001023 @VisibleForTesting
1024 BubbleStackView getStackView() {
1025 return mStackView;
1026 }
1027
Mady Mellor70cba7bb2019-07-02 15:06:07 -07001028 /**
1029 * Description of current bubble state.
1030 */
1031 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1032 pw.println("BubbleController state:");
1033 mBubbleData.dump(fd, pw, args);
1034 pw.println();
Joshua Tsuji395bcfe2019-07-02 19:23:23 -04001035 if (mStackView != null) {
1036 mStackView.dump(fd, pw, args);
1037 }
1038 pw.println();
Mady Mellor70cba7bb2019-07-02 15:06:07 -07001039 }
1040
Mady Mellore80930e2019-03-21 16:00:45 -07001041 /**
Mark Renoufcecc77b2019-01-30 16:32:24 -05001042 * This task stack listener is responsible for responding to tasks moved to the front
1043 * which are on the default (main) display. When this happens, expanded bubbles must be
1044 * collapsed so the user may interact with the app which was just moved to the front.
1045 * <p>
1046 * This listener is registered with SystemUI's ActivityManagerWrapper which dispatches
1047 * these calls via a main thread Handler.
1048 */
1049 @MainThread
1050 private class BubbleTaskStackListener extends TaskStackChangeListener {
1051
Mark Renoufcecc77b2019-01-30 16:32:24 -05001052 @Override
Mark Renoufc808f062019-02-07 15:20:37 -05001053 public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
1054 if (mStackView != null && taskInfo.displayId == Display.DEFAULT_DISPLAY) {
Mady Mellor047e24e2019-08-05 11:35:40 -07001055 if (!mStackView.isExpansionAnimating()) {
1056 mBubbleData.setExpanded(false);
1057 }
Mark Renoufcecc77b2019-01-30 16:32:24 -05001058 }
1059 }
1060
Mark Renoufcecc77b2019-01-30 16:32:24 -05001061 @Override
Mark Renoufa12e8762019-03-07 15:43:01 -05001062 public void onActivityLaunchOnSecondaryDisplayRerouted() {
Mark Renoufcecc77b2019-01-30 16:32:24 -05001063 if (mStackView != null) {
Mark Renouf71a3af62019-04-08 15:02:54 -04001064 mBubbleData.setExpanded(false);
Mark Renoufcecc77b2019-01-30 16:32:24 -05001065 }
1066 }
Mark Renouf446251d2019-04-26 10:22:41 -04001067
1068 @Override
1069 public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
1070 if (mStackView != null && taskInfo.displayId == getExpandedDisplayId(mContext)) {
1071 mBubbleData.setExpanded(false);
1072 }
1073 }
Issei Suzukicac2a502019-04-16 16:52:50 +02001074
1075 @Override
1076 public void onSingleTaskDisplayDrawn(int displayId) {
Mady Mellor5186b132019-09-16 17:55:48 -07001077 final Bubble expandedBubble = mStackView != null
1078 ? mStackView.getExpandedBubble()
1079 : null;
Issei Suzukicac2a502019-04-16 16:52:50 +02001080 if (expandedBubble != null && expandedBubble.getDisplayId() == displayId) {
1081 expandedBubble.setContentVisibility(true);
1082 }
1083 }
Issei Suzuki734bc942019-06-05 13:59:52 +02001084
1085 @Override
1086 public void onSingleTaskDisplayEmpty(int displayId) {
Mady Mellor5186b132019-09-16 17:55:48 -07001087 final Bubble expandedBubble = mStackView != null
1088 ? mStackView.getExpandedBubble()
1089 : null;
Mady Mellorca184aae2019-09-17 16:07:12 -07001090 int expandedId = expandedBubble != null ? expandedBubble.getDisplayId() : -1;
1091 if (mStackView != null && mStackView.isExpanded() && expandedId == displayId) {
Issei Suzuki734bc942019-06-05 13:59:52 +02001092 mBubbleData.setExpanded(false);
Issei Suzuki734bc942019-06-05 13:59:52 +02001093 }
Mady Mellorca184aae2019-09-17 16:07:12 -07001094 mBubbleData.notifyDisplayEmpty(displayId);
Issei Suzuki734bc942019-06-05 13:59:52 +02001095 }
Mark Renoufcecc77b2019-01-30 16:32:24 -05001096 }
1097
Mady Mellorca0c24c2019-05-16 16:14:32 -07001098 /**
1099 * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
1100 *
1101 * Keep checks in sync with NotificationManagerService#canLaunchInActivityView. Typically
1102 * that should filter out any invalid bubbles, but should protect SysUI side just in case.
1103 *
1104 * @param context the context to use.
1105 * @param entry the entry to bubble.
1106 */
1107 static boolean canLaunchInActivityView(Context context, NotificationEntry entry) {
1108 PendingIntent intent = entry.getBubbleMetadata() != null
Mady Mellor2ac2d3a2020-01-08 17:18:54 -08001109 ? entry.getBubbleMetadata().getBubbleIntent()
Mady Mellorca0c24c2019-05-16 16:14:32 -07001110 : null;
Mady Mellor2ac2d3a2020-01-08 17:18:54 -08001111 if (entry.getBubbleMetadata() != null
1112 && entry.getBubbleMetadata().getShortcutId() != null) {
1113 return true;
1114 }
Mady Mellorca0c24c2019-05-16 16:14:32 -07001115 if (intent == null) {
Mady Mellor7f234902019-10-20 12:06:29 -07001116 Log.w(TAG, "Unable to create bubble -- no intent: " + entry.getKey());
Mady Mellorca0c24c2019-05-16 16:14:32 -07001117 return false;
1118 }
Mady Mellorf3b9fab2019-11-13 17:27:32 -08001119 PackageManager packageManager = StatusBar.getPackageManagerForUser(
1120 context, entry.getSbn().getUser().getIdentifier());
Mady Mellorca0c24c2019-05-16 16:14:32 -07001121 ActivityInfo info =
Mady Mellorf3b9fab2019-11-13 17:27:32 -08001122 intent.getIntent().resolveActivityInfo(packageManager, 0);
Mady Mellorca0c24c2019-05-16 16:14:32 -07001123 if (info == null) {
Mady Mellor7f234902019-10-20 12:06:29 -07001124 Log.w(TAG, "Unable to send as bubble, "
1125 + entry.getKey() + " couldn't find activity info for intent: "
Mady Mellorca0c24c2019-05-16 16:14:32 -07001126 + intent);
1127 return false;
1128 }
1129 if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
Mady Mellor7f234902019-10-20 12:06:29 -07001130 Log.w(TAG, "Unable to send as bubble, "
1131 + entry.getKey() + " activity is not resizable for intent: "
Mady Mellorca0c24c2019-05-16 16:14:32 -07001132 + intent);
1133 return false;
1134 }
Mady Mellorca0c24c2019-05-16 16:14:32 -07001135 return true;
1136 }
1137
Joshua Tsujia19515f2019-02-13 18:02:29 -05001138 /** PinnedStackListener that dispatches IME visibility updates to the stack. */
Hongwei Wang43a752b2019-09-17 20:20:30 +00001139 private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener {
Joshua Tsujia19515f2019-02-13 18:02:29 -05001140 @Override
Joshua Tsujid9422832019-03-05 13:32:37 -05001141 public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
1142 if (mStackView != null && mStackView.getBubbleCount() > 0) {
1143 mStackView.post(() -> mStackView.onImeVisibilityChanged(imeVisible, imeHeight));
Joshua Tsujia19515f2019-02-13 18:02:29 -05001144 }
1145 }
Joshua Tsujia19515f2019-02-13 18:02:29 -05001146 }
Aran Inkaa4dfa72019-11-18 16:49:07 -05001147
1148 // TODO: Copied from RemoteInputView. Consolidate RemoteInput intent logic.
1149 private Intent prepareRemoteInputFromData(String contentType, Uri data,
1150 RemoteInput remoteInput, NotificationEntry entry) {
1151 HashMap<String, Uri> results = new HashMap<>();
1152 results.put(contentType, data);
1153 mRemoteInputUriController.grantInlineReplyUriPermission(entry.getSbn(), data);
1154 Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
1155 RemoteInput.addDataResultToIntent(remoteInput, fillInIntent, results);
1156
1157 return fillInIntent;
1158 }
1159
1160 // TODO: Copied from RemoteInputView. Consolidate RemoteInput intent logic.
1161 private void sendRemoteInput(Intent intent, NotificationEntry entry,
1162 PendingIntent pendingIntent) {
1163 // Tell ShortcutManager that this package has been "activated". ShortcutManager
1164 // will reset the throttling for this package.
1165 // Strictly speaking, the intent receiver may be different from the notification publisher,
1166 // but that's an edge case, and also because we can't always know which package will receive
1167 // an intent, so we just reset for the publisher.
1168 mContext.getSystemService(ShortcutManager.class).onApplicationActive(
1169 entry.getSbn().getPackageName(),
1170 entry.getSbn().getUser().getIdentifier());
1171
1172 try {
1173 pendingIntent.send(mContext, 0, intent);
1174 } catch (PendingIntent.CanceledException e) {
1175 Log.i(TAG, "Unable to send remote input result", e);
1176 }
1177 }
1178
1179 private void sendScreenshotToBubble(Bubble bubble) {
Aran Ink141a8152019-12-12 13:31:23 -05001180 mScreenshotHelper.takeScreenshot(
1181 android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
1182 true /* hasStatus */,
1183 true /* hasNav */,
1184 mHandler,
1185 new Consumer<Uri>() {
1186 @Override
1187 public void accept(Uri uri) {
1188 if (uri != null) {
1189 NotificationEntry entry = bubble.getEntry();
1190 Pair<RemoteInput, Notification.Action> pair = entry.getSbn()
1191 .getNotification().findRemoteInputActionPair(false);
1192 if (pair != null) {
1193 RemoteInput remoteInput = pair.first;
1194 Notification.Action action = pair.second;
1195 Intent dataIntent = prepareRemoteInputFromData("image/png", uri,
1196 remoteInput, entry);
1197 sendRemoteInput(dataIntent, entry, action.actionIntent);
1198 mBubbleData.setSelectedBubble(bubble);
1199 mBubbleData.setExpanded(true);
1200 } else {
1201 Log.w(TAG, "No RemoteInput found for notification: "
1202 + entry.getSbn().getKey());
Aran Inkaa4dfa72019-11-18 16:49:07 -05001203 }
Aran Ink141a8152019-12-12 13:31:23 -05001204 }
1205 }
1206 });
Aran Inkaa4dfa72019-11-18 16:49:07 -05001207 }
1208
1209 private final BubbleScreenshotListener mBubbleScreenshotListener =
1210 bubble -> sendScreenshotToBubble(bubble);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -08001211}