blob: 4fc646119261328b9c2b2e238ebee1c0e7199b93 [file] [log] [blame]
Gus Prevasec9e1f02018-12-18 15:28:12 -05001/*
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.statusbar.notification;
18
19import static com.android.systemui.statusbar.StatusBarState.SHADE;
20
21import android.app.Notification;
22import android.app.NotificationManager;
23import android.content.Context;
24import android.database.ContentObserver;
Lucas Dupin2c3992b2019-03-11 16:34:08 -070025import android.hardware.display.AmbientDisplayConfiguration;
Gus Prevasec9e1f02018-12-18 15:28:12 -050026import android.os.PowerManager;
27import android.os.RemoteException;
28import android.os.ServiceManager;
Lucas Dupin2c3992b2019-03-11 16:34:08 -070029import android.os.UserHandle;
Gus Prevasec9e1f02018-12-18 15:28:12 -050030import android.provider.Settings;
31import android.service.dreams.DreamService;
32import android.service.dreams.IDreamManager;
33import android.service.notification.StatusBarNotification;
Gus Prevasec9e1f02018-12-18 15:28:12 -050034import android.util.Log;
35
36import com.android.internal.annotations.VisibleForTesting;
37import com.android.systemui.Dependency;
Beverly8fdb5332019-02-04 14:29:49 -050038import com.android.systemui.plugins.statusbar.StatusBarStateController;
Gus Prevasec9e1f02018-12-18 15:28:12 -050039import com.android.systemui.statusbar.NotificationPresenter;
Selim Cinekc3fec682019-06-06 18:11:07 -070040import com.android.systemui.statusbar.StatusBarState;
Ned Burnsf81c4c42019-01-07 14:10:43 -050041import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Gus Prevasec9e1f02018-12-18 15:28:12 -050042import com.android.systemui.statusbar.phone.ShadeController;
43import com.android.systemui.statusbar.policy.HeadsUpManager;
44
45/**
46 * Provides heads-up and pulsing state for notification entries.
47 */
48public class NotificationInterruptionStateProvider {
49
50 private static final String TAG = "InterruptionStateProvider";
51 private static final boolean DEBUG = false;
Lucas Dupinc0b81112019-07-25 18:56:12 -070052 private static final boolean DEBUG_HEADS_UP = true;
Gus Prevasec9e1f02018-12-18 15:28:12 -050053 private static final boolean ENABLE_HEADS_UP = true;
54 private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
55
56 private final StatusBarStateController mStatusBarStateController =
57 Dependency.get(StatusBarStateController.class);
58 private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
Lucas Dupin2c3992b2019-03-11 16:34:08 -070059 private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
Gus Prevasec9e1f02018-12-18 15:28:12 -050060
61 private final Context mContext;
62 private final PowerManager mPowerManager;
63 private final IDreamManager mDreamManager;
64
65 private NotificationPresenter mPresenter;
66 private ShadeController mShadeController;
67 private HeadsUpManager mHeadsUpManager;
68 private HeadsUpSuppressor mHeadsUpSuppressor;
69
70 private ContentObserver mHeadsUpObserver;
71 @VisibleForTesting
72 protected boolean mUseHeadsUp = false;
73 private boolean mDisableNotificationAlerts;
74
75 public NotificationInterruptionStateProvider(Context context) {
76 this(context,
77 (PowerManager) context.getSystemService(Context.POWER_SERVICE),
78 IDreamManager.Stub.asInterface(
Lucas Dupin2c3992b2019-03-11 16:34:08 -070079 ServiceManager.checkService(DreamService.DREAM_SERVICE)),
80 new AmbientDisplayConfiguration(context));
Gus Prevasec9e1f02018-12-18 15:28:12 -050081 }
82
83 @VisibleForTesting
84 protected NotificationInterruptionStateProvider(
85 Context context,
86 PowerManager powerManager,
Lucas Dupin2c3992b2019-03-11 16:34:08 -070087 IDreamManager dreamManager,
88 AmbientDisplayConfiguration ambientDisplayConfiguration) {
Gus Prevasec9e1f02018-12-18 15:28:12 -050089 mContext = context;
90 mPowerManager = powerManager;
91 mDreamManager = dreamManager;
Lucas Dupin2c3992b2019-03-11 16:34:08 -070092 mAmbientDisplayConfiguration = ambientDisplayConfiguration;
Gus Prevasec9e1f02018-12-18 15:28:12 -050093 }
94
95 /** Sets up late-binding dependencies for this component. */
96 public void setUpWithPresenter(
97 NotificationPresenter notificationPresenter,
98 HeadsUpManager headsUpManager,
99 HeadsUpSuppressor headsUpSuppressor) {
100 mPresenter = notificationPresenter;
101 mHeadsUpManager = headsUpManager;
102 mHeadsUpSuppressor = headsUpSuppressor;
103
104 mHeadsUpObserver = new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
105 @Override
106 public void onChange(boolean selfChange) {
107 boolean wasUsing = mUseHeadsUp;
108 mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
109 && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
110 mContext.getContentResolver(),
111 Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
112 Settings.Global.HEADS_UP_OFF);
113 Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
114 if (wasUsing != mUseHeadsUp) {
115 if (!mUseHeadsUp) {
116 Log.d(TAG,
117 "dismissing any existing heads up notification on disable event");
118 mHeadsUpManager.releaseAllImmediately();
119 }
120 }
121 }
122 };
123
124 if (ENABLE_HEADS_UP) {
125 mContext.getContentResolver().registerContentObserver(
126 Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
127 true,
128 mHeadsUpObserver);
129 mContext.getContentResolver().registerContentObserver(
130 Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
131 mHeadsUpObserver);
132 }
133 mHeadsUpObserver.onChange(true); // set up
134 }
135
136 private ShadeController getShadeController() {
137 if (mShadeController == null) {
138 mShadeController = Dependency.get(ShadeController.class);
139 }
140 return mShadeController;
141 }
142
143 /**
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800144 * Whether the notification should appear as a bubble with a fly-out on top of the screen.
145 *
146 * @param entry the entry to check
147 * @return true if the entry should bubble up, false otherwise
148 */
149 public boolean shouldBubbleUp(NotificationEntry entry) {
Mady Melloraa8fef22019-04-11 13:36:40 -0700150 final StatusBarNotification sbn = entry.notification;
151 if (!entry.canBubble) {
152 if (DEBUG) {
153 Log.d(TAG, "No bubble up: not allowed to bubble: " + sbn.getKey());
154 }
155 return false;
156 }
157
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800158 if (!entry.isBubble()) {
159 if (DEBUG) {
160 Log.d(TAG, "No bubble up: notification " + sbn.getKey()
161 + " is bubble? " + entry.isBubble());
162 }
163 return false;
164 }
165
Mady Melloraa8fef22019-04-11 13:36:40 -0700166 final Notification n = sbn.getNotification();
167 if (n.getBubbleMetadata() == null || n.getBubbleMetadata().getIntent() == null) {
168 if (DEBUG) {
169 Log.d(TAG, "No bubble up: notification: " + sbn.getKey()
170 + " doesn't have valid metadata");
171 }
172 return false;
173 }
174
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800175 if (!canHeadsUpCommon(entry)) {
176 return false;
177 }
178
179 return true;
180 }
181
182 /**
Gus Prevasec9e1f02018-12-18 15:28:12 -0500183 * Whether the notification should peek in from the top and alert the user.
184 *
185 * @param entry the entry to check
186 * @return true if the entry should heads up, false otherwise
187 */
Ned Burnsf81c4c42019-01-07 14:10:43 -0500188 public boolean shouldHeadsUp(NotificationEntry entry) {
Selim Cinekc3fec682019-06-06 18:11:07 -0700189 if (mStatusBarStateController.isDozing()) {
190 return shouldHeadsUpWhenDozing(entry);
191 } else {
192 return shouldHeadsUpWhenAwake(entry);
Gus Prevasec9e1f02018-12-18 15:28:12 -0500193 }
Selim Cinekc3fec682019-06-06 18:11:07 -0700194 }
195
196 private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
197 StatusBarNotification sbn = entry.notification;
Gus Prevasec9e1f02018-12-18 15:28:12 -0500198
Gus Prevasec9e1f02018-12-18 15:28:12 -0500199 boolean inShade = mStatusBarStateController.getState() == SHADE;
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800200 if (entry.isBubble() && inShade) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700201 if (DEBUG_HEADS_UP) {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800202 Log.d(TAG, "No heads up: in unlocked shade where notification is shown as a "
203 + "bubble: " + sbn.getKey());
204 }
Gus Prevasec9e1f02018-12-18 15:28:12 -0500205 return false;
206 }
207
208 if (!canAlertCommon(entry)) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700209 if (DEBUG_HEADS_UP) {
Gus Prevasec9e1f02018-12-18 15:28:12 -0500210 Log.d(TAG, "No heads up: notification shouldn't alert: " + sbn.getKey());
211 }
212 return false;
213 }
214
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800215 if (!canHeadsUpCommon(entry)) {
216 return false;
217 }
218
219 if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700220 if (DEBUG_HEADS_UP) {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800221 Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
Gus Prevasec9e1f02018-12-18 15:28:12 -0500222 }
223 return false;
224 }
225
226 boolean isDreaming = false;
227 try {
228 isDreaming = mDreamManager.isDreaming();
229 } catch (RemoteException e) {
230 Log.e(TAG, "Failed to query dream manager.", e);
231 }
232 boolean inUse = mPowerManager.isScreenOn() && !isDreaming;
233
234 if (!inUse) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700235 if (DEBUG_HEADS_UP) {
Gus Prevasec9e1f02018-12-18 15:28:12 -0500236 Log.d(TAG, "No heads up: not in use: " + sbn.getKey());
237 }
238 return false;
239 }
240
Gus Prevasec9e1f02018-12-18 15:28:12 -0500241 if (!mHeadsUpSuppressor.canHeadsUp(entry, sbn)) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700242 if (DEBUG_HEADS_UP) {
243 Log.d(TAG, "No heads up: aborted by suppressor: " + sbn.getKey());
244 }
Gus Prevasec9e1f02018-12-18 15:28:12 -0500245 return false;
246 }
247
248 return true;
249 }
250
251 /**
252 * Whether or not the notification should "pulse" on the user's display when the phone is
253 * dozing. This displays the ambient view of the notification.
254 *
255 * @param entry the entry to check
256 * @return true if the entry should ambient pulse, false otherwise
257 */
Selim Cinekc3fec682019-06-06 18:11:07 -0700258 private boolean shouldHeadsUpWhenDozing(NotificationEntry entry) {
Gus Prevasec9e1f02018-12-18 15:28:12 -0500259 StatusBarNotification sbn = entry.notification;
260
Lucas Dupin2c3992b2019-03-11 16:34:08 -0700261 if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700262 if (DEBUG_HEADS_UP) {
Lucas Dupin2c3992b2019-03-11 16:34:08 -0700263 Log.d(TAG, "No pulsing: disabled by setting: " + sbn.getKey());
264 }
265 return false;
266 }
267
Gus Prevasec9e1f02018-12-18 15:28:12 -0500268 if (!canAlertCommon(entry)) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700269 if (DEBUG_HEADS_UP) {
Gus Prevasec9e1f02018-12-18 15:28:12 -0500270 Log.d(TAG, "No pulsing: notification shouldn't alert: " + sbn.getKey());
271 }
272 return false;
273 }
274
275 if (entry.shouldSuppressAmbient()) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700276 if (DEBUG_HEADS_UP) {
Gus Prevasec9e1f02018-12-18 15:28:12 -0500277 Log.d(TAG, "No pulsing: ambient effect suppressed: " + sbn.getKey());
278 }
279 return false;
280 }
281
282 if (entry.importance < NotificationManager.IMPORTANCE_DEFAULT) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700283 if (DEBUG_HEADS_UP) {
Gus Prevasec9e1f02018-12-18 15:28:12 -0500284 Log.d(TAG, "No pulsing: not important enough: " + sbn.getKey());
285 }
286 return false;
287 }
Selim Cinekc3fec682019-06-06 18:11:07 -0700288 return true;
Gus Prevasec9e1f02018-12-18 15:28:12 -0500289 }
290
291 /**
Selim Cinekc3fec682019-06-06 18:11:07 -0700292 * Common checks between regular heads up and when pulsing. See
Ned Burnsf81c4c42019-01-07 14:10:43 -0500293 * {@link #shouldHeadsUp(NotificationEntry)} and
Selim Cinekc3fec682019-06-06 18:11:07 -0700294 * {@link #shouldHeadsUpWhenDozing(NotificationEntry)}. Notifications that fail any of these
295 * checks
Gus Prevasec9e1f02018-12-18 15:28:12 -0500296 * should not alert at all.
297 *
298 * @param entry the entry to check
299 * @return true if these checks pass, false if the notification should not alert
300 */
Ned Burnsf81c4c42019-01-07 14:10:43 -0500301 protected boolean canAlertCommon(NotificationEntry entry) {
Gus Prevasec9e1f02018-12-18 15:28:12 -0500302 StatusBarNotification sbn = entry.notification;
303
304 if (mNotificationFilter.shouldFilterOut(entry)) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700305 if (DEBUG || DEBUG_HEADS_UP) {
Gus Prevasec9e1f02018-12-18 15:28:12 -0500306 Log.d(TAG, "No alerting: filtered notification: " + sbn.getKey());
307 }
308 return false;
309 }
310
311 // Don't alert notifications that are suppressed due to group alert behavior
312 if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700313 if (DEBUG || DEBUG_HEADS_UP) {
Gus Prevasec9e1f02018-12-18 15:28:12 -0500314 Log.d(TAG, "No alerting: suppressed due to group alert behavior");
315 }
316 return false;
317 }
318
319 return true;
320 }
321
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800322 /**
323 * Common checks between heads up alerting and bubble fly out alerting. See
324 * {@link #shouldHeadsUp(NotificationEntry)} and
325 * {@link #shouldBubbleUp(NotificationEntry)}. Notifications that fail any of these
326 * checks should not interrupt the user on screen.
327 *
328 * @param entry the entry to check
329 * @return true if these checks pass, false if the notification should not interrupt on screen
330 */
331 public boolean canHeadsUpCommon(NotificationEntry entry) {
332 StatusBarNotification sbn = entry.notification;
333
334 if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700335 if (DEBUG_HEADS_UP) {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800336 Log.d(TAG, "No heads up: no huns or vr mode");
337 }
338 return false;
339 }
340
341 if (entry.shouldSuppressPeek()) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700342 if (DEBUG_HEADS_UP) {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800343 Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
344 }
345 return false;
346 }
347
348 if (isSnoozedPackage(sbn)) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700349 if (DEBUG_HEADS_UP) {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800350 Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
351 }
352 return false;
353 }
354
355 if (entry.hasJustLaunchedFullScreenIntent()) {
Lucas Dupinc0b81112019-07-25 18:56:12 -0700356 if (DEBUG_HEADS_UP) {
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800357 Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
358 }
359 return false;
360 }
361
362 return true;
363 }
364
Gus Prevasec9e1f02018-12-18 15:28:12 -0500365 private boolean isSnoozedPackage(StatusBarNotification sbn) {
366 return mHeadsUpManager.isSnoozed(sbn.getPackageName());
367 }
368
369 /** Sets whether to disable all alerts. */
370 public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
371 mDisableNotificationAlerts = disableNotificationAlerts;
372 mHeadsUpObserver.onChange(true);
373 }
374
375 protected NotificationPresenter getPresenter() {
376 return mPresenter;
377 }
378
Selim Cinekc3fec682019-06-06 18:11:07 -0700379 /**
380 * When an entry was added, should we launch its fullscreen intent? Examples are Alarms or
381 * incoming calls.
382 *
383 * @param entry the entry that was added
384 * @return {@code true} if we should launch the full screen intent
385 */
386 public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) {
387 return entry.notification.getNotification().fullScreenIntent != null
388 && (!shouldHeadsUp(entry)
389 || mStatusBarStateController.getState() == StatusBarState.KEYGUARD);
390 }
391
Gus Prevasec9e1f02018-12-18 15:28:12 -0500392 /** A component which can suppress heads-up notifications due to the overall state of the UI. */
393 public interface HeadsUpSuppressor {
394 /**
395 * Returns false if the provided notification is ineligible for heads-up according to this
396 * component.
397 *
398 * @param entry entry of the notification that might be heads upped
399 * @param sbn notification that might be heads upped
400 * @return false if the notification can not be heads upped
401 */
Ned Burnsf81c4c42019-01-07 14:10:43 -0500402 boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn);
Gus Prevasec9e1f02018-12-18 15:28:12 -0500403
404 }
405
406}