blob: b15c5debc17d71862ab5aaf948933a8ec4bc3fab [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
Steven Wu552f63f2019-02-05 13:41:36 -050019import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
20import static android.util.StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING;
21import static android.util.StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE;
22import static android.util.StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS;
Mady Mellord1c78b262018-11-06 18:04:40 -080023import static android.view.View.INVISIBLE;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080024import static android.view.View.VISIBLE;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080025import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080026
Mady Mellor3f2efdb2018-11-21 11:30:45 -080027import static com.android.systemui.statusbar.StatusBarState.SHADE;
28import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080029
Mady Mellorb4991e62019-01-10 15:14:51 -080030import android.annotation.Nullable;
31import android.app.INotificationManager;
Mady Mellor5549dd22018-11-06 18:07:34 -080032import android.app.Notification;
Mark Renouf89b1a4a2018-12-04 14:59:45 -050033import android.app.PendingIntent;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080034import android.content.Context;
Mark Renouf89b1a4a2018-12-04 14:59:45 -050035import android.content.pm.ActivityInfo;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080036import android.graphics.Point;
Mady Mellord1c78b262018-11-06 18:04:40 -080037import android.graphics.Rect;
Mady Mellorb4991e62019-01-10 15:14:51 -080038import android.os.ServiceManager;
Mady Mellorceced172018-11-27 11:18:39 -080039import android.provider.Settings;
Mady Mellor5549dd22018-11-06 18:07:34 -080040import android.service.notification.StatusBarNotification;
Mark Renouf89b1a4a2018-12-04 14:59:45 -050041import android.util.Log;
Steven Wu552f63f2019-02-05 13:41:36 -050042import android.util.StatsLog;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080043import android.view.LayoutInflater;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080044import android.view.ViewGroup;
45import android.view.WindowManager;
46import android.widget.FrameLayout;
47
Mark Renouf658c6bc2019-01-30 10:26:54 -050048import androidx.annotation.MainThread;
49
Mady Mellorebdbbb92018-11-15 14:36:48 -080050import com.android.internal.annotations.VisibleForTesting;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080051import com.android.internal.statusbar.NotificationVisibility;
Ned Burns01e38212019-01-03 16:32:52 -050052import com.android.systemui.Dependency;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080053import com.android.systemui.R;
Beverly8fdb5332019-02-04 14:29:49 -050054import com.android.systemui.plugins.statusbar.StatusBarStateController;
Ned Burns01e38212019-01-03 16:32:52 -050055import com.android.systemui.statusbar.notification.NotificationEntryListener;
56import com.android.systemui.statusbar.notification.NotificationEntryManager;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080057import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
Ned Burnsf81c4c42019-01-07 14:10:43 -050058import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080059import com.android.systemui.statusbar.notification.row.NotificationInflater;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080060import com.android.systemui.statusbar.phone.StatusBarWindowController;
61
62import java.util.HashMap;
63import java.util.Map;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080064import java.util.Set;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080065
Jason Monk27d01a622018-12-10 15:57:09 -050066import javax.inject.Inject;
67import javax.inject.Singleton;
68
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080069/**
70 * Bubbles are a special type of content that can "float" on top of other apps or System UI.
71 * Bubbles can be expanded to show more content.
72 *
73 * The controller manages addition, removal, and visible state of bubbles on screen.
74 */
Jason Monk27d01a622018-12-10 15:57:09 -050075@Singleton
Mady Mellor3d82e682019-02-05 13:34:48 -080076public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListener {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080077 private static final int MAX_BUBBLES = 5; // TODO: actually enforce this
78
79 private static final String TAG = "BubbleController";
80
Mady Mellor5549dd22018-11-06 18:07:34 -080081 // Enables some subset of notifs to automatically become bubbles
Ned Burns01e38212019-01-03 16:32:52 -050082 private static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
Mady Mellor5549dd22018-11-06 18:07:34 -080083
Mady Mellorf6e3ac02019-01-29 10:37:52 -080084 // Secure settings flags
85 // Feature level flag
86 private static final String ENABLE_BUBBLES = "experiment_enable_bubbles";
87 // Auto bubble flags set whether different notification types should be presented as a bubble
Mady Mellorceced172018-11-27 11:18:39 -080088 private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging";
89 private static final String ENABLE_AUTO_BUBBLE_ONGOING = "experiment_autobubble_ongoing";
90 private static final String ENABLE_AUTO_BUBBLE_ALL = "experiment_autobubble_all";
Mady Mellorf6e3ac02019-01-29 10:37:52 -080091 // Use an activity view for an auto-bubbled notification if it has an appropriate content intent
Mark Renouf89b1a4a2018-12-04 14:59:45 -050092 private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent";
Mady Mellorceced172018-11-27 11:18:39 -080093
Ned Burns01e38212019-01-03 16:32:52 -050094 private final Context mContext;
95 private final NotificationEntryManager mNotificationEntryManager;
Mady Mellord1c78b262018-11-06 18:04:40 -080096 private BubbleStateChangeListener mStateChangeListener;
Mady Mellorcd9b1302018-11-06 18:08:04 -080097 private BubbleExpandListener mExpandListener;
Mady Mellor3f2efdb2018-11-21 11:30:45 -080098 private LayoutInflater mInflater;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080099
Ned Burns01e38212019-01-03 16:32:52 -0500100 private final Map<String, BubbleView> mBubbles = new HashMap<>();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800101 private BubbleStackView mStackView;
Ned Burns01e38212019-01-03 16:32:52 -0500102 private final Point mDisplaySize;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800103
104 // Bubbles get added to the status bar view
Ned Burns01e38212019-01-03 16:32:52 -0500105 private final StatusBarWindowController mStatusBarWindowController;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800106 private StatusBarStateListener mStatusBarStateListener;
107
108 private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
109 Dependency.get(NotificationInterruptionStateProvider.class);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800110
Mady Mellorb4991e62019-01-10 15:14:51 -0800111 private INotificationManager mNotificationManagerService;
112
Mady Mellord1c78b262018-11-06 18:04:40 -0800113 // Used for determining view rect for touch interaction
114 private Rect mTempRect = new Rect();
115
Mady Mellor5549dd22018-11-06 18:07:34 -0800116 /**
Mady Mellord1c78b262018-11-06 18:04:40 -0800117 * Listener to be notified when some states of the bubbles change.
118 */
119 public interface BubbleStateChangeListener {
120 /**
121 * Called when the stack has bubbles or no longer has bubbles.
122 */
123 void onHasBubblesChanged(boolean hasBubbles);
124 }
125
Mady Mellorcd9b1302018-11-06 18:08:04 -0800126 /**
127 * Listener to find out about stack expansion / collapse events.
128 */
129 public interface BubbleExpandListener {
130 /**
131 * Called when the expansion state of the bubble stack changes.
Mady Mellorcd9b1302018-11-06 18:08:04 -0800132 * @param isExpanding whether it's expanding or collapsing
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800133 * @param key the notification key associated with bubble being expanded
Mady Mellorcd9b1302018-11-06 18:08:04 -0800134 */
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800135 void onBubbleExpandChanged(boolean isExpanding, String key);
136 }
137
138 /**
139 * Listens for the current state of the status bar and updates the visibility state
140 * of bubbles as needed.
141 */
142 private class StatusBarStateListener implements StatusBarStateController.StateListener {
143 private int mState;
144 /**
145 * Returns the current status bar state.
146 */
147 public int getCurrentState() {
148 return mState;
149 }
150
151 @Override
152 public void onStateChanged(int newState) {
153 mState = newState;
154 updateVisibility();
155 }
Mady Mellorcd9b1302018-11-06 18:08:04 -0800156 }
157
Jason Monk27d01a622018-12-10 15:57:09 -0500158 @Inject
Jason Monk92d5c242018-12-21 14:37:34 -0500159 public BubbleController(Context context, StatusBarWindowController statusBarWindowController) {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800160 mContext = context;
161 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
162 mDisplaySize = new Point();
163 wm.getDefaultDisplay().getSize(mDisplaySize);
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800164 mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800165
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800166 mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
Ned Burns01e38212019-01-03 16:32:52 -0500167 mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
Mady Mellorb4991e62019-01-10 15:14:51 -0800168
169 try {
170 mNotificationManagerService = INotificationManager.Stub.asInterface(
171 ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
172 } catch (ServiceManager.ServiceNotFoundException e) {
173 e.printStackTrace();
174 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800175
176 mStatusBarWindowController = statusBarWindowController;
177 mStatusBarStateListener = new StatusBarStateListener();
178 Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
Mady Mellor5549dd22018-11-06 18:07:34 -0800179 }
180
181 /**
Mady Mellord1c78b262018-11-06 18:04:40 -0800182 * Set a listener to be notified when some states of the bubbles change.
183 */
184 public void setBubbleStateChangeListener(BubbleStateChangeListener listener) {
185 mStateChangeListener = listener;
186 }
187
188 /**
Mady Mellorcd9b1302018-11-06 18:08:04 -0800189 * Set a listener to be notified of bubble expand events.
190 */
191 public void setExpandListener(BubbleExpandListener listener) {
Issei Suzukiac9fcb72019-02-04 17:45:57 +0100192 mExpandListener = ((isExpanding, key) -> {
193 if (listener != null) {
194 listener.onBubbleExpandChanged(isExpanding, key);
195 }
196 mStatusBarWindowController.setBubbleExpanded(isExpanding);
197 });
Mady Mellorcd9b1302018-11-06 18:08:04 -0800198 if (mStackView != null) {
199 mStackView.setExpandListener(mExpandListener);
200 }
201 }
202
203 /**
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800204 * Whether or not there are bubbles present, regardless of them being visible on the
205 * screen (e.g. if on AOD).
206 */
207 public boolean hasBubbles() {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800208 for (BubbleView bv : mBubbles.values()) {
209 if (!bv.getEntry().isBubbleDismissed()) {
210 return true;
211 }
212 }
213 return false;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800214 }
215
216 /**
217 * Whether the stack of bubbles is expanded or not.
218 */
219 public boolean isStackExpanded() {
220 return mStackView != null && mStackView.isExpanded();
221 }
222
223 /**
224 * Tell the stack of bubbles to collapse.
225 */
226 public void collapseStack() {
227 if (mStackView != null) {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800228 mStackView.collapseStack();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800229 }
230 }
231
232 /**
233 * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
234 */
Ned Burns01e38212019-01-03 16:32:52 -0500235 void dismissStack() {
Mady Mellord1c78b262018-11-06 18:04:40 -0800236 if (mStackView == null) {
237 return;
238 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800239 Set<String> keys = mBubbles.keySet();
240 for (String key: keys) {
241 mBubbles.get(key).getEntry().setBubbleDismissed(true);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800242 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800243 mStackView.stackDismissed();
244
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800245 updateVisibility();
Ned Burns01e38212019-01-03 16:32:52 -0500246 mNotificationEntryManager.updateNotifications();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800247 }
248
249 /**
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800250 * Adds or updates a bubble associated with the provided notification entry.
251 *
252 * @param notif the notification associated with this bubble.
253 * @param updatePosition whether this update should promote the bubble to the top of the stack.
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800254 */
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800255 public void updateBubble(NotificationEntry notif, boolean updatePosition) {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800256 if (mBubbles.containsKey(notif.key)) {
257 // It's an update
258 BubbleView bubble = mBubbles.get(notif.key);
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800259 mStackView.updateBubble(bubble, notif, updatePosition);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800260 } else {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800261 if (mStackView == null) {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800262 mStackView = new BubbleStackView(mContext);
Mady Mellord1c78b262018-11-06 18:04:40 -0800263 ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800264 // XXX: Bug when you expand the shade on top of expanded bubble, there is no scrim
265 // between bubble and the shade
266 int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1;
267 sbv.addView(mStackView, bubblePosition,
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800268 new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
Mady Mellorcd9b1302018-11-06 18:08:04 -0800269 if (mExpandListener != null) {
270 mStackView.setExpandListener(mExpandListener);
271 }
Mady Mellore8e07712019-01-23 12:45:33 -0800272 mStackView.setOnBlockedListener(this);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800273 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800274 // It's new
275 BubbleView bubble = (BubbleView) mInflater.inflate(
276 R.layout.bubble_view, mStackView, false /* attachToRoot */);
277 bubble.setNotif(notif);
Mady Melloredd4ee12019-01-18 10:45:11 -0800278 PendingIntent bubbleIntent = getValidBubbleIntent(notif);
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800279 if (bubbleIntent != null) {
280 bubble.setBubbleIntent(bubbleIntent);
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800281 }
282 mBubbles.put(bubble.getKey(), bubble);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800283 mStackView.addBubble(bubble);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800284 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800285 updateVisibility();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800286 }
287
288 /**
289 * Removes the bubble associated with the {@param uri}.
Mark Renouf658c6bc2019-01-30 10:26:54 -0500290 * <p>
291 * Must be called from the main thread.
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800292 */
Mark Renouf658c6bc2019-01-30 10:26:54 -0500293 @MainThread
Ned Burns01e38212019-01-03 16:32:52 -0500294 void removeBubble(String key) {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800295 BubbleView bv = mBubbles.remove(key);
Mady Mellord1c78b262018-11-06 18:04:40 -0800296 if (mStackView != null && bv != null) {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800297 mStackView.removeBubble(bv);
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500298 bv.destroyActivityView(mStackView);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800299 }
Ned Burns01e38212019-01-03 16:32:52 -0500300
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800301 NotificationEntry entry = bv != null ? bv.getEntry() : null;
Ned Burns01e38212019-01-03 16:32:52 -0500302 if (entry != null) {
303 entry.setBubbleDismissed(true);
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800304 mNotificationEntryManager.updateNotifications();
Mady Mellor5549dd22018-11-06 18:07:34 -0800305 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800306 updateVisibility();
Mady Mellord1c78b262018-11-06 18:04:40 -0800307 }
308
Mady Mellore8e07712019-01-23 12:45:33 -0800309 @Override
310 public void onBubbleBlocked(NotificationEntry entry) {
311 Object[] bubbles = mBubbles.values().toArray();
312 for (int i = 0; i < bubbles.length; i++) {
313 NotificationEntry e = ((BubbleView) bubbles[i]).getEntry();
314 boolean samePackage = entry.notification.getPackageName().equals(
315 e.notification.getPackageName());
316 if (samePackage) {
317 removeBubble(entry.key);
318 }
319 }
320 }
321
Ned Burns01e38212019-01-03 16:32:52 -0500322 @SuppressWarnings("FieldCanBeLocal")
323 private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
324 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -0500325 public void onPendingEntryAdded(NotificationEntry entry) {
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800326 if (!areBubblesEnabled(mContext)) {
327 return;
328 }
329 if (shouldAutoBubbleForFlags(mContext, entry) || shouldBubble(entry)) {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800330 // TODO: handle group summaries
331 // It's a new notif, it shows in the shade and as a bubble
Ned Burns01e38212019-01-03 16:32:52 -0500332 entry.setIsBubble(true);
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800333 entry.setShowInShadeWhenBubble(true);
334 }
335 }
336
337 @Override
338 public void onEntryInflated(NotificationEntry entry,
339 @NotificationInflater.InflationFlag int inflatedFlags) {
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800340 if (!areBubblesEnabled(mContext)) {
341 return;
342 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800343 if (entry.isBubble() && mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) {
344 updateBubble(entry, true /* updatePosition */);
345 }
346 }
347
348 @Override
349 public void onPreEntryUpdated(NotificationEntry entry) {
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800350 if (!areBubblesEnabled(mContext)) {
351 return;
352 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800353 if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
354 && alertAgain(entry, entry.notification.getNotification())) {
355 entry.setShowInShadeWhenBubble(true);
356 entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed
357 if (mBubbles.containsKey(entry.key)) {
358 mBubbles.get(entry.key).updateDotVisibility();
359 }
360 updateBubble(entry, true /* updatePosition */);
361 }
362 }
363
364 @Override
365 public void onEntryRemoved(NotificationEntry entry,
366 @Nullable NotificationVisibility visibility,
367 boolean removedByUser) {
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800368 if (!areBubblesEnabled(mContext)) {
369 return;
370 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800371 entry.setShowInShadeWhenBubble(false);
372 if (mBubbles.containsKey(entry.key)) {
373 mBubbles.get(entry.key).updateDotVisibility();
374 }
375 if (!removedByUser) {
376 // This was a cancel so we should remove the bubble
377 removeBubble(entry.key);
Ned Burns01e38212019-01-03 16:32:52 -0500378 }
379 }
380 };
381
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800382 /**
383 * Lets any listeners know if bubble state has changed.
384 */
Mady Mellord1c78b262018-11-06 18:04:40 -0800385 private void updateBubblesShowing() {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800386 if (mStackView == null) {
387 return;
Mady Mellord1c78b262018-11-06 18:04:40 -0800388 }
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800389
Mady Mellord1c78b262018-11-06 18:04:40 -0800390 boolean hadBubbles = mStatusBarWindowController.getBubblesShowing();
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800391 boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE;
Mady Mellord1c78b262018-11-06 18:04:40 -0800392 mStatusBarWindowController.setBubblesShowing(hasBubblesShowing);
Mady Mellord1c78b262018-11-06 18:04:40 -0800393 if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
394 mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
395 }
Mady Mellor5549dd22018-11-06 18:07:34 -0800396 }
397
398 /**
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800399 * Updates the visibility of the bubbles based on current state.
400 * Does not un-bubble, just hides or un-hides. Will notify any
401 * {@link BubbleStateChangeListener}s if visibility changes.
Mady Mellor5549dd22018-11-06 18:07:34 -0800402 */
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800403 public void updateVisibility() {
404 if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) {
405 // Bubbles only appear in unlocked shade
406 mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE);
407 } else if (mStackView != null) {
408 mStackView.setVisibility(INVISIBLE);
409 collapseStack();
Mady Mellor5549dd22018-11-06 18:07:34 -0800410 }
Mady Mellord1c78b262018-11-06 18:04:40 -0800411 updateBubblesShowing();
412 }
413
414 /**
415 * Rect indicating the touchable region for the bubble stack / expanded stack.
416 */
417 public Rect getTouchableRegion() {
418 if (mStackView == null || mStackView.getVisibility() != VISIBLE) {
419 return null;
420 }
421 mStackView.getBoundsOnScreen(mTempRect);
422 return mTempRect;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800423 }
424
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800425 @VisibleForTesting
426 BubbleStackView getStackView() {
427 return mStackView;
428 }
429
430 @Nullable
431 private PendingIntent getValidBubbleIntent(NotificationEntry notif) {
432 Notification notification = notif.notification.getNotification();
Steven Wu552f63f2019-02-05 13:41:36 -0500433 String packageName = notif.notification.getPackageName();
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800434 Notification.BubbleMetadata data = notif.getBubbleMetadata();
Steven Wu552f63f2019-02-05 13:41:36 -0500435 if (data != null && canLaunchInActivityView(data.getIntent(),
436 true /* enable logging for bubbles */, packageName)) {
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800437 return data.getIntent();
Steven Wu552f63f2019-02-05 13:41:36 -0500438 }
439 if (shouldUseContentIntent(mContext)
440 && canLaunchInActivityView(notification.contentIntent,
441 false /* disable logging for notifications */, packageName)) {
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800442 Log.d(TAG, "[addBubble " + notif.key
443 + "]: No appOverlayIntent, using contentIntent.");
444 return notification.contentIntent;
445 }
446 Log.d(TAG, "[addBubble " + notif.key + "]: No supported intent for ActivityView.");
447 return null;
448 }
449
450 /**
451 * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
Steven Wu552f63f2019-02-05 13:41:36 -0500452 *
453 * @param intent the pending intent of the bubble.
454 * @param enableLogging whether bubble developer error should be logged.
455 * @param packageName the notification package name for this bubble.
456 * @return
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800457 */
Steven Wu552f63f2019-02-05 13:41:36 -0500458 private boolean canLaunchInActivityView(PendingIntent intent, boolean enableLogging,
459 String packageName) {
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500460 if (intent == null) {
461 return false;
462 }
463 ActivityInfo info =
464 intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0);
Steven Wu552f63f2019-02-05 13:41:36 -0500465 if (info == null) {
466 if (enableLogging) {
467 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
468 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING);
469 }
470 return false;
471 }
472 if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
473 if (enableLogging) {
474 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
475 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE);
476 }
477 return false;
478 }
479 if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) {
480 if (enableLogging) {
481 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
482 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS);
483 }
484 return false;
485 }
486 return (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0;
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500487 }
488
Mady Mellor5549dd22018-11-06 18:07:34 -0800489 /**
Mady Mellorb4991e62019-01-10 15:14:51 -0800490 * Whether the notification has been developer configured to bubble and is allowed by the user.
491 */
Mady Mellorc18ba962019-01-29 11:11:56 -0800492 @VisibleForTesting
493 protected boolean shouldBubble(NotificationEntry entry) {
Mady Mellorb4991e62019-01-10 15:14:51 -0800494 StatusBarNotification n = entry.notification;
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800495 boolean hasOverlayIntent = n.getNotification().getBubbleMetadata() != null
496 && n.getNotification().getBubbleMetadata().getIntent() != null;
Julia Reynolds4509ce72019-01-31 13:12:43 -0500497 return hasOverlayIntent && entry.canBubble;
Mady Mellorb4991e62019-01-10 15:14:51 -0800498 }
499
500 /**
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800501 * Whether the notification should automatically bubble or not. Gated by secure settings flags.
Mady Mellor5549dd22018-11-06 18:07:34 -0800502 */
Mady Mellor9bad2242019-01-28 11:21:51 -0800503 @VisibleForTesting
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800504 protected boolean shouldAutoBubbleForFlags(Context context, NotificationEntry entry) {
Mady Mellorceced172018-11-27 11:18:39 -0800505 if (entry.isBubbleDismissed()) {
Mady Mellor5549dd22018-11-06 18:07:34 -0800506 return false;
507 }
Mady Mellorb4991e62019-01-10 15:14:51 -0800508 StatusBarNotification n = entry.notification;
Mady Mellorceced172018-11-27 11:18:39 -0800509
510 boolean autoBubbleMessages = shouldAutoBubbleMessages(context) || DEBUG_ENABLE_AUTO_BUBBLE;
511 boolean autoBubbleOngoing = shouldAutoBubbleOngoing(context) || DEBUG_ENABLE_AUTO_BUBBLE;
512 boolean autoBubbleAll = shouldAutoBubbleAll(context) || DEBUG_ENABLE_AUTO_BUBBLE;
513
Mady Mellor5549dd22018-11-06 18:07:34 -0800514 boolean hasRemoteInput = false;
515 if (n.getNotification().actions != null) {
516 for (Notification.Action action : n.getNotification().actions) {
517 if (action.getRemoteInputs() != null) {
518 hasRemoteInput = true;
519 break;
520 }
521 }
522 }
Mady Mellor711f9562018-12-05 14:53:46 -0800523 boolean isCall = Notification.CATEGORY_CALL.equals(n.getNotification().category)
524 && n.isOngoing();
525 boolean isMusic = n.getNotification().hasMediaSession();
526 boolean isImportantOngoing = isMusic || isCall;
Mady Mellorceced172018-11-27 11:18:39 -0800527
Mady Mellor5549dd22018-11-06 18:07:34 -0800528 Class<? extends Notification.Style> style = n.getNotification().getNotificationStyle();
Mady Mellore3175372018-12-04 17:05:11 -0800529 boolean isMessageType = Notification.CATEGORY_MESSAGE.equals(n.getNotification().category);
530 boolean isMessageStyle = Notification.MessagingStyle.class.equals(style);
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800531 return (((isMessageType && hasRemoteInput) || isMessageStyle) && autoBubbleMessages)
Mady Mellor711f9562018-12-05 14:53:46 -0800532 || (isImportantOngoing && autoBubbleOngoing)
Mady Mellorceced172018-11-27 11:18:39 -0800533 || autoBubbleAll;
534 }
535
536 private static boolean shouldAutoBubbleMessages(Context context) {
537 return Settings.Secure.getInt(context.getContentResolver(),
538 ENABLE_AUTO_BUBBLE_MESSAGES, 0) != 0;
539 }
540
541 private static boolean shouldAutoBubbleOngoing(Context context) {
542 return Settings.Secure.getInt(context.getContentResolver(),
543 ENABLE_AUTO_BUBBLE_ONGOING, 0) != 0;
544 }
545
546 private static boolean shouldAutoBubbleAll(Context context) {
547 return Settings.Secure.getInt(context.getContentResolver(),
548 ENABLE_AUTO_BUBBLE_ALL, 0) != 0;
Mady Mellor5549dd22018-11-06 18:07:34 -0800549 }
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500550
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500551 private static boolean shouldUseContentIntent(Context context) {
552 return Settings.Secure.getInt(context.getContentResolver(),
553 ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0;
554 }
Mady Mellorf6e3ac02019-01-29 10:37:52 -0800555
556 private static boolean areBubblesEnabled(Context context) {
557 return Settings.Secure.getInt(context.getContentResolver(),
558 ENABLE_BUBBLES, 1) != 0;
559 }
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800560}