blob: 7b461b17329c174a97e74e839a2851413a96a3e3 [file] [log] [blame]
Daniel Sandler5feceeb2013-03-22 18:29:23 -07001/*
2 * Copyright (C) 2013 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 android.service.notification;
Chris Wren51017d02015-12-15 15:34:46 -050018import android.service.notification.IStatusBarNotificationHolder;
Daniel Sandler5feceeb2013-03-22 18:29:23 -070019
Jeff Brown5c507c12014-06-05 17:14:39 -070020import android.annotation.SystemApi;
Daniel Sandler5feceeb2013-03-22 18:29:23 -070021import android.annotation.SdkConstant;
22import android.app.INotificationManager;
Christoph Studer4600f9b2014-07-22 22:44:43 +020023import android.app.Notification;
24import android.app.Notification.Builder;
John Spurlock80774932015-05-07 17:38:50 -040025import android.app.NotificationManager;
Daniel Sandler5feceeb2013-03-22 18:29:23 -070026import android.app.Service;
Chris Wren1941fc72014-05-14 15:20:51 -040027import android.content.ComponentName;
Daniel Sandler5feceeb2013-03-22 18:29:23 -070028import android.content.Context;
29import android.content.Intent;
Christoph Studercee44ba2014-05-20 18:36:43 +020030import android.content.pm.ParceledListSlice;
Daniel Sandlerf5a78382015-05-15 23:59:36 -040031import android.graphics.drawable.BitmapDrawable;
32import android.graphics.drawable.Drawable;
33import android.graphics.drawable.Icon;
34import android.graphics.Bitmap;
Julia Reynoldsd9228f12015-10-20 10:37:27 -040035import android.os.Build;
Chris Wren3ad4e3a2014-09-02 17:23:51 -040036import android.os.Bundle;
Daniel Sandler5feceeb2013-03-22 18:29:23 -070037import android.os.IBinder;
Christoph Studer05ad4822014-05-16 14:16:03 +020038import android.os.Parcel;
39import android.os.Parcelable;
Chris Wrenf9536642014-04-17 10:01:54 -040040import android.os.RemoteException;
Daniel Sandler5feceeb2013-03-22 18:29:23 -070041import android.os.ServiceManager;
Christoph Studerdda48f12014-07-29 23:13:16 +020042import android.util.ArrayMap;
43import android.util.ArraySet;
Daniel Sandler5feceeb2013-03-22 18:29:23 -070044import android.util.Log;
45
Chris Wren24fb8942015-06-18 14:33:56 -040046import java.util.ArrayList;
Christoph Studerdda48f12014-07-29 23:13:16 +020047import java.util.Collections;
Christoph Studercee44ba2014-05-20 18:36:43 +020048import java.util.List;
49
Scott Main04667da2013-04-25 16:57:16 -070050/**
Christoph Studer05ad4822014-05-16 14:16:03 +020051 * A service that receives calls from the system when new notifications are
52 * posted or removed, or their ranking changed.
Scott Main04667da2013-04-25 16:57:16 -070053 * <p>To extend this class, you must declare the service in your manifest file with
54 * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission
55 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
56 * <pre>
57 * &lt;service android:name=".NotificationListener"
58 * android:label="&#64;string/service_name"
59 * android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
60 * &lt;intent-filter>
61 * &lt;action android:name="android.service.notification.NotificationListenerService" />
62 * &lt;/intent-filter>
63 * &lt;/service></pre>
Ruben Brunkdd18a0b2015-12-04 16:16:31 -080064 * <p> Typically, while enabled in user settings, this service will be bound on boot or when a
65 * settings change occurs that could affect whether this service should run. However, for some
66 * system usage modes, the you may instead specify that this service is instead bound and unbound
67 * in response to mode changes by including a category in the intent filter. Currently
68 * supported categories are:
69 * <ul>
70 * <li>{@link #CATEGORY_VR_NOTIFICATIONS} - this service is bound when an Activity has enabled
71 * VR mode. {@see android.app.Activity#setVrMode(boolean)}.</li>
72 * </ul>
73 * </p>
Scott Main04667da2013-04-25 16:57:16 -070074 */
Daniel Sandler5feceeb2013-03-22 18:29:23 -070075public abstract class NotificationListenerService extends Service {
76 // TAG = "NotificationListenerService[MySubclass]"
77 private final String TAG = NotificationListenerService.class.getSimpleName()
78 + "[" + getClass().getSimpleName() + "]";
79
Christoph Studer85a384b2014-08-27 20:16:15 +020080 /**
81 * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
82 * Normal interruption filter.
83 */
John Spurlock80774932015-05-07 17:38:50 -040084 public static final int INTERRUPTION_FILTER_ALL
85 = NotificationManager.INTERRUPTION_FILTER_ALL;
John Spurlockd8afe3c2014-08-01 14:04:07 -040086
Christoph Studer85a384b2014-08-27 20:16:15 +020087 /**
88 * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
89 * Priority interruption filter.
90 */
John Spurlock80774932015-05-07 17:38:50 -040091 public static final int INTERRUPTION_FILTER_PRIORITY
92 = NotificationManager.INTERRUPTION_FILTER_PRIORITY;
John Spurlockd8afe3c2014-08-01 14:04:07 -040093
Christoph Studer85a384b2014-08-27 20:16:15 +020094 /**
95 * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
96 * No interruptions filter.
97 */
John Spurlock80774932015-05-07 17:38:50 -040098 public static final int INTERRUPTION_FILTER_NONE
99 = NotificationManager.INTERRUPTION_FILTER_NONE;
John Spurlockd8afe3c2014-08-01 14:04:07 -0400100
John Spurlock4f1163c2015-04-02 17:41:21 -0400101 /**
102 * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
103 * Alarms only interruption filter.
104 */
John Spurlock80774932015-05-07 17:38:50 -0400105 public static final int INTERRUPTION_FILTER_ALARMS
106 = NotificationManager.INTERRUPTION_FILTER_ALARMS;
John Spurlock4f1163c2015-04-02 17:41:21 -0400107
John Spurlock83104102015-02-12 23:25:12 -0500108 /** {@link #getCurrentInterruptionFilter() Interruption filter} constant - returned when
109 * the value is unavailable for any reason. For example, before the notification listener
110 * is connected.
111 *
112 * {@see #onListenerConnected()}
113 */
John Spurlock80774932015-05-07 17:38:50 -0400114 public static final int INTERRUPTION_FILTER_UNKNOWN
115 = NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
John Spurlock83104102015-02-12 23:25:12 -0500116
John Spurlockd8afe3c2014-08-01 14:04:07 -0400117 /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
118 * should disable notification sound, vibrating and other visual or aural effects.
Christoph Studer85a384b2014-08-27 20:16:15 +0200119 * This does not change the interruption filter, only the effects. **/
120 public static final int HINT_HOST_DISABLE_EFFECTS = 1;
John Spurlock1fa865f2014-07-21 14:56:39 -0400121
Julia Reynoldsd5607292016-02-05 15:25:58 -0500122 /**
123 * Whether notification suppressed by DND should not interruption visually when the screen is
124 * off.
125 */
126 public static final int SUPPRESSED_EFFECT_SCREEN_OFF =
127 NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
128 /**
129 * Whether notification suppressed by DND should not interruption visually when the screen is
130 * on.
131 */
Julia Reynolds61721582016-01-05 08:35:25 -0500132 public static final int SUPPRESSED_EFFECT_SCREEN_ON =
133 NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
Julia Reynoldsf612869ae2015-11-05 16:48:55 -0500134
Christoph Studerb82bc782014-08-20 14:29:43 +0200135 /**
136 * The full trim of the StatusBarNotification including all its features.
137 *
138 * @hide
139 */
140 @SystemApi
141 public static final int TRIM_FULL = 0;
142
143 /**
144 * A light trim of the StatusBarNotification excluding the following features:
145 *
146 * <ol>
147 * <li>{@link Notification#tickerView tickerView}</li>
148 * <li>{@link Notification#contentView contentView}</li>
149 * <li>{@link Notification#largeIcon largeIcon}</li>
150 * <li>{@link Notification#bigContentView bigContentView}</li>
151 * <li>{@link Notification#headsUpContentView headsUpContentView}</li>
152 * <li>{@link Notification#EXTRA_LARGE_ICON extras[EXTRA_LARGE_ICON]}</li>
153 * <li>{@link Notification#EXTRA_LARGE_ICON_BIG extras[EXTRA_LARGE_ICON_BIG]}</li>
154 * <li>{@link Notification#EXTRA_PICTURE extras[EXTRA_PICTURE]}</li>
Christoph Studer223f44e2014-09-02 14:59:32 +0200155 * <li>{@link Notification#EXTRA_BIG_TEXT extras[EXTRA_BIG_TEXT]}</li>
Christoph Studerb82bc782014-08-20 14:29:43 +0200156 * </ol>
157 *
158 * @hide
159 */
160 @SystemApi
161 public static final int TRIM_LIGHT = 1;
162
Chris Wren51017d02015-12-15 15:34:46 -0500163 /** @hide */
164 protected NotificationListenerWrapper mWrapper = null;
Christoph Studerd0694b62014-06-04 16:36:01 +0200165 private RankingMap mRankingMap;
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700166
167 private INotificationManager mNoMan;
168
Chris Wren1941fc72014-05-14 15:20:51 -0400169 /** Only valid after a successful call to (@link registerAsService}. */
170 private int mCurrentUser;
171
Christoph Studer4600f9b2014-07-22 22:44:43 +0200172
173 // This context is required for system services since NotificationListenerService isn't
174 // started as a real Service and hence no context is available.
175 private Context mSystemContext;
176
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700177 /**
178 * The {@link Intent} that must be declared as handled by the service.
179 */
180 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
181 public static final String SERVICE_INTERFACE
182 = "android.service.notification.NotificationListenerService";
183
184 /**
Ruben Brunkdd18a0b2015-12-04 16:16:31 -0800185 * If this category is declared in the application manifest for a service of this type, this
186 * service will be bound when VR mode is enabled, and unbound when VR mode is disabled rather
187 * than the normal lifecycle for a notification service.
188 *
189 * {@see android.app.Activity#setVrMode(boolean)}
190 */
191 @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY)
192 public static final String CATEGORY_VR_NOTIFICATIONS =
193 "android.intent.category.vr.notifications";
194
195 /**
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700196 * Implement this method to learn about new notifications as they are posted by apps.
197 *
198 * @param sbn A data structure encapsulating the original {@link android.app.Notification}
199 * object as well as its identifying information (tag and id) and source
200 * (package name).
201 */
Christoph Studerd0694b62014-06-04 16:36:01 +0200202 public void onNotificationPosted(StatusBarNotification sbn) {
203 // optional
204 }
205
206 /**
207 * Implement this method to learn about new notifications as they are posted by apps.
208 *
209 * @param sbn A data structure encapsulating the original {@link android.app.Notification}
210 * object as well as its identifying information (tag and id) and source
211 * (package name).
212 * @param rankingMap The current ranking map that can be used to retrieve ranking information
213 * for active notifications, including the newly posted one.
214 */
215 public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
216 onNotificationPosted(sbn);
217 }
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700218
219 /**
220 * Implement this method to learn when notifications are removed.
221 * <P>
222 * This might occur because the user has dismissed the notification using system UI (or another
223 * notification listener) or because the app has withdrawn the notification.
Daniel Sandler1a497d32013-04-18 14:52:45 -0400224 * <P>
225 * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
Scott Main04667da2013-04-25 16:57:16 -0700226 * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
Daniel Sandler1a497d32013-04-18 14:52:45 -0400227 * fields such as {@link android.app.Notification#contentView} and
228 * {@link android.app.Notification#largeIcon}. However, all other fields on
229 * {@link StatusBarNotification}, sufficient to match this call with a prior call to
230 * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700231 *
Daniel Sandler1a497d32013-04-18 14:52:45 -0400232 * @param sbn A data structure encapsulating at least the original information (tag and id)
233 * and source (package name) used to post the {@link android.app.Notification} that
234 * was just removed.
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700235 */
Christoph Studerd0694b62014-06-04 16:36:01 +0200236 public void onNotificationRemoved(StatusBarNotification sbn) {
237 // optional
238 }
239
240 /**
241 * Implement this method to learn when notifications are removed.
242 * <P>
243 * This might occur because the user has dismissed the notification using system UI (or another
244 * notification listener) or because the app has withdrawn the notification.
245 * <P>
246 * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
247 * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
248 * fields such as {@link android.app.Notification#contentView} and
249 * {@link android.app.Notification#largeIcon}. However, all other fields on
250 * {@link StatusBarNotification}, sufficient to match this call with a prior call to
251 * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
252 *
253 * @param sbn A data structure encapsulating at least the original information (tag and id)
254 * and source (package name) used to post the {@link android.app.Notification} that
255 * was just removed.
256 * @param rankingMap The current ranking map that can be used to retrieve ranking information
257 * for active notifications.
258 *
259 */
260 public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
261 onNotificationRemoved(sbn);
262 }
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700263
John Spurlocka4294292014-03-24 18:02:32 -0400264 /**
265 * Implement this method to learn about when the listener is enabled and connected to
Christoph Studercee44ba2014-05-20 18:36:43 +0200266 * the notification manager. You are safe to call {@link #getActiveNotifications()}
John Spurlocka4294292014-03-24 18:02:32 -0400267 * at this time.
John Spurlocka4294292014-03-24 18:02:32 -0400268 */
Christoph Studercee44ba2014-05-20 18:36:43 +0200269 public void onListenerConnected() {
John Spurlocka4294292014-03-24 18:02:32 -0400270 // optional
271 }
272
Chris Wrenf9536642014-04-17 10:01:54 -0400273 /**
Christoph Studer05ad4822014-05-16 14:16:03 +0200274 * Implement this method to be notified when the notification ranking changes.
Christoph Studerd0694b62014-06-04 16:36:01 +0200275 *
276 * @param rankingMap The current ranking map that can be used to retrieve ranking information
277 * for active notifications.
Chris Wrenf9536642014-04-17 10:01:54 -0400278 */
Christoph Studerd0694b62014-06-04 16:36:01 +0200279 public void onNotificationRankingUpdate(RankingMap rankingMap) {
Chris Wrenf9536642014-04-17 10:01:54 -0400280 // optional
281 }
282
John Spurlock1fa865f2014-07-21 14:56:39 -0400283 /**
284 * Implement this method to be notified when the
John Spurlockd8afe3c2014-08-01 14:04:07 -0400285 * {@link #getCurrentListenerHints() Listener hints} change.
John Spurlock1fa865f2014-07-21 14:56:39 -0400286 *
John Spurlockd8afe3c2014-08-01 14:04:07 -0400287 * @param hints The current {@link #getCurrentListenerHints() listener hints}.
John Spurlock1fa865f2014-07-21 14:56:39 -0400288 */
John Spurlockd8afe3c2014-08-01 14:04:07 -0400289 public void onListenerHintsChanged(int hints) {
John Spurlock1fa865f2014-07-21 14:56:39 -0400290 // optional
291 }
292
Christoph Studer85a384b2014-08-27 20:16:15 +0200293 /**
294 * Implement this method to be notified when the
295 * {@link #getCurrentInterruptionFilter() interruption filter} changed.
296 *
297 * @param interruptionFilter The current
298 * {@link #getCurrentInterruptionFilter() interruption filter}.
299 */
300 public void onInterruptionFilterChanged(int interruptionFilter) {
301 // optional
302 }
303
Chris Wren51017d02015-12-15 15:34:46 -0500304 /** @hide */
305 protected final INotificationManager getNotificationInterface() {
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700306 if (mNoMan == null) {
307 mNoMan = INotificationManager.Stub.asInterface(
308 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
309 }
310 return mNoMan;
311 }
312
313 /**
314 * Inform the notification manager about dismissal of a single notification.
315 * <p>
316 * Use this if your listener has a user interface that allows the user to dismiss individual
317 * notifications, similar to the behavior of Android's status bar and notification panel.
318 * It should be called after the user dismisses a single notification using your UI;
319 * upon being informed, the notification manager will actually remove the notification
320 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
321 * <P>
322 * <b>Note:</b> If your listener allows the user to fire a notification's
323 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
324 * this method at that time <i>if</i> the Notification in question has the
325 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
326 *
327 * @param pkg Package of the notifying app.
328 * @param tag Tag of the notification as specified by the notifying app in
329 * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
330 * @param id ID of the notification as specified by the notifying app in
331 * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
Kenny Guya263e4e2014-03-03 18:24:03 +0000332 * <p>
333 * @deprecated Use {@link #cancelNotification(String key)}
Dianne Hackborn955d8d62014-10-07 20:17:19 -0700334 * instead. Beginning with {@link android.os.Build.VERSION_CODES#LOLLIPOP} this method will no longer
Kenny Guya263e4e2014-03-03 18:24:03 +0000335 * cancel the notification. It will continue to cancel the notification for applications
Dianne Hackborn955d8d62014-10-07 20:17:19 -0700336 * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700337 */
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400338 public final void cancelNotification(String pkg, String tag, int id) {
John Spurlockda9a3be2014-02-12 12:12:26 -0500339 if (!isBound()) return;
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700340 try {
Kenny Guya263e4e2014-03-03 18:24:03 +0000341 getNotificationInterface().cancelNotificationFromListener(
342 mWrapper, pkg, tag, id);
343 } catch (android.os.RemoteException ex) {
344 Log.v(TAG, "Unable to contact notification manager", ex);
345 }
346 }
347
348 /**
349 * Inform the notification manager about dismissal of a single notification.
350 * <p>
351 * Use this if your listener has a user interface that allows the user to dismiss individual
352 * notifications, similar to the behavior of Android's status bar and notification panel.
353 * It should be called after the user dismisses a single notification using your UI;
354 * upon being informed, the notification manager will actually remove the notification
355 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
356 * <P>
357 * <b>Note:</b> If your listener allows the user to fire a notification's
358 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
359 * this method at that time <i>if</i> the Notification in question has the
360 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
361 * <p>
362 * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}.
363 */
364 public final void cancelNotification(String key) {
365 if (!isBound()) return;
366 try {
367 getNotificationInterface().cancelNotificationsFromListener(mWrapper,
Daniel Sandlerf5a78382015-05-15 23:59:36 -0400368 new String[] { key });
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700369 } catch (android.os.RemoteException ex) {
370 Log.v(TAG, "Unable to contact notification manager", ex);
371 }
372 }
373
374 /**
375 * Inform the notification manager about dismissal of all notifications.
376 * <p>
377 * Use this if your listener has a user interface that allows the user to dismiss all
378 * notifications, similar to the behavior of Android's status bar and notification panel.
379 * It should be called after the user invokes the "dismiss all" function of your UI;
380 * upon being informed, the notification manager will actually remove all active notifications
381 * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks.
382 *
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400383 * {@see #cancelNotification(String, String, int)}
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700384 */
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400385 public final void cancelAllNotifications() {
John Spurlocka4294292014-03-24 18:02:32 -0400386 cancelNotifications(null /*all*/);
387 }
388
389 /**
390 * Inform the notification manager about dismissal of specific notifications.
391 * <p>
392 * Use this if your listener has a user interface that allows the user to dismiss
393 * multiple notifications at once.
394 *
395 * @param keys Notifications to dismiss, or {@code null} to dismiss all.
396 *
397 * {@see #cancelNotification(String, String, int)}
398 */
399 public final void cancelNotifications(String[] keys) {
John Spurlockda9a3be2014-02-12 12:12:26 -0500400 if (!isBound()) return;
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700401 try {
John Spurlocka4294292014-03-24 18:02:32 -0400402 getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys);
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700403 } catch (android.os.RemoteException ex) {
404 Log.v(TAG, "Unable to contact notification manager", ex);
405 }
406 }
407
Daniel Sandler25cf8ce2013-04-24 15:34:57 -0400408 /**
Amith Yamasanif47e51e2015-04-17 10:02:15 -0700409 * Inform the notification manager that these notifications have been viewed by the
Amith Yamasanic6ecbce2015-06-23 12:58:43 -0700410 * user. This should only be called when there is sufficient confidence that the user is
411 * looking at the notifications, such as when the notifications appear on the screen due to
412 * an explicit user interaction.
Amith Yamasanif47e51e2015-04-17 10:02:15 -0700413 * @param keys Notifications to mark as seen.
414 */
415 public final void setNotificationsShown(String[] keys) {
416 if (!isBound()) return;
417 try {
418 getNotificationInterface().setNotificationsShownFromListener(mWrapper, keys);
419 } catch (android.os.RemoteException ex) {
420 Log.v(TAG, "Unable to contact notification manager", ex);
421 }
422 }
423
424 /**
Christoph Studerb82bc782014-08-20 14:29:43 +0200425 * Sets the notification trim that will be received via {@link #onNotificationPosted}.
426 *
427 * <p>
428 * Setting a trim other than {@link #TRIM_FULL} enables listeners that don't need access to the
429 * full notification features right away to reduce their memory footprint. Full notifications
430 * can be requested on-demand via {@link #getActiveNotifications(int)}.
431 *
432 * <p>
433 * Set to {@link #TRIM_FULL} initially.
434 *
435 * @hide
436 *
437 * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}.
438 * See <code>TRIM_*</code> constants.
439 */
440 @SystemApi
441 public final void setOnNotificationPostedTrim(int trim) {
442 if (!isBound()) return;
443 try {
444 getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim);
445 } catch (RemoteException ex) {
446 Log.v(TAG, "Unable to contact notification manager", ex);
447 }
448 }
449
450 /**
Daniel Sandler25cf8ce2013-04-24 15:34:57 -0400451 * Request the list of outstanding notifications (that is, those that are visible to the
John Spurlocka4294292014-03-24 18:02:32 -0400452 * current user). Useful when you don't know what's already been posted.
Daniel Sandler25cf8ce2013-04-24 15:34:57 -0400453 *
Chris Wrenf9536642014-04-17 10:01:54 -0400454 * @return An array of active notifications, sorted in natural order.
Daniel Sandler25cf8ce2013-04-24 15:34:57 -0400455 */
456 public StatusBarNotification[] getActiveNotifications() {
Christoph Studerb82bc782014-08-20 14:29:43 +0200457 return getActiveNotifications(null, TRIM_FULL);
458 }
459
460 /**
461 * Request the list of outstanding notifications (that is, those that are visible to the
462 * current user). Useful when you don't know what's already been posted.
463 *
464 * @hide
465 *
466 * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
467 * @return An array of active notifications, sorted in natural order.
468 */
469 @SystemApi
470 public StatusBarNotification[] getActiveNotifications(int trim) {
471 return getActiveNotifications(null, trim);
Dan Sandlerea75fdd2014-08-12 12:29:19 -0400472 }
473
474 /**
475 * Request one or more notifications by key. Useful if you have been keeping track of
476 * notifications but didn't want to retain the bits, and now need to go back and extract
477 * more data out of those notifications.
478 *
Christoph Studerb82bc782014-08-20 14:29:43 +0200479 * @param keys the keys of the notifications to request
Dan Sandlerea75fdd2014-08-12 12:29:19 -0400480 * @return An array of notifications corresponding to the requested keys, in the
481 * same order as the key list.
482 */
483 public StatusBarNotification[] getActiveNotifications(String[] keys) {
Christoph Studerb82bc782014-08-20 14:29:43 +0200484 return getActiveNotifications(keys, TRIM_FULL);
485 }
486
487 /**
488 * Request one or more notifications by key. Useful if you have been keeping track of
489 * notifications but didn't want to retain the bits, and now need to go back and extract
490 * more data out of those notifications.
491 *
492 * @hide
493 *
494 * @param keys the keys of the notifications to request
495 * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
496 * @return An array of notifications corresponding to the requested keys, in the
497 * same order as the key list.
498 */
499 @SystemApi
500 public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) {
501 if (!isBound())
502 return null;
Daniel Sandler25cf8ce2013-04-24 15:34:57 -0400503 try {
Christoph Studerb82bc782014-08-20 14:29:43 +0200504 ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
505 .getActiveNotificationsFromListener(mWrapper, keys, trim);
Christoph Studercee44ba2014-05-20 18:36:43 +0200506 List<StatusBarNotification> list = parceledList.getList();
Chris Wren24fb8942015-06-18 14:33:56 -0400507 ArrayList<StatusBarNotification> corruptNotifications = null;
Christoph Studer4600f9b2014-07-22 22:44:43 +0200508 int N = list.size();
509 for (int i = 0; i < N; i++) {
Chris Wren24fb8942015-06-18 14:33:56 -0400510 StatusBarNotification sbn = list.get(i);
511 Notification notification = sbn.getNotification();
512 try {
Chris Wren24fb8942015-06-18 14:33:56 -0400513 // convert icon metadata to legacy format for older clients
514 createLegacyIconExtras(notification);
Julia Reynoldsd9228f12015-10-20 10:37:27 -0400515 // populate remote views for older clients.
516 maybePopulateRemoteViews(notification);
Chris Wren24fb8942015-06-18 14:33:56 -0400517 } catch (IllegalArgumentException e) {
518 if (corruptNotifications == null) {
519 corruptNotifications = new ArrayList<>(N);
520 }
521 corruptNotifications.add(sbn);
522 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
523 sbn.getPackageName());
524 }
Christoph Studer4600f9b2014-07-22 22:44:43 +0200525 }
Chris Wren24fb8942015-06-18 14:33:56 -0400526 if (corruptNotifications != null) {
527 list.removeAll(corruptNotifications);
528 }
529 return list.toArray(new StatusBarNotification[list.size()]);
John Spurlocka4294292014-03-24 18:02:32 -0400530 } catch (android.os.RemoteException ex) {
531 Log.v(TAG, "Unable to contact notification manager", ex);
532 }
533 return null;
534 }
535
536 /**
John Spurlockd8afe3c2014-08-01 14:04:07 -0400537 * Gets the set of hints representing current state.
John Spurlock1fa865f2014-07-21 14:56:39 -0400538 *
539 * <p>
John Spurlockd8afe3c2014-08-01 14:04:07 -0400540 * The current state may differ from the requested state if the hint represents state
John Spurlock1fa865f2014-07-21 14:56:39 -0400541 * shared across all listeners or a feature the notification host does not support or refuses
542 * to grant.
543 *
Christoph Studer85a384b2014-08-27 20:16:15 +0200544 * @return Zero or more of the HINT_ constants.
John Spurlock1fa865f2014-07-21 14:56:39 -0400545 */
John Spurlockd8afe3c2014-08-01 14:04:07 -0400546 public final int getCurrentListenerHints() {
Christoph Studer85a384b2014-08-27 20:16:15 +0200547 if (!isBound()) return 0;
John Spurlock1fa865f2014-07-21 14:56:39 -0400548 try {
John Spurlockd8afe3c2014-08-01 14:04:07 -0400549 return getNotificationInterface().getHintsFromListener(mWrapper);
John Spurlock1fa865f2014-07-21 14:56:39 -0400550 } catch (android.os.RemoteException ex) {
551 Log.v(TAG, "Unable to contact notification manager", ex);
Christoph Studer85a384b2014-08-27 20:16:15 +0200552 return 0;
553 }
554 }
555
556 /**
557 * Gets the current notification interruption filter active on the host.
558 *
559 * <p>
560 * The interruption filter defines which notifications are allowed to interrupt the user
561 * (e.g. via sound &amp; vibration) and is applied globally. Listeners can find out whether
562 * a specific notification matched the interruption filter via
563 * {@link Ranking#matchesInterruptionFilter()}.
564 * <p>
565 * The current filter may differ from the previously requested filter if the notification host
566 * does not support or refuses to apply the requested filter, or if another component changed
567 * the filter in the meantime.
568 * <p>
569 * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
570 *
John Spurlock83104102015-02-12 23:25:12 -0500571 * @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when
572 * unavailable.
Christoph Studer85a384b2014-08-27 20:16:15 +0200573 */
574 public final int getCurrentInterruptionFilter() {
John Spurlock83104102015-02-12 23:25:12 -0500575 if (!isBound()) return INTERRUPTION_FILTER_UNKNOWN;
Christoph Studer85a384b2014-08-27 20:16:15 +0200576 try {
Chris Wren957ed702014-09-24 18:17:36 -0400577 return getNotificationInterface().getInterruptionFilterFromListener(mWrapper);
Christoph Studer85a384b2014-08-27 20:16:15 +0200578 } catch (android.os.RemoteException ex) {
579 Log.v(TAG, "Unable to contact notification manager", ex);
John Spurlock83104102015-02-12 23:25:12 -0500580 return INTERRUPTION_FILTER_UNKNOWN;
John Spurlock1fa865f2014-07-21 14:56:39 -0400581 }
582 }
583
584 /**
John Spurlockd8afe3c2014-08-01 14:04:07 -0400585 * Sets the desired {@link #getCurrentListenerHints() listener hints}.
John Spurlock1fa865f2014-07-21 14:56:39 -0400586 *
587 * <p>
Christoph Studer85a384b2014-08-27 20:16:15 +0200588 * This is merely a request, the host may or may not choose to take action depending
John Spurlock1fa865f2014-07-21 14:56:39 -0400589 * on other listener requests or other global state.
590 * <p>
John Spurlockd8afe3c2014-08-01 14:04:07 -0400591 * Listen for updates using {@link #onListenerHintsChanged(int)}.
John Spurlock1fa865f2014-07-21 14:56:39 -0400592 *
John Spurlockd8afe3c2014-08-01 14:04:07 -0400593 * @param hints One or more of the HINT_ constants.
John Spurlock1fa865f2014-07-21 14:56:39 -0400594 */
John Spurlockd8afe3c2014-08-01 14:04:07 -0400595 public final void requestListenerHints(int hints) {
John Spurlock1fa865f2014-07-21 14:56:39 -0400596 if (!isBound()) return;
597 try {
John Spurlockd8afe3c2014-08-01 14:04:07 -0400598 getNotificationInterface().requestHintsFromListener(mWrapper, hints);
John Spurlock1fa865f2014-07-21 14:56:39 -0400599 } catch (android.os.RemoteException ex) {
600 Log.v(TAG, "Unable to contact notification manager", ex);
601 }
602 }
603
604 /**
Christoph Studer85a384b2014-08-27 20:16:15 +0200605 * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}.
606 *
607 * <p>
608 * This is merely a request, the host may or may not choose to apply the requested
609 * interruption filter depending on other listener requests or other global state.
610 * <p>
611 * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
612 *
613 * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants.
614 */
615 public final void requestInterruptionFilter(int interruptionFilter) {
616 if (!isBound()) return;
617 try {
618 getNotificationInterface()
619 .requestInterruptionFilterFromListener(mWrapper, interruptionFilter);
620 } catch (android.os.RemoteException ex) {
621 Log.v(TAG, "Unable to contact notification manager", ex);
622 }
623 }
624
625 /**
Christoph Studer05ad4822014-05-16 14:16:03 +0200626 * Returns current ranking information.
John Spurlocka4294292014-03-24 18:02:32 -0400627 *
Christoph Studer05ad4822014-05-16 14:16:03 +0200628 * <p>
629 * The returned object represents the current ranking snapshot and only
Christoph Studerd0694b62014-06-04 16:36:01 +0200630 * applies for currently active notifications.
631 * <p>
632 * Generally you should use the RankingMap that is passed with events such
633 * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)},
634 * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and
635 * so on. This method should only be used when needing access outside of
636 * such events, for example to retrieve the RankingMap right after
637 * initialization.
Christoph Studer05ad4822014-05-16 14:16:03 +0200638 *
Christoph Studerd0694b62014-06-04 16:36:01 +0200639 * @return A {@link RankingMap} object providing access to ranking information
John Spurlocka4294292014-03-24 18:02:32 -0400640 */
Christoph Studerd0694b62014-06-04 16:36:01 +0200641 public RankingMap getCurrentRanking() {
642 return mRankingMap;
Daniel Sandler25cf8ce2013-04-24 15:34:57 -0400643 }
644
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700645 @Override
646 public IBinder onBind(Intent intent) {
647 if (mWrapper == null) {
Chris Wren51017d02015-12-15 15:34:46 -0500648 mWrapper = new NotificationListenerWrapper();
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700649 }
650 return mWrapper;
651 }
652
Chris Wren51017d02015-12-15 15:34:46 -0500653 /** @hide */
654 protected boolean isBound() {
John Spurlockda9a3be2014-02-12 12:12:26 -0500655 if (mWrapper == null) {
656 Log.w(TAG, "Notification listener service not yet bound.");
657 return false;
658 }
659 return true;
660 }
661
Chris Wren1941fc72014-05-14 15:20:51 -0400662 /**
663 * Directly register this service with the Notification Manager.
664 *
665 * <p>Only system services may use this call. It will fail for non-system callers.
666 * Apps should ask the user to add their listener in Settings.
667 *
Christoph Studer4600f9b2014-07-22 22:44:43 +0200668 * @param context Context required for accessing resources. Since this service isn't
669 * launched as a real Service when using this method, a context has to be passed in.
Chris Wren1941fc72014-05-14 15:20:51 -0400670 * @param componentName the component that will consume the notification information
671 * @param currentUser the user to use as the stream filter
672 * @hide
673 */
Jeff Brown5c507c12014-06-05 17:14:39 -0700674 @SystemApi
Christoph Studer4600f9b2014-07-22 22:44:43 +0200675 public void registerAsSystemService(Context context, ComponentName componentName,
676 int currentUser) throws RemoteException {
677 mSystemContext = context;
Chris Wren1941fc72014-05-14 15:20:51 -0400678 if (mWrapper == null) {
Chris Wren51017d02015-12-15 15:34:46 -0500679 mWrapper = new NotificationListenerWrapper();
Chris Wren1941fc72014-05-14 15:20:51 -0400680 }
681 INotificationManager noMan = getNotificationInterface();
682 noMan.registerListener(mWrapper, componentName, currentUser);
683 mCurrentUser = currentUser;
684 }
685
686 /**
687 * Directly unregister this service from the Notification Manager.
688 *
689 * <P>This method will fail for listeners that were not registered
690 * with (@link registerAsService).
691 * @hide
692 */
Jeff Brown5c507c12014-06-05 17:14:39 -0700693 @SystemApi
Chris Wren1941fc72014-05-14 15:20:51 -0400694 public void unregisterAsSystemService() throws RemoteException {
695 if (mWrapper != null) {
696 INotificationManager noMan = getNotificationInterface();
697 noMan.unregisterListener(mWrapper, mCurrentUser);
698 }
699 }
700
Chris Wrenab41eec2016-01-04 18:01:27 -0500701 /**
702 * Request that the listener be rebound, after a previous call to (@link requestUnbind).
703 *
Chris Wren10c63d82016-02-05 14:55:35 -0500704 * <P>This method will fail for listeners that have
Chris Wrenab41eec2016-01-04 18:01:27 -0500705 * not been granted the permission by the user.
706 *
707 * <P>The service should wait for the {@link #onListenerConnected()} event
708 * before performing any operations.
709 */
710 public static final void requestRebind(ComponentName componentName)
711 throws RemoteException {
712 INotificationManager noMan = INotificationManager.Stub.asInterface(
713 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
714 noMan.requestBindListener(componentName);
715 }
716
717 /**
718 * Request that the service be unbound.
719 *
720 * <P>This will no longer receive updates until
721 * {@link #requestRebind(ComponentName)} is called.
722 * The service will likely be kiled by the system after this call.
723 */
724 public final void requestUnbind() throws RemoteException {
725 if (mWrapper != null) {
726 INotificationManager noMan = getNotificationInterface();
727 noMan.requestUnbindListener(mWrapper);
728 }
729 }
730
Daniel Sandlerf5a78382015-05-15 23:59:36 -0400731 /** Convert new-style Icons to legacy representations for pre-M clients. */
732 private void createLegacyIconExtras(Notification n) {
733 Icon smallIcon = n.getSmallIcon();
734 Icon largeIcon = n.getLargeIcon();
Dan Sandler99a37f12015-06-09 14:34:38 -0400735 if (smallIcon != null && smallIcon.getType() == Icon.TYPE_RESOURCE) {
Daniel Sandlerf5a78382015-05-15 23:59:36 -0400736 n.extras.putInt(Notification.EXTRA_SMALL_ICON, smallIcon.getResId());
737 n.icon = smallIcon.getResId();
738 }
739 if (largeIcon != null) {
740 Drawable d = largeIcon.loadDrawable(getContext());
741 if (d != null && d instanceof BitmapDrawable) {
742 final Bitmap largeIconBits = ((BitmapDrawable) d).getBitmap();
743 n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, largeIconBits);
744 n.largeIcon = largeIconBits;
745 }
746 }
747 }
748
Julia Reynoldsd9228f12015-10-20 10:37:27 -0400749 /**
750 * Populates remote views for pre-N targeting apps.
751 */
752 private void maybePopulateRemoteViews(Notification notification) {
753 if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
754 Builder builder = Builder.recoverBuilder(getContext(), notification);
755 notification.contentView = builder.makeContentView();
756 notification.bigContentView = builder.makeBigContentView();
757 notification.headsUpContentView = builder.makeHeadsUpContentView();
758 }
759 }
760
Chris Wren51017d02015-12-15 15:34:46 -0500761 /** @hide */
762 protected class NotificationListenerWrapper extends INotificationListener.Stub {
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700763 @Override
Griff Hazen84a00ea2014-09-02 17:10:47 -0700764 public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
Christoph Studer05ad4822014-05-16 14:16:03 +0200765 NotificationRankingUpdate update) {
Griff Hazen84a00ea2014-09-02 17:10:47 -0700766 StatusBarNotification sbn;
767 try {
768 sbn = sbnHolder.get();
769 } catch (RemoteException e) {
770 Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
771 return;
772 }
Christoph Studer4600f9b2014-07-22 22:44:43 +0200773
Chris Wren24fb8942015-06-18 14:33:56 -0400774 try {
Julia Reynoldsd9228f12015-10-20 10:37:27 -0400775 Notification notification = sbn.getNotification();
Chris Wren24fb8942015-06-18 14:33:56 -0400776 // convert icon metadata to legacy format for older clients
777 createLegacyIconExtras(sbn.getNotification());
Julia Reynoldsd9228f12015-10-20 10:37:27 -0400778 maybePopulateRemoteViews(sbn.getNotification());
Chris Wren24fb8942015-06-18 14:33:56 -0400779 } catch (IllegalArgumentException e) {
Andreas Gampe1ed71f32015-12-11 15:49:07 -0800780 // warn and drop corrupt notification
Chris Wren24fb8942015-06-18 14:33:56 -0400781 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
782 sbn.getPackageName());
Andreas Gampe1ed71f32015-12-11 15:49:07 -0800783 sbn = null;
Chris Wren24fb8942015-06-18 14:33:56 -0400784 }
Daniel Sandlerf5a78382015-05-15 23:59:36 -0400785
Christoph Studer05ad4822014-05-16 14:16:03 +0200786 // protect subclass from concurrent modifications of (@link mNotificationKeys}.
787 synchronized (mWrapper) {
788 applyUpdate(update);
789 try {
Chris Wren24fb8942015-06-18 14:33:56 -0400790 if (sbn != null) {
791 NotificationListenerService.this.onNotificationPosted(sbn, mRankingMap);
792 } else {
793 // still pass along the ranking map, it may contain other information
794 NotificationListenerService.this.onNotificationRankingUpdate(mRankingMap);
795 }
Christoph Studer05ad4822014-05-16 14:16:03 +0200796 } catch (Throwable t) {
797 Log.w(TAG, "Error running onNotificationPosted", t);
Chris Wrenf9536642014-04-17 10:01:54 -0400798 }
John Spurlockc133ab82013-06-10 15:16:22 -0400799 }
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700800 }
801 @Override
Griff Hazen84a00ea2014-09-02 17:10:47 -0700802 public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
Christoph Studer05ad4822014-05-16 14:16:03 +0200803 NotificationRankingUpdate update) {
Griff Hazen84a00ea2014-09-02 17:10:47 -0700804 StatusBarNotification sbn;
805 try {
806 sbn = sbnHolder.get();
807 } catch (RemoteException e) {
808 Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e);
809 return;
810 }
Christoph Studer05ad4822014-05-16 14:16:03 +0200811 // protect subclass from concurrent modifications of (@link mNotificationKeys}.
812 synchronized (mWrapper) {
813 applyUpdate(update);
814 try {
Christoph Studerd0694b62014-06-04 16:36:01 +0200815 NotificationListenerService.this.onNotificationRemoved(sbn, mRankingMap);
Christoph Studer05ad4822014-05-16 14:16:03 +0200816 } catch (Throwable t) {
817 Log.w(TAG, "Error running onNotificationRemoved", t);
Chris Wrenf9536642014-04-17 10:01:54 -0400818 }
John Spurlockc133ab82013-06-10 15:16:22 -0400819 }
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700820 }
John Spurlocka4294292014-03-24 18:02:32 -0400821 @Override
Christoph Studer05ad4822014-05-16 14:16:03 +0200822 public void onListenerConnected(NotificationRankingUpdate update) {
823 // protect subclass from concurrent modifications of (@link mNotificationKeys}.
824 synchronized (mWrapper) {
825 applyUpdate(update);
826 try {
Christoph Studercee44ba2014-05-20 18:36:43 +0200827 NotificationListenerService.this.onListenerConnected();
Christoph Studer05ad4822014-05-16 14:16:03 +0200828 } catch (Throwable t) {
829 Log.w(TAG, "Error running onListenerConnected", t);
Chris Wrenf9536642014-04-17 10:01:54 -0400830 }
John Spurlocka4294292014-03-24 18:02:32 -0400831 }
832 }
Chris Wrenf9536642014-04-17 10:01:54 -0400833 @Override
Christoph Studer05ad4822014-05-16 14:16:03 +0200834 public void onNotificationRankingUpdate(NotificationRankingUpdate update)
Chris Wrenf9536642014-04-17 10:01:54 -0400835 throws RemoteException {
Christoph Studer05ad4822014-05-16 14:16:03 +0200836 // protect subclass from concurrent modifications of (@link mNotificationKeys}.
837 synchronized (mWrapper) {
838 applyUpdate(update);
839 try {
Christoph Studerd0694b62014-06-04 16:36:01 +0200840 NotificationListenerService.this.onNotificationRankingUpdate(mRankingMap);
Christoph Studer05ad4822014-05-16 14:16:03 +0200841 } catch (Throwable t) {
842 Log.w(TAG, "Error running onNotificationRankingUpdate", t);
Chris Wrenf9536642014-04-17 10:01:54 -0400843 }
Chris Wrenf9536642014-04-17 10:01:54 -0400844 }
845 }
John Spurlock1fa865f2014-07-21 14:56:39 -0400846 @Override
John Spurlockd8afe3c2014-08-01 14:04:07 -0400847 public void onListenerHintsChanged(int hints) throws RemoteException {
John Spurlock1fa865f2014-07-21 14:56:39 -0400848 try {
John Spurlockd8afe3c2014-08-01 14:04:07 -0400849 NotificationListenerService.this.onListenerHintsChanged(hints);
John Spurlock1fa865f2014-07-21 14:56:39 -0400850 } catch (Throwable t) {
John Spurlockd8afe3c2014-08-01 14:04:07 -0400851 Log.w(TAG, "Error running onListenerHintsChanged", t);
John Spurlock1fa865f2014-07-21 14:56:39 -0400852 }
853 }
Christoph Studer85a384b2014-08-27 20:16:15 +0200854
855 @Override
856 public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException {
857 try {
858 NotificationListenerService.this.onInterruptionFilterChanged(interruptionFilter);
859 } catch (Throwable t) {
860 Log.w(TAG, "Error running onInterruptionFilterChanged", t);
861 }
862 }
Chris Wren51017d02015-12-15 15:34:46 -0500863
864 @Override
865 public void onNotificationEnqueued(IStatusBarNotificationHolder notificationHolder,
866 int importance, boolean user) throws RemoteException {
867 // no-op in the listener
868 }
869
870 @Override
871 public void onNotificationVisibilityChanged(String key, long time, boolean visible)
872 throws RemoteException {
873 // no-op in the listener
874 }
875
876 @Override
877 public void onNotificationClick(String key, long time) throws RemoteException {
878 // no-op in the listener
879 }
880
881 @Override
882 public void onNotificationActionClick(String key, long time, int actionIndex)
883 throws RemoteException {
884 // no-op in the listener
885 }
886
887 @Override
888 public void onNotificationRemovedReason(String key, long time, int reason)
889 throws RemoteException {
890 // no-op in the listener
891 }
Chris Wrenf9536642014-04-17 10:01:54 -0400892 }
893
Christoph Studer05ad4822014-05-16 14:16:03 +0200894 private void applyUpdate(NotificationRankingUpdate update) {
Christoph Studerd0694b62014-06-04 16:36:01 +0200895 mRankingMap = new RankingMap(update);
896 }
897
Christoph Studer4600f9b2014-07-22 22:44:43 +0200898 private Context getContext() {
899 if (mSystemContext != null) {
900 return mSystemContext;
901 }
902 return this;
903 }
904
Christoph Studerd0694b62014-06-04 16:36:01 +0200905 /**
Christoph Studer1d599da2014-06-12 15:25:59 +0200906 * Stores ranking related information on a currently active notification.
Christoph Studerd0694b62014-06-04 16:36:01 +0200907 *
908 * <p>
Christoph Studer1d599da2014-06-12 15:25:59 +0200909 * Ranking objects aren't automatically updated as notification events
910 * occur. Instead, ranking information has to be retrieved again via the
911 * current {@link RankingMap}.
Christoph Studerd0694b62014-06-04 16:36:01 +0200912 */
913 public static class Ranking {
Chris Wren3ad4e3a2014-09-02 17:23:51 -0400914 /** Value signifying that the user has not expressed a per-app visibility override value.
915 * @hide */
916 public static final int VISIBILITY_NO_OVERRIDE = -1000;
917
Chris Wren9fa689f2015-11-20 16:44:53 -0500918 /**
Julia Reynolds5d25ee72015-11-20 15:38:20 -0500919 * Value signifying that the user has not expressed an importance.
Chris Wren9fa689f2015-11-20 16:44:53 -0500920 *
Julia Reynolds5d25ee72015-11-20 15:38:20 -0500921 * This value is for persisting preferences, and should never be associated with
Chris Wren9fa689f2015-11-20 16:44:53 -0500922 * an actual notification.
923 */
924 public static final int IMPORTANCE_UNSPECIFIED = -1000;
925
926 /**
927 * A notification with no importance: shows nowhere, is blocked.
928 */
Julia Reynoldsead00aa2015-12-07 08:23:48 -0500929 public static final int IMPORTANCE_NONE = 0;
Chris Wren9fa689f2015-11-20 16:44:53 -0500930
931 /**
932 * Low notification importance: only shows in the shade, below the fold.
933 */
Julia Reynoldsead00aa2015-12-07 08:23:48 -0500934 public static final int IMPORTANCE_LOW = 1;
Chris Wren9fa689f2015-11-20 16:44:53 -0500935
936 /**
937 * Default notification importance: shows everywhere, but is not intrusive.
938 */
Julia Reynoldsead00aa2015-12-07 08:23:48 -0500939 public static final int IMPORTANCE_DEFAULT = 2;
Chris Wren9fa689f2015-11-20 16:44:53 -0500940
941 /**
942 * Higher notification importance: shows everywhere, makes noise,
943 * but does not visually intrude.
944 */
Julia Reynoldsead00aa2015-12-07 08:23:48 -0500945 public static final int IMPORTANCE_HIGH = 3;
Chris Wren9fa689f2015-11-20 16:44:53 -0500946
947 /**
948 * Highest notification importance: shows everywhere, makes noise,
949 * and also visually intrudes.
950 */
Julia Reynoldsead00aa2015-12-07 08:23:48 -0500951 public static final int IMPORTANCE_MAX = 4;
Chris Wren9fa689f2015-11-20 16:44:53 -0500952
Christoph Studer1d599da2014-06-12 15:25:59 +0200953 private String mKey;
954 private int mRank = -1;
955 private boolean mIsAmbient;
Christoph Studerce7d6d22014-08-26 19:21:31 +0200956 private boolean mMatchesInterruptionFilter;
Chris Wren3ad4e3a2014-09-02 17:23:51 -0400957 private int mVisibilityOverride;
Julia Reynoldsf612869ae2015-11-05 16:48:55 -0500958 private int mSuppressedVisualEffects;
Chris Wrenbdf33762015-12-04 15:50:51 -0500959 private int mImportance;
960 private CharSequence mImportanceExplanation;
Christoph Studerd0694b62014-06-04 16:36:01 +0200961
Christoph Studer1d599da2014-06-12 15:25:59 +0200962 public Ranking() {}
Christoph Studerd0694b62014-06-04 16:36:01 +0200963
964 /**
965 * Returns the key of the notification this Ranking applies to.
966 */
967 public String getKey() {
968 return mKey;
969 }
970
971 /**
972 * Returns the rank of the notification.
973 *
974 * @return the rank of the notification, that is the 0-based index in
975 * the list of active notifications.
976 */
977 public int getRank() {
978 return mRank;
979 }
980
981 /**
982 * Returns whether the notification is an ambient notification, that is
983 * a notification that doesn't require the user's immediate attention.
984 */
985 public boolean isAmbient() {
986 return mIsAmbient;
987 }
988
989 /**
Chris Wren3ad4e3a2014-09-02 17:23:51 -0400990 * Returns the user specificed visibility for the package that posted
991 * this notification, or
992 * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
993 * no such preference has been expressed.
994 * @hide
995 */
996 public int getVisibilityOverride() {
997 return mVisibilityOverride;
998 }
999
Julia Reynoldsf612869ae2015-11-05 16:48:55 -05001000 /**
1001 * Returns the type(s) of visual effects that should be suppressed for this notification.
Julia Reynoldsd5607292016-02-05 15:25:58 -05001002 * See {@link #SUPPRESSED_EFFECT_SCREEN_OFF}, {@link #SUPPRESSED_EFFECT_SCREEN_ON}.
Julia Reynoldsf612869ae2015-11-05 16:48:55 -05001003 */
1004 public int getSuppressedVisualEffects() {
1005 return mSuppressedVisualEffects;
1006 }
1007
Christoph Studerce7d6d22014-08-26 19:21:31 +02001008 /**
1009 * Returns whether the notification matches the user's interruption
1010 * filter.
Chris Wren0fef44d2014-09-30 13:05:14 -04001011 *
1012 * @return {@code true} if the notification is allowed by the filter, or
1013 * {@code false} if it is blocked.
Christoph Studerce7d6d22014-08-26 19:21:31 +02001014 */
1015 public boolean matchesInterruptionFilter() {
1016 return mMatchesInterruptionFilter;
Christoph Studer1d599da2014-06-12 15:25:59 +02001017 }
1018
Chris Wren9fa689f2015-11-20 16:44:53 -05001019 /**
1020 * Returns the importance of the notification, which dictates its
1021 * modes of presentation, see: {@link #IMPORTANCE_DEFAULT}, etc.
1022 *
1023 * @return the rank of the notification
1024 */
1025 public int getImportance() {
Chris Wrenbdf33762015-12-04 15:50:51 -05001026 return mImportance;
Chris Wren9fa689f2015-11-20 16:44:53 -05001027 }
1028
1029 /**
Chris Wren10c63d82016-02-05 14:55:35 -05001030 * If the importance has been overriden by user preference, then this will be non-null,
Chris Wren9fa689f2015-11-20 16:44:53 -05001031 * and should be displayed to the user.
1032 *
1033 * @return the explanation for the importance, or null if it is the natural importance
1034 */
1035 public CharSequence getImportanceExplanation() {
Chris Wrenbdf33762015-12-04 15:50:51 -05001036 return mImportanceExplanation;
Chris Wren9fa689f2015-11-20 16:44:53 -05001037 }
1038
Julia Reynolds0421e6d2016-01-08 09:51:24 -05001039 private void populate(String key, int rank, boolean matchesInterruptionFilter,
1040 int visibilityOverride, int suppressedVisualEffects, int importance,
Chris Wrenbdf33762015-12-04 15:50:51 -05001041 CharSequence explanation) {
Christoph Studer1d599da2014-06-12 15:25:59 +02001042 mKey = key;
1043 mRank = rank;
Julia Reynolds0421e6d2016-01-08 09:51:24 -05001044 mIsAmbient = importance < IMPORTANCE_DEFAULT;
Christoph Studerce7d6d22014-08-26 19:21:31 +02001045 mMatchesInterruptionFilter = matchesInterruptionFilter;
Chris Wren3ad4e3a2014-09-02 17:23:51 -04001046 mVisibilityOverride = visibilityOverride;
Julia Reynoldsf612869ae2015-11-05 16:48:55 -05001047 mSuppressedVisualEffects = suppressedVisualEffects;
Chris Wrenbdf33762015-12-04 15:50:51 -05001048 mImportance = importance;
1049 mImportanceExplanation = explanation;
Christoph Studerd0694b62014-06-04 16:36:01 +02001050 }
Julia Reynolds5d25ee72015-11-20 15:38:20 -05001051
1052 /**
1053 * {@hide}
1054 */
1055 public static String importanceToString(int importance) {
1056 switch (importance) {
1057 case IMPORTANCE_UNSPECIFIED:
1058 return "UNSPECIFIED";
1059 case IMPORTANCE_NONE:
1060 return "NONE";
1061 case IMPORTANCE_LOW:
1062 return "LOW";
1063 case IMPORTANCE_DEFAULT:
1064 return "DEFAULT";
1065 case IMPORTANCE_HIGH:
1066 return "HIGH";
1067 case IMPORTANCE_MAX:
1068 return "MAX";
1069 default:
1070 return "UNKNOWN(" + String.valueOf(importance) + ")";
1071 }
1072 }
Christoph Studer05ad4822014-05-16 14:16:03 +02001073 }
1074
1075 /**
1076 * Provides access to ranking information on currently active
1077 * notifications.
1078 *
1079 * <p>
1080 * Note that this object represents a ranking snapshot that only applies to
1081 * notifications active at the time of retrieval.
1082 */
Christoph Studerd0694b62014-06-04 16:36:01 +02001083 public static class RankingMap implements Parcelable {
Christoph Studer05ad4822014-05-16 14:16:03 +02001084 private final NotificationRankingUpdate mRankingUpdate;
Christoph Studerdda48f12014-07-29 23:13:16 +02001085 private ArrayMap<String,Integer> mRanks;
1086 private ArraySet<Object> mIntercepted;
Chris Wren3ad4e3a2014-09-02 17:23:51 -04001087 private ArrayMap<String, Integer> mVisibilityOverrides;
Julia Reynoldsf612869ae2015-11-05 16:48:55 -05001088 private ArrayMap<String, Integer> mSuppressedVisualEffects;
Chris Wrenbdf33762015-12-04 15:50:51 -05001089 private ArrayMap<String, Integer> mImportance;
1090 private ArrayMap<String, String> mImportanceExplanation;
Christoph Studer05ad4822014-05-16 14:16:03 +02001091
Christoph Studerd0694b62014-06-04 16:36:01 +02001092 private RankingMap(NotificationRankingUpdate rankingUpdate) {
Christoph Studer05ad4822014-05-16 14:16:03 +02001093 mRankingUpdate = rankingUpdate;
1094 }
1095
1096 /**
1097 * Request the list of notification keys in their current ranking
1098 * order.
1099 *
1100 * @return An array of active notification keys, in their ranking order.
1101 */
1102 public String[] getOrderedKeys() {
1103 return mRankingUpdate.getOrderedKeys();
1104 }
1105
1106 /**
Christoph Studer1d599da2014-06-12 15:25:59 +02001107 * Populates outRanking with ranking information for the notification
1108 * with the given key.
Christoph Studer05ad4822014-05-16 14:16:03 +02001109 *
Christoph Studer1d599da2014-06-12 15:25:59 +02001110 * @return true if a valid key has been passed and outRanking has
1111 * been populated; false otherwise
Christoph Studer05ad4822014-05-16 14:16:03 +02001112 */
Christoph Studer1d599da2014-06-12 15:25:59 +02001113 public boolean getRanking(String key, Ranking outRanking) {
1114 int rank = getRank(key);
Julia Reynolds0421e6d2016-01-08 09:51:24 -05001115 outRanking.populate(key, rank, !isIntercepted(key),
Chris Wrenbdf33762015-12-04 15:50:51 -05001116 getVisibilityOverride(key), getSuppressedVisualEffects(key),
1117 getImportance(key), getImportanceExplanation(key));
Christoph Studer1d599da2014-06-12 15:25:59 +02001118 return rank >= 0;
Christoph Studerd0694b62014-06-04 16:36:01 +02001119 }
1120
Christoph Studer1d599da2014-06-12 15:25:59 +02001121 private int getRank(String key) {
Christoph Studerdda48f12014-07-29 23:13:16 +02001122 synchronized (this) {
1123 if (mRanks == null) {
1124 buildRanksLocked();
Christoph Studer05ad4822014-05-16 14:16:03 +02001125 }
1126 }
Christoph Studerdda48f12014-07-29 23:13:16 +02001127 Integer rank = mRanks.get(key);
1128 return rank != null ? rank : -1;
Christoph Studer1d599da2014-06-12 15:25:59 +02001129 }
1130
Christoph Studer1d599da2014-06-12 15:25:59 +02001131 private boolean isIntercepted(String key) {
Christoph Studerdda48f12014-07-29 23:13:16 +02001132 synchronized (this) {
1133 if (mIntercepted == null) {
1134 buildInterceptedSetLocked();
Christoph Studer1d599da2014-06-12 15:25:59 +02001135 }
1136 }
Christoph Studerdda48f12014-07-29 23:13:16 +02001137 return mIntercepted.contains(key);
1138 }
1139
Chris Wren3ad4e3a2014-09-02 17:23:51 -04001140 private int getVisibilityOverride(String key) {
1141 synchronized (this) {
1142 if (mVisibilityOverrides == null) {
1143 buildVisibilityOverridesLocked();
1144 }
1145 }
Julia Reynoldsf612869ae2015-11-05 16:48:55 -05001146 Integer override = mVisibilityOverrides.get(key);
1147 if (override == null) {
Chris Wren3ad4e3a2014-09-02 17:23:51 -04001148 return Ranking.VISIBILITY_NO_OVERRIDE;
1149 }
Julia Reynoldsf612869ae2015-11-05 16:48:55 -05001150 return override.intValue();
1151 }
1152
1153 private int getSuppressedVisualEffects(String key) {
1154 synchronized (this) {
1155 if (mSuppressedVisualEffects == null) {
1156 buildSuppressedVisualEffectsLocked();
1157 }
1158 }
1159 Integer suppressed = mSuppressedVisualEffects.get(key);
1160 if (suppressed == null) {
1161 return 0;
1162 }
1163 return suppressed.intValue();
Chris Wren3ad4e3a2014-09-02 17:23:51 -04001164 }
1165
Chris Wrenbdf33762015-12-04 15:50:51 -05001166 private int getImportance(String key) {
1167 synchronized (this) {
1168 if (mImportance == null) {
1169 buildImportanceLocked();
1170 }
1171 }
1172 Integer importance = mImportance.get(key);
1173 if (importance == null) {
1174 return Ranking.IMPORTANCE_DEFAULT;
1175 }
1176 return importance.intValue();
1177 }
1178
1179 private String getImportanceExplanation(String key) {
1180 synchronized (this) {
1181 if (mImportanceExplanation == null) {
1182 buildImportanceExplanationLocked();
1183 }
1184 }
1185 return mImportanceExplanation.get(key);
1186 }
1187
Christoph Studerdda48f12014-07-29 23:13:16 +02001188 // Locked by 'this'
1189 private void buildRanksLocked() {
1190 String[] orderedKeys = mRankingUpdate.getOrderedKeys();
1191 mRanks = new ArrayMap<>(orderedKeys.length);
1192 for (int i = 0; i < orderedKeys.length; i++) {
1193 String key = orderedKeys[i];
1194 mRanks.put(key, i);
1195 }
1196 }
1197
1198 // Locked by 'this'
1199 private void buildInterceptedSetLocked() {
1200 String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys();
1201 mIntercepted = new ArraySet<>(dndInterceptedKeys.length);
1202 Collections.addAll(mIntercepted, dndInterceptedKeys);
Christoph Studer05ad4822014-05-16 14:16:03 +02001203 }
1204
Chris Wren3ad4e3a2014-09-02 17:23:51 -04001205 // Locked by 'this'
1206 private void buildVisibilityOverridesLocked() {
1207 Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides();
1208 mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size());
1209 for (String key: visibilityBundle.keySet()) {
1210 mVisibilityOverrides.put(key, visibilityBundle.getInt(key));
1211 }
1212 }
1213
Julia Reynoldsf612869ae2015-11-05 16:48:55 -05001214 // Locked by 'this'
1215 private void buildSuppressedVisualEffectsLocked() {
1216 Bundle suppressedBundle = mRankingUpdate.getSuppressedVisualEffects();
1217 mSuppressedVisualEffects = new ArrayMap<>(suppressedBundle.size());
1218 for (String key: suppressedBundle.keySet()) {
1219 mSuppressedVisualEffects.put(key, suppressedBundle.getInt(key));
1220 }
1221 }
Chris Wrenbdf33762015-12-04 15:50:51 -05001222 // Locked by 'this'
1223 private void buildImportanceLocked() {
1224 String[] orderedKeys = mRankingUpdate.getOrderedKeys();
1225 int[] importance = mRankingUpdate.getImportance();
1226 mImportance = new ArrayMap<>(orderedKeys.length);
1227 for (int i = 0; i < orderedKeys.length; i++) {
1228 String key = orderedKeys[i];
1229 mImportance.put(key, importance[i]);
1230 }
1231 }
1232
1233 // Locked by 'this'
1234 private void buildImportanceExplanationLocked() {
1235 Bundle explanationBundle = mRankingUpdate.getImportanceExplanation();
1236 mImportanceExplanation = new ArrayMap<>(explanationBundle.size());
1237 for (String key: explanationBundle.keySet()) {
1238 mImportanceExplanation.put(key, explanationBundle.getString(key));
1239 }
1240 }
Julia Reynoldsf612869ae2015-11-05 16:48:55 -05001241
Christoph Studer05ad4822014-05-16 14:16:03 +02001242 // ----------- Parcelable
1243
1244 @Override
1245 public int describeContents() {
1246 return 0;
1247 }
1248
1249 @Override
1250 public void writeToParcel(Parcel dest, int flags) {
1251 dest.writeParcelable(mRankingUpdate, flags);
1252 }
1253
Christoph Studerd0694b62014-06-04 16:36:01 +02001254 public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() {
Christoph Studer05ad4822014-05-16 14:16:03 +02001255 @Override
Christoph Studerd0694b62014-06-04 16:36:01 +02001256 public RankingMap createFromParcel(Parcel source) {
Christoph Studer05ad4822014-05-16 14:16:03 +02001257 NotificationRankingUpdate rankingUpdate = source.readParcelable(null);
Christoph Studerd0694b62014-06-04 16:36:01 +02001258 return new RankingMap(rankingUpdate);
Christoph Studer05ad4822014-05-16 14:16:03 +02001259 }
1260
1261 @Override
Christoph Studerd0694b62014-06-04 16:36:01 +02001262 public RankingMap[] newArray(int size) {
1263 return new RankingMap[size];
Christoph Studer05ad4822014-05-16 14:16:03 +02001264 }
1265 };
Daniel Sandler5feceeb2013-03-22 18:29:23 -07001266 }
1267}