blob: f80ca68d4e51ce145fb93ac459bd321eab9edf47 [file] [log] [blame]
Mady Mellorc3d6f7d2018-11-07 09:36:56 -08001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.bubbles;
18
Mady Mellor3a0a1b42019-05-23 06:40:21 -070019import static android.app.Notification.FLAG_BUBBLE;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -040020import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
21import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
22import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
Mady Mellorca0c24c2019-05-16 16:14:32 -070023import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
Mady Mellorc2ff0112019-03-28 14:18:06 -070024import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
25import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
26import static android.service.notification.NotificationListenerService.REASON_CANCEL;
27import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
Mady Mellor390bff42019-04-05 15:09:01 -070028import static android.view.Display.DEFAULT_DISPLAY;
29import static android.view.Display.INVALID_DISPLAY;
Mady Mellord1c78b262018-11-06 18:04:40 -080030import static android.view.View.INVISIBLE;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080031import static android.view.View.VISIBLE;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080032import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080033
Mady Mellor3f2efdb2018-11-21 11:30:45 -080034import static com.android.systemui.statusbar.StatusBarState.SHADE;
Mady Mellor1a4e86f2019-05-03 16:07:23 -070035import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080036
Mark Renoufba5ab512019-05-02 15:21:01 -040037import static java.lang.annotation.ElementType.FIELD;
38import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
39import static java.lang.annotation.ElementType.PARAMETER;
Mark Renouf08bc42a2019-03-07 13:01:59 -050040import static java.lang.annotation.RetentionPolicy.SOURCE;
41
Mady Mellore80930e2019-03-21 16:00:45 -070042import android.app.ActivityManager;
Mark Renoufc808f062019-02-07 15:20:37 -050043import android.app.ActivityManager.RunningTaskInfo;
Mark Renoufcecc77b2019-01-30 16:32:24 -050044import android.app.ActivityTaskManager;
45import android.app.IActivityTaskManager;
Mady Mellor5549dd22018-11-06 18:07:34 -080046import android.app.Notification;
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;
Joshua Tsujia19515f2019-02-13 18:02:29 -050051import android.content.pm.ParceledListSlice;
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;
Mady Mellorceced172018-11-27 11:18:39 -080056import android.provider.Settings;
Mady Mellor5549dd22018-11-06 18:07:34 -080057import android.service.notification.StatusBarNotification;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -040058import android.service.notification.ZenModeConfig;
Mark Renouf9ba6cea2019-04-17 11:53:50 -040059import android.util.Log;
Mark Renouf82a40e62019-05-23 16:16:24 -040060import android.util.Pair;
Mark Renoufcecc77b2019-01-30 16:32:24 -050061import android.view.Display;
Joshua Tsujia19515f2019-02-13 18:02:29 -050062import android.view.IPinnedStackController;
63import android.view.IPinnedStackListener;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080064import android.view.ViewGroup;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080065import android.widget.FrameLayout;
66
Mark Renouf08bc42a2019-03-07 13:01:59 -050067import androidx.annotation.IntDef;
Mark Renouf658c6bc2019-01-30 10:26:54 -050068import androidx.annotation.MainThread;
Joshua Tsujic650a142019-05-22 11:31:19 -040069import androidx.annotation.Nullable;
Mark Renouf658c6bc2019-01-30 10:26:54 -050070
Mady Mellorebdbbb92018-11-15 14:36:48 -080071import com.android.internal.annotations.VisibleForTesting;
Mady Mellora54e9fa2019-04-18 13:26:18 -070072import com.android.internal.statusbar.IStatusBarService;
Ned Burns01e38212019-01-03 16:32:52 -050073import com.android.systemui.Dependency;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080074import com.android.systemui.R;
Beverly8fdb5332019-02-04 14:29:49 -050075import com.android.systemui.plugins.statusbar.StatusBarStateController;
Mark Renoufcecc77b2019-01-30 16:32:24 -050076import com.android.systemui.shared.system.ActivityManagerWrapper;
77import com.android.systemui.shared.system.TaskStackChangeListener;
Joshua Tsujia19515f2019-02-13 18:02:29 -050078import com.android.systemui.shared.system.WindowManagerWrapper;
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;
Ned Burns1a5e22f2019-02-14 15:11:52 -050084import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080085import com.android.systemui.statusbar.phone.StatusBarWindowController;
Lyn Hanf1c9b8b2019-03-14 16:49:48 -070086import com.android.systemui.statusbar.policy.ConfigurationController;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -040087import com.android.systemui.statusbar.policy.ZenModeController;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080088
Mark Renouf08bc42a2019-03-07 13:01:59 -050089import java.lang.annotation.Retention;
Mark Renoufba5ab512019-05-02 15:21:01 -040090import java.lang.annotation.Target;
Mady Mellore80930e2019-03-21 16:00:45 -070091import java.util.List;
Mark Renouf08bc42a2019-03-07 13:01:59 -050092
Jason Monk27d01a622018-12-10 15:57:09 -050093import javax.inject.Inject;
94import javax.inject.Singleton;
95
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080096/**
97 * Bubbles are a special type of content that can "float" on top of other apps or System UI.
98 * Bubbles can be expanded to show more content.
99 *
100 * The controller manages addition, removal, and visible state of bubbles on screen.
101 */
Jason Monk27d01a622018-12-10 15:57:09 -0500102@Singleton
Mark Renouf71a3af62019-04-08 15:02:54 -0400103public class BubbleController implements ConfigurationController.ConfigurationListener {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800104
105 private static final String TAG = "BubbleController";
Mark Renoufba5ab512019-05-02 15:21:01 -0400106 private static final boolean DEBUG = false;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800107
Mark Renouf08bc42a2019-03-07 13:01:59 -0500108 @Retention(SOURCE)
109 @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
Mady Melloraa8fef22019-04-11 13:36:40 -0700110 DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE})
Mark Renoufba5ab512019-05-02 15:21:01 -0400111 @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
Mark Renouf08bc42a2019-03-07 13:01:59 -0500112 @interface DismissReason {}
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700113
Mark Renouf08bc42a2019-03-07 13:01:59 -0500114 static final int DISMISS_USER_GESTURE = 1;
115 static final int DISMISS_AGED = 2;
116 static final int DISMISS_TASK_FINISHED = 3;
117 static final int DISMISS_BLOCKED = 4;
118 static final int DISMISS_NOTIF_CANCEL = 5;
119 static final int DISMISS_ACCESSIBILITY_ACTION = 6;
Mady Melloraa8fef22019-04-11 13:36:40 -0700120 static final int DISMISS_NO_LONGER_BUBBLE = 7;
Mark Renouf08bc42a2019-03-07 13:01:59 -0500121
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400122 public static final int MAX_BUBBLES = 5; // TODO: actually enforce this
Joshua Tsuji25a4b7b2019-03-22 14:11:06 -0400123
Mady Mellor5549dd22018-11-06 18:07:34 -0800124 // Enables some subset of notifs to automatically become bubbles
Mady Mellorca0c24c2019-05-16 16:14:32 -0700125 public static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
Mady Mellor5549dd22018-11-06 18:07:34 -0800126
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800127 /** Flag to enable or disable the entire feature */
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800128 private static final String ENABLE_BUBBLES = "experiment_enable_bubbles";
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800129 /** Auto bubble flags set whether different notif types should be presented as a bubble */
Mady Mellorceced172018-11-27 11:18:39 -0800130 private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging";
131 private static final String ENABLE_AUTO_BUBBLE_ONGOING = "experiment_autobubble_ongoing";
132 private static final String ENABLE_AUTO_BUBBLE_ALL = "experiment_autobubble_all";
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800133
134 /** Use an activityView for an auto-bubbled notifs if it has an appropriate content intent */
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500135 private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent";
Mady Mellorceced172018-11-27 11:18:39 -0800136
Joshua Tsuji010c2b12019-02-25 18:11:25 -0500137 private static final String BUBBLE_STIFFNESS = "experiment_bubble_stiffness";
138 private static final String BUBBLE_BOUNCINESS = "experiment_bubble_bounciness";
139
Ned Burns01e38212019-01-03 16:32:52 -0500140 private final Context mContext;
141 private final NotificationEntryManager mNotificationEntryManager;
Mark Renoufcecc77b2019-01-30 16:32:24 -0500142 private final IActivityTaskManager mActivityTaskManager;
143 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 Mellorc3d6f7d2018-11-07 09:36:56 -0800147
Mady Mellor3dff9e62019-02-05 18:12:53 -0800148 private BubbleData mBubbleData;
Joshua Tsujic650a142019-05-22 11:31:19 -0400149 @Nullable private BubbleStackView mStackView;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800150
151 // Bubbles get added to the status bar view
Ned Burns01e38212019-01-03 16:32:52 -0500152 private final StatusBarWindowController mStatusBarWindowController;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400153 private final ZenModeController mZenModeController;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800154 private StatusBarStateListener mStatusBarStateListener;
155
Mady Melloraa8fef22019-04-11 13:36:40 -0700156 private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
Mady Mellora54e9fa2019-04-18 13:26:18 -0700157 private IStatusBarService mBarService;
Mady Mellorb4991e62019-01-10 15:14:51 -0800158
Mady Mellord1c78b262018-11-06 18:04:40 -0800159 // Used for determining view rect for touch interaction
160 private Rect mTempRect = new Rect();
161
Joshua Tsujif418f9e2019-04-04 17:09:53 -0400162 /** Last known orientation, used to detect orientation changes in {@link #onConfigChanged}. */
163 private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
164
Mady Mellor5549dd22018-11-06 18:07:34 -0800165 /**
Mady Mellord1c78b262018-11-06 18:04:40 -0800166 * Listener to be notified when some states of the bubbles change.
167 */
168 public interface BubbleStateChangeListener {
169 /**
170 * Called when the stack has bubbles or no longer has bubbles.
171 */
172 void onHasBubblesChanged(boolean hasBubbles);
173 }
174
Mady Mellorcd9b1302018-11-06 18:08:04 -0800175 /**
176 * Listener to find out about stack expansion / collapse events.
177 */
178 public interface BubbleExpandListener {
179 /**
180 * Called when the expansion state of the bubble stack changes.
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700181 *
Mady Mellorcd9b1302018-11-06 18:08:04 -0800182 * @param isExpanding whether it's expanding or collapsing
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800183 * @param key the notification key associated with bubble being expanded
Mady Mellorcd9b1302018-11-06 18:08:04 -0800184 */
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800185 void onBubbleExpandChanged(boolean isExpanding, String key);
186 }
187
188 /**
189 * Listens for the current state of the status bar and updates the visibility state
190 * of bubbles as needed.
191 */
192 private class StatusBarStateListener implements StatusBarStateController.StateListener {
193 private int mState;
194 /**
195 * Returns the current status bar state.
196 */
197 public int getCurrentState() {
198 return mState;
199 }
200
201 @Override
202 public void onStateChanged(int newState) {
203 mState = newState;
Mark Renouf71a3af62019-04-08 15:02:54 -0400204 boolean shouldCollapse = (mState != SHADE);
205 if (shouldCollapse) {
206 collapseStack();
207 }
Lyn Han6c40fe72019-05-08 14:06:33 -0700208 updateStack();
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800209 }
Mady Mellorcd9b1302018-11-06 18:08:04 -0800210 }
211
Jason Monk27d01a622018-12-10 15:57:09 -0500212 @Inject
Mady Mellorcfd06c12019-02-13 14:32:12 -0800213 public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
Mady Melloraa8fef22019-04-11 13:36:40 -0700214 BubbleData data, ConfigurationController configurationController,
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400215 NotificationInterruptionStateProvider interruptionStateProvider,
216 ZenModeController zenModeController) {
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700217 this(context, statusBarWindowController, data, null /* synchronizer */,
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400218 configurationController, interruptionStateProvider, zenModeController);
Issei Suzukic0387542019-03-08 17:31:14 +0100219 }
220
221 public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700222 BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
Mady Melloraa8fef22019-04-11 13:36:40 -0700223 ConfigurationController configurationController,
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400224 NotificationInterruptionStateProvider interruptionStateProvider,
225 ZenModeController zenModeController) {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800226 mContext = context;
Mady Melloraa8fef22019-04-11 13:36:40 -0700227 mNotificationInterruptionStateProvider = interruptionStateProvider;
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400228 mZenModeController = zenModeController;
229 mZenModeController.addCallback(new ZenModeController.Callback() {
230 @Override
231 public void onZenChanged(int zen) {
232 updateStackViewForZenConfig();
233 }
234
235 @Override
236 public void onConfigChanged(ZenModeConfig config) {
237 updateStackViewForZenConfig();
238 }
239 });
Mady Melloraa8fef22019-04-11 13:36:40 -0700240
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700241 configurationController.addCallback(this /* configurationListener */);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800242
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400243 mBubbleData = data;
244 mBubbleData.setListener(mBubbleDataListener);
245
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800246 mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
Ned Burns01e38212019-01-03 16:32:52 -0500247 mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
Mady Mellorc2ff0112019-03-28 14:18:06 -0700248 mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
Mady Mellorb4991e62019-01-10 15:14:51 -0800249
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800250 mStatusBarWindowController = statusBarWindowController;
251 mStatusBarStateListener = new StatusBarStateListener();
252 Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
Mark Renoufcecc77b2019-01-30 16:32:24 -0500253
254 mActivityTaskManager = ActivityTaskManager.getService();
255 mTaskStackListener = new BubbleTaskStackListener();
256 ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800257
Joshua Tsujia19515f2019-02-13 18:02:29 -0500258 try {
259 WindowManagerWrapper.getInstance().addPinnedStackListener(new BubblesImeListener());
260 } catch (RemoteException e) {
261 e.printStackTrace();
262 }
Issei Suzukic0387542019-03-08 17:31:14 +0100263 mSurfaceSynchronizer = synchronizer;
Mady Mellora54e9fa2019-04-18 13:26:18 -0700264
265 mBarService = IStatusBarService.Stub.asInterface(
266 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
Mady Mellor5549dd22018-11-06 18:07:34 -0800267 }
268
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400269 /**
270 * BubbleStackView is lazily created by this method the first time a Bubble is added. This
271 * method initializes the stack view and adds it to the StatusBar just above the scrim.
272 */
273 private void ensureStackViewCreated() {
274 if (mStackView == null) {
275 mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer);
276 ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
277 // TODO(b/130237686): When you expand the shade on top of expanded bubble, there is no
278 // scrim between bubble and the shade
279 int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1;
280 sbv.addView(mStackView, bubblePosition,
281 new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
282 if (mExpandListener != null) {
283 mStackView.setExpandListener(mExpandListener);
284 }
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400285
286 updateStackViewForZenConfig();
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400287 }
288 }
289
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700290 @Override
291 public void onUiModeChanged() {
292 if (mStackView != null) {
Lyn Han02cca812019-04-02 16:27:32 -0700293 mStackView.onThemeChanged();
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700294 }
295 }
296
297 @Override
298 public void onOverlayChanged() {
299 if (mStackView != null) {
Lyn Han02cca812019-04-02 16:27:32 -0700300 mStackView.onThemeChanged();
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700301 }
302 }
303
Joshua Tsujif418f9e2019-04-04 17:09:53 -0400304 @Override
305 public void onConfigChanged(Configuration newConfig) {
306 if (mStackView != null && newConfig != null && newConfig.orientation != mOrientation) {
307 mStackView.onOrientationChanged();
308 mOrientation = newConfig.orientation;
309 }
310 }
311
Mady Mellor5549dd22018-11-06 18:07:34 -0800312 /**
Mady Mellord1c78b262018-11-06 18:04:40 -0800313 * Set a listener to be notified when some states of the bubbles change.
314 */
315 public void setBubbleStateChangeListener(BubbleStateChangeListener listener) {
316 mStateChangeListener = listener;
317 }
318
319 /**
Mady Mellorcd9b1302018-11-06 18:08:04 -0800320 * Set a listener to be notified of bubble expand events.
321 */
322 public void setExpandListener(BubbleExpandListener listener) {
Issei Suzukiac9fcb72019-02-04 17:45:57 +0100323 mExpandListener = ((isExpanding, key) -> {
324 if (listener != null) {
325 listener.onBubbleExpandChanged(isExpanding, key);
326 }
327 mStatusBarWindowController.setBubbleExpanded(isExpanding);
328 });
Mady Mellorcd9b1302018-11-06 18:08:04 -0800329 if (mStackView != null) {
330 mStackView.setExpandListener(mExpandListener);
331 }
332 }
333
334 /**
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800335 * Whether or not there are bubbles present, regardless of them being visible on the
336 * screen (e.g. if on AOD).
337 */
338 public boolean hasBubbles() {
Mady Mellor3dff9e62019-02-05 18:12:53 -0800339 if (mStackView == null) {
340 return false;
341 }
Mark Renouf71a3af62019-04-08 15:02:54 -0400342 return mBubbleData.hasBubbles();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800343 }
344
345 /**
346 * Whether the stack of bubbles is expanded or not.
347 */
348 public boolean isStackExpanded() {
Mark Renouf71a3af62019-04-08 15:02:54 -0400349 return mBubbleData.isExpanded();
350 }
351
352 /**
353 * Tell the stack of bubbles to expand.
354 */
355 public void expandStack() {
356 mBubbleData.setExpanded(true);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800357 }
358
359 /**
360 * Tell the stack of bubbles to collapse.
361 */
362 public void collapseStack() {
Mark Renouf71a3af62019-04-08 15:02:54 -0400363 mBubbleData.setExpanded(false /* expanded */);
364 }
365
366 void selectBubble(Bubble bubble) {
367 mBubbleData.setSelectedBubble(bubble);
368 }
369
370 @VisibleForTesting
371 void selectBubble(String key) {
372 Bubble bubble = mBubbleData.getBubbleWithKey(key);
373 selectBubble(bubble);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800374 }
375
376 /**
Mark Renouffec45da2019-03-13 13:24:27 -0400377 * Request the stack expand if needed, then select the specified Bubble as current.
378 *
379 * @param notificationKey the notification key for the bubble to be selected
380 */
381 public void expandStackAndSelectBubble(String notificationKey) {
Mark Renouf71a3af62019-04-08 15:02:54 -0400382 Bubble bubble = mBubbleData.getBubbleWithKey(notificationKey);
383 if (bubble != null) {
384 mBubbleData.setSelectedBubble(bubble);
385 mBubbleData.setExpanded(true);
Mark Renouffec45da2019-03-13 13:24:27 -0400386 }
387 }
388
389 /**
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800390 * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
391 */
Mark Renouf08bc42a2019-03-07 13:01:59 -0500392 void dismissStack(@DismissReason int reason) {
Mark Renouf71a3af62019-04-08 15:02:54 -0400393 mBubbleData.dismissAll(reason);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800394 }
395
396 /**
Mark Renouf041d7262019-02-06 12:09:41 -0500397 * Directs a back gesture at the bubble stack. When opened, the current expanded bubble
398 * is forwarded a back key down/up pair.
399 */
400 public void performBackPressIfNeeded() {
401 if (mStackView != null) {
402 mStackView.performBackPressIfNeeded();
403 }
404 }
405
406 /**
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800407 * Adds or updates a bubble associated with the provided notification entry.
408 *
Mark Renouf8b6a3c62019-04-09 10:17:40 -0400409 * @param notif the notification associated with this bubble.
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800410 */
Mark Renouff97ed462019-04-05 13:46:24 -0400411 void updateBubble(NotificationEntry notif) {
Mady Mellor66efd5e2019-05-15 13:38:11 -0700412 // If this is an interruptive notif, mark that it's interrupted
413 if (notif.importance >= NotificationManager.IMPORTANCE_HIGH) {
414 notif.setInterruption();
415 }
Mark Renouf71a3af62019-04-08 15:02:54 -0400416 mBubbleData.notificationEntryUpdated(notif);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800417 }
418
419 /**
420 * Removes the bubble associated with the {@param uri}.
Mark Renouf658c6bc2019-01-30 10:26:54 -0500421 * <p>
422 * Must be called from the main thread.
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800423 */
Mark Renouf658c6bc2019-01-30 10:26:54 -0500424 @MainThread
Mark Renouf08bc42a2019-03-07 13:01:59 -0500425 void removeBubble(String key, int reason) {
Mark Renouf71a3af62019-04-08 15:02:54 -0400426 // TEMP: refactor to change this to pass entry
427 Bubble bubble = mBubbleData.getBubbleWithKey(key);
428 if (bubble != null) {
429 mBubbleData.notificationEntryRemoved(bubble.entry, reason);
Mady Mellore8e07712019-01-23 12:45:33 -0800430 }
431 }
432
Ned Burns01e38212019-01-03 16:32:52 -0500433 @SuppressWarnings("FieldCanBeLocal")
Mady Mellorc2ff0112019-03-28 14:18:06 -0700434 private final NotificationRemoveInterceptor mRemoveInterceptor =
435 new NotificationRemoveInterceptor() {
436 @Override
437 public boolean onNotificationRemoveRequested(String key, int reason) {
438 if (!mBubbleData.hasBubbleWithKey(key)) {
439 return false;
440 }
441 NotificationEntry entry = mBubbleData.getBubbleWithKey(key).entry;
442
443 final boolean isClearAll = reason == REASON_CANCEL_ALL;
444 final boolean isUserDimiss = reason == REASON_CANCEL;
445 final boolean isAppCancel = reason == REASON_APP_CANCEL
446 || reason == REASON_APP_CANCEL_ALL;
447
448 // Need to check for !appCancel here because the notification may have
449 // previously been dismissed & entry.isRowDismissed would still be true
450 boolean userRemovedNotif = (entry.isRowDismissed() && !isAppCancel)
451 || isClearAll || isUserDimiss;
452
453 // The bubble notification sticks around in the data as long as the bubble is
454 // not dismissed and the app hasn't cancelled the notification.
455 boolean bubbleExtended = entry.isBubble() && !entry.isBubbleDismissed()
456 && userRemovedNotif;
457 if (bubbleExtended) {
458 entry.setShowInShadeWhenBubble(false);
459 if (mStackView != null) {
460 mStackView.updateDotVisibility(entry.key);
461 }
462 mNotificationEntryManager.updateNotifications();
463 return true;
464 } else if (!userRemovedNotif && !entry.isBubbleDismissed()) {
465 // This wasn't a user removal so we should remove the bubble as well
466 mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
467 return false;
468 }
469 return false;
470 }
471 };
472
473 @SuppressWarnings("FieldCanBeLocal")
Ned Burns01e38212019-01-03 16:32:52 -0500474 private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
475 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -0500476 public void onPendingEntryAdded(NotificationEntry entry) {
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800477 if (!areBubblesEnabled(mContext)) {
478 return;
479 }
Mady Mellorca0c24c2019-05-16 16:14:32 -0700480 if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
481 && canLaunchInActivityView(mContext, entry)) {
Mady Mellor8a1f0252019-04-01 11:31:34 -0700482 updateShowInShadeForSuppressNotification(entry);
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800483 }
484 }
485
486 @Override
Ned Burns1a5e22f2019-02-14 15:11:52 -0500487 public void onEntryInflated(NotificationEntry entry, @InflationFlag int inflatedFlags) {
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800488 if (!areBubblesEnabled(mContext)) {
489 return;
490 }
Mady Mellorca0c24c2019-05-16 16:14:32 -0700491 if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
492 && canLaunchInActivityView(mContext, entry)) {
Mark Renouff97ed462019-04-05 13:46:24 -0400493 updateBubble(entry);
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800494 }
495 }
496
497 @Override
498 public void onPreEntryUpdated(NotificationEntry entry) {
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800499 if (!areBubblesEnabled(mContext)) {
500 return;
501 }
Mady Mellorca0c24c2019-05-16 16:14:32 -0700502 boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
503 && canLaunchInActivityView(mContext, entry);
Mady Melloraa8fef22019-04-11 13:36:40 -0700504 if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.key)) {
505 // It was previously a bubble but no longer a bubble -- lets remove it
506 removeBubble(entry.key, DISMISS_NO_LONGER_BUBBLE);
Mady Mellorff40e012019-05-03 15:34:41 -0700507 } else if (shouldBubble) {
Mady Mellor8a1f0252019-04-01 11:31:34 -0700508 updateShowInShadeForSuppressNotification(entry);
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800509 entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed
Mark Renouff97ed462019-04-05 13:46:24 -0400510 updateBubble(entry);
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800511 }
512 }
Ned Burns01e38212019-01-03 16:32:52 -0500513 };
514
Mark Renouf71a3af62019-04-08 15:02:54 -0400515 @SuppressWarnings("FieldCanBeLocal")
Mark Renouf3bc5b362019-04-05 14:37:59 -0400516 private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {
Mark Renouf71a3af62019-04-08 15:02:54 -0400517
Mark Renouf3bc5b362019-04-05 14:37:59 -0400518 @Override
Mark Renouf82a40e62019-05-23 16:16:24 -0400519 public void applyUpdate(BubbleData.Update update) {
520 if (mStackView == null && update.addedBubble != null) {
521 // Lazy init stack view when the first bubble is added.
522 ensureStackViewCreated();
Mark Renouf71a3af62019-04-08 15:02:54 -0400523 }
Mark Renouf82a40e62019-05-23 16:16:24 -0400524
525 // If not yet initialized, ignore all other changes.
526 if (mStackView == null) {
527 return;
528 }
529
530 if (update.addedBubble != null) {
531 mStackView.addBubble(update.addedBubble);
532 }
533
534 // Collapsing? Do this first before remaining steps.
535 if (update.expandedChanged && !update.expanded) {
536 mStackView.setExpanded(false);
537 }
538
539 // Do removals, if any.
540 for (Pair<Bubble, Integer> removed : update.removedBubbles) {
541 final Bubble bubble = removed.first;
542 @DismissReason final int reason = removed.second;
543 mStackView.removeBubble(bubble);
544
545 if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
546 && !bubble.entry.showInShadeWhenBubble()) {
547 // The bubble is gone & the notification is gone, time to actually remove it
548 mNotificationEntryManager.performRemoveNotification(bubble.entry.notification,
549 UNDEFINED_DISMISS_REASON);
550 } else {
Mady Mellor3a0a1b42019-05-23 06:40:21 -0700551 // Update the flag for SysUI
552 bubble.entry.notification.getNotification().flags &= ~FLAG_BUBBLE;
553
554 // Make sure NoMan knows it's not a bubble anymore so anyone querying it will
555 // get right result back
Mark Renouf82a40e62019-05-23 16:16:24 -0400556 try {
557 mBarService.onNotificationBubbleChanged(bubble.getKey(),
558 false /* isBubble */);
559 } catch (RemoteException e) {
560 // Bad things have happened
561 }
Mady Mellora54e9fa2019-04-18 13:26:18 -0700562 }
563 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400564
Mark Renouf82a40e62019-05-23 16:16:24 -0400565 if (update.updatedBubble != null) {
566 mStackView.updateBubble(update.updatedBubble);
Mark Renouf71a3af62019-04-08 15:02:54 -0400567 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400568
Mark Renouf82a40e62019-05-23 16:16:24 -0400569 if (update.orderChanged) {
570 mStackView.updateBubbleOrder(update.bubbles);
Mark Renoufba5ab512019-05-02 15:21:01 -0400571 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400572
Mark Renouf82a40e62019-05-23 16:16:24 -0400573 if (update.selectionChanged) {
574 mStackView.setSelectedBubble(update.selectedBubble);
Mark Renouf71a3af62019-04-08 15:02:54 -0400575 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400576
Mark Renouf82a40e62019-05-23 16:16:24 -0400577 // Expanding? Apply this last.
578 if (update.expandedChanged && update.expanded) {
579 mStackView.setExpanded(true);
Mark Renouf71a3af62019-04-08 15:02:54 -0400580 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400581
Mark Renouf71a3af62019-04-08 15:02:54 -0400582 mNotificationEntryManager.updateNotifications();
Lyn Han6c40fe72019-05-08 14:06:33 -0700583 updateStack();
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400584
585 if (DEBUG) {
586 Log.d(TAG, "[BubbleData]");
587 Log.d(TAG, formatBubblesString(mBubbleData.getBubbles(),
588 mBubbleData.getSelectedBubble()));
589
590 if (mStackView != null) {
591 Log.d(TAG, "[BubbleStackView]");
592 Log.d(TAG, formatBubblesString(mStackView.getBubblesOnScreen(),
593 mStackView.getExpandedBubble()));
594 }
595 }
Mark Renouf3bc5b362019-04-05 14:37:59 -0400596 }
597 };
598
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800599 /**
Joshua Tsujidd4d9f92019-05-13 13:57:38 -0400600 * Updates the stack view's suppression flags from the latest config from the zen (do not
601 * disturb) controller.
602 */
603 private void updateStackViewForZenConfig() {
604 final ZenModeConfig zenModeConfig = mZenModeController.getConfig();
605
606 if (zenModeConfig == null || mStackView == null) {
607 return;
608 }
609
610 final int suppressedEffects = zenModeConfig.suppressedVisualEffects;
611 final boolean hideNotificationDotsSelected =
612 (suppressedEffects & SUPPRESSED_EFFECT_BADGE) != 0;
613 final boolean dontPopNotifsOnScreenSelected =
614 (suppressedEffects & SUPPRESSED_EFFECT_PEEK) != 0;
615 final boolean hideFromPullDownShadeSelected =
616 (suppressedEffects & SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0;
617
618 final boolean dndEnabled = mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF;
619
620 mStackView.setSuppressNewDot(
621 dndEnabled && hideNotificationDotsSelected);
622 mStackView.setSuppressFlyout(
623 dndEnabled && (dontPopNotifsOnScreenSelected
624 || hideFromPullDownShadeSelected));
625 }
626
627 /**
628 * Lets any listeners know if bubble state has changed.
Lyn Han6c40fe72019-05-08 14:06:33 -0700629 * Updates the visibility of the bubbles based on current state.
630 * Does not un-bubble, just hides or un-hides. Notifies any
631 * {@link BubbleStateChangeListener}s of visibility changes.
632 * Updates stack description for TalkBack focus.
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800633 */
Lyn Han6c40fe72019-05-08 14:06:33 -0700634 public void updateStack() {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800635 if (mStackView == null) {
636 return;
Mady Mellord1c78b262018-11-06 18:04:40 -0800637 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800638 if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) {
639 // Bubbles only appear in unlocked shade
640 mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE);
641 } else if (mStackView != null) {
642 mStackView.setVisibility(INVISIBLE);
Mady Mellor5549dd22018-11-06 18:07:34 -0800643 }
Lyn Han6c40fe72019-05-08 14:06:33 -0700644
645 // Let listeners know if bubble state changed.
646 boolean hadBubbles = mStatusBarWindowController.getBubblesShowing();
647 boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE;
648 mStatusBarWindowController.setBubblesShowing(hasBubblesShowing);
649 if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
650 mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
651 }
652
653 mStackView.updateContentDescription();
Mady Mellord1c78b262018-11-06 18:04:40 -0800654 }
655
656 /**
657 * Rect indicating the touchable region for the bubble stack / expanded stack.
658 */
659 public Rect getTouchableRegion() {
660 if (mStackView == null || mStackView.getVisibility() != VISIBLE) {
661 return null;
662 }
663 mStackView.getBoundsOnScreen(mTempRect);
664 return mTempRect;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800665 }
666
Mady Mellor390bff42019-04-05 15:09:01 -0700667 /**
668 * The display id of the expanded view, if the stack is expanded and not occluded by the
669 * status bar, otherwise returns {@link Display#INVALID_DISPLAY}.
670 */
671 public int getExpandedDisplayId(Context context) {
Joel Galenson4071ddb2019-04-18 13:30:45 -0700672 if (mStackView == null) {
673 return INVALID_DISPLAY;
674 }
Mady Mellor390bff42019-04-05 15:09:01 -0700675 boolean defaultDisplay = context.getDisplay() != null
676 && context.getDisplay().getDisplayId() == DEFAULT_DISPLAY;
677 Bubble b = mStackView.getExpandedBubble();
678 if (defaultDisplay && b != null && isStackExpanded()
679 && !mStatusBarWindowController.getPanelExpanded()) {
680 return b.expandedView.getVirtualDisplayId();
681 }
682 return INVALID_DISPLAY;
683 }
684
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800685 @VisibleForTesting
686 BubbleStackView getStackView() {
687 return mStackView;
688 }
689
Mady Mellor5549dd22018-11-06 18:07:34 -0800690 /**
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800691 * Whether the notification should automatically bubble or not. Gated by secure settings flags.
Mady Mellor5549dd22018-11-06 18:07:34 -0800692 */
Mady Mellor9bad2242019-01-28 11:21:51 -0800693 @VisibleForTesting
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800694 protected boolean shouldAutoBubbleForFlags(Context context, NotificationEntry entry) {
Mady Mellorceced172018-11-27 11:18:39 -0800695 if (entry.isBubbleDismissed()) {
Mady Mellor5549dd22018-11-06 18:07:34 -0800696 return false;
697 }
Mady Mellorb4991e62019-01-10 15:14:51 -0800698 StatusBarNotification n = entry.notification;
Mady Mellorceced172018-11-27 11:18:39 -0800699
700 boolean autoBubbleMessages = shouldAutoBubbleMessages(context) || DEBUG_ENABLE_AUTO_BUBBLE;
701 boolean autoBubbleOngoing = shouldAutoBubbleOngoing(context) || DEBUG_ENABLE_AUTO_BUBBLE;
702 boolean autoBubbleAll = shouldAutoBubbleAll(context) || DEBUG_ENABLE_AUTO_BUBBLE;
703
Mady Mellor5549dd22018-11-06 18:07:34 -0800704 boolean hasRemoteInput = false;
705 if (n.getNotification().actions != null) {
706 for (Notification.Action action : n.getNotification().actions) {
707 if (action.getRemoteInputs() != null) {
708 hasRemoteInput = true;
709 break;
710 }
711 }
712 }
Mady Mellor711f9562018-12-05 14:53:46 -0800713 boolean isCall = Notification.CATEGORY_CALL.equals(n.getNotification().category)
714 && n.isOngoing();
715 boolean isMusic = n.getNotification().hasMediaSession();
716 boolean isImportantOngoing = isMusic || isCall;
Mady Mellorceced172018-11-27 11:18:39 -0800717
Mady Mellor5549dd22018-11-06 18:07:34 -0800718 Class<? extends Notification.Style> style = n.getNotification().getNotificationStyle();
Mady Mellore3175372018-12-04 17:05:11 -0800719 boolean isMessageType = Notification.CATEGORY_MESSAGE.equals(n.getNotification().category);
720 boolean isMessageStyle = Notification.MessagingStyle.class.equals(style);
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800721 return (((isMessageType && hasRemoteInput) || isMessageStyle) && autoBubbleMessages)
Mady Mellor711f9562018-12-05 14:53:46 -0800722 || (isImportantOngoing && autoBubbleOngoing)
Mady Mellorceced172018-11-27 11:18:39 -0800723 || autoBubbleAll;
724 }
725
Mady Mellor8a1f0252019-04-01 11:31:34 -0700726 private void updateShowInShadeForSuppressNotification(NotificationEntry entry) {
727 boolean suppressNotification = entry.getBubbleMetadata() != null
Mady Mellorc529d6d2019-04-16 14:22:52 -0700728 && entry.getBubbleMetadata().isNotificationSuppressed()
Steven Wu8ba8ca92019-04-11 10:47:42 -0400729 && isForegroundApp(mContext, entry.notification.getPackageName());
Mady Mellor8a1f0252019-04-01 11:31:34 -0700730 entry.setShowInShadeWhenBubble(!suppressNotification);
731 }
732
Mark Renouf9ba6cea2019-04-17 11:53:50 -0400733 static String formatBubblesString(List<Bubble> bubbles, Bubble selected) {
734 StringBuilder sb = new StringBuilder();
735 for (Bubble bubble : bubbles) {
736 if (bubble == null) {
737 sb.append(" <null> !!!!!\n");
738 } else {
739 boolean isSelected = (bubble == selected);
740 sb.append(String.format("%s Bubble{act=%12d, ongoing=%d, key=%s}\n",
741 ((isSelected) ? "->" : " "),
742 bubble.getLastActivity(),
743 (bubble.isOngoing() ? 1 : 0),
744 bubble.getKey()));
745 }
746 }
747 return sb.toString();
748 }
749
Mady Mellore80930e2019-03-21 16:00:45 -0700750 /**
751 * Return true if the applications with the package name is running in foreground.
752 *
Steven Wu8ba8ca92019-04-11 10:47:42 -0400753 * @param context application context.
Mady Mellore80930e2019-03-21 16:00:45 -0700754 * @param pkgName application package name.
755 */
Steven Wu8ba8ca92019-04-11 10:47:42 -0400756 public static boolean isForegroundApp(Context context, String pkgName) {
757 ActivityManager am = context.getSystemService(ActivityManager.class);
Mady Mellore80930e2019-03-21 16:00:45 -0700758 List<RunningTaskInfo> tasks = am.getRunningTasks(1 /* maxNum */);
759 return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
760 }
761
Mark Renoufcecc77b2019-01-30 16:32:24 -0500762 /**
763 * This task stack listener is responsible for responding to tasks moved to the front
764 * which are on the default (main) display. When this happens, expanded bubbles must be
765 * collapsed so the user may interact with the app which was just moved to the front.
766 * <p>
767 * This listener is registered with SystemUI's ActivityManagerWrapper which dispatches
768 * these calls via a main thread Handler.
769 */
770 @MainThread
771 private class BubbleTaskStackListener extends TaskStackChangeListener {
772
Mark Renoufcecc77b2019-01-30 16:32:24 -0500773 @Override
Mark Renoufc808f062019-02-07 15:20:37 -0500774 public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
775 if (mStackView != null && taskInfo.displayId == Display.DEFAULT_DISPLAY) {
Mark Renouf71a3af62019-04-08 15:02:54 -0400776 mBubbleData.setExpanded(false);
Mark Renoufcecc77b2019-01-30 16:32:24 -0500777 }
778 }
779
Mark Renoufcecc77b2019-01-30 16:32:24 -0500780 @Override
Mark Renoufa12e8762019-03-07 15:43:01 -0500781 public void onActivityLaunchOnSecondaryDisplayRerouted() {
Mark Renoufcecc77b2019-01-30 16:32:24 -0500782 if (mStackView != null) {
Mark Renouf71a3af62019-04-08 15:02:54 -0400783 mBubbleData.setExpanded(false);
Mark Renoufcecc77b2019-01-30 16:32:24 -0500784 }
785 }
Mark Renouf446251d2019-04-26 10:22:41 -0400786
787 @Override
788 public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
789 if (mStackView != null && taskInfo.displayId == getExpandedDisplayId(mContext)) {
790 mBubbleData.setExpanded(false);
791 }
792 }
Mark Renoufcecc77b2019-01-30 16:32:24 -0500793 }
794
Mady Mellorceced172018-11-27 11:18:39 -0800795 private static boolean shouldAutoBubbleMessages(Context context) {
796 return Settings.Secure.getInt(context.getContentResolver(),
797 ENABLE_AUTO_BUBBLE_MESSAGES, 0) != 0;
798 }
799
800 private static boolean shouldAutoBubbleOngoing(Context context) {
801 return Settings.Secure.getInt(context.getContentResolver(),
802 ENABLE_AUTO_BUBBLE_ONGOING, 0) != 0;
803 }
804
805 private static boolean shouldAutoBubbleAll(Context context) {
806 return Settings.Secure.getInt(context.getContentResolver(),
807 ENABLE_AUTO_BUBBLE_ALL, 0) != 0;
Mady Mellor5549dd22018-11-06 18:07:34 -0800808 }
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500809
Mady Mellor3dff9e62019-02-05 18:12:53 -0800810 static boolean shouldUseContentIntent(Context context) {
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500811 return Settings.Secure.getInt(context.getContentResolver(),
812 ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0;
813 }
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800814
815 private static boolean areBubblesEnabled(Context context) {
816 return Settings.Secure.getInt(context.getContentResolver(),
817 ENABLE_BUBBLES, 1) != 0;
818 }
Joshua Tsujia19515f2019-02-13 18:02:29 -0500819
Joshua Tsuji010c2b12019-02-25 18:11:25 -0500820 /** Default stiffness to use for bubble physics animations. */
821 public static int getBubbleStiffness(Context context, int defaultStiffness) {
822 return Settings.Secure.getInt(
823 context.getContentResolver(), BUBBLE_STIFFNESS, defaultStiffness);
824 }
825
826 /** Default bounciness/damping ratio to use for bubble physics animations. */
827 public static float getBubbleBounciness(Context context, float defaultBounciness) {
828 return Settings.Secure.getInt(
829 context.getContentResolver(),
830 BUBBLE_BOUNCINESS,
831 (int) (defaultBounciness * 100)) / 100f;
832 }
833
Mady Mellorca0c24c2019-05-16 16:14:32 -0700834 /**
835 * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
836 *
837 * Keep checks in sync with NotificationManagerService#canLaunchInActivityView. Typically
838 * that should filter out any invalid bubbles, but should protect SysUI side just in case.
839 *
840 * @param context the context to use.
841 * @param entry the entry to bubble.
842 */
843 static boolean canLaunchInActivityView(Context context, NotificationEntry entry) {
844 PendingIntent intent = entry.getBubbleMetadata() != null
845 ? entry.getBubbleMetadata().getIntent()
846 : null;
847 if (intent == null) {
848 Log.w(TAG, "Unable to create bubble -- no intent");
849 return false;
850 }
851 ActivityInfo info =
852 intent.getIntent().resolveActivityInfo(context.getPackageManager(), 0);
853 if (info == null) {
854 Log.w(TAG, "Unable to send as bubble -- couldn't find activity info for intent: "
855 + intent);
856 return false;
857 }
858 if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
859 Log.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: "
860 + intent);
861 return false;
862 }
863 if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) {
864 Log.w(TAG, "Unable to send as bubble -- activity is not documentLaunchMode=always "
865 + "for intent: " + intent);
866 return false;
867 }
868 if ((info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) {
869 Log.w(TAG, "Unable to send as bubble -- activity is not embeddable for intent: "
870 + intent);
871 return false;
872 }
873 return true;
874 }
875
Joshua Tsujia19515f2019-02-13 18:02:29 -0500876 /** PinnedStackListener that dispatches IME visibility updates to the stack. */
877 private class BubblesImeListener extends IPinnedStackListener.Stub {
878
879 @Override
880 public void onListenerRegistered(IPinnedStackController controller) throws RemoteException {
881 }
882
883 @Override
884 public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
885 Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
886 int displayRotation) throws RemoteException {}
887
888 @Override
Joshua Tsujid9422832019-03-05 13:32:37 -0500889 public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
890 if (mStackView != null && mStackView.getBubbleCount() > 0) {
891 mStackView.post(() -> mStackView.onImeVisibilityChanged(imeVisible, imeHeight));
Joshua Tsujia19515f2019-02-13 18:02:29 -0500892 }
893 }
894
895 @Override
896 public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight)
897 throws RemoteException {}
898
899 @Override
900 public void onMinimizedStateChanged(boolean isMinimized) throws RemoteException {}
901
902 @Override
903 public void onActionsChanged(ParceledListSlice actions) throws RemoteException {}
904 }
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800905}