blob: 8bd0f4ded5514aecdbe7fb55f0eb78afd0aae753 [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;
18
Jeff Brown5c507c12014-06-05 17:14:39 -070019import android.annotation.SystemApi;
Daniel Sandler5feceeb2013-03-22 18:29:23 -070020import android.annotation.SdkConstant;
21import android.app.INotificationManager;
22import android.app.Service;
Chris Wren1941fc72014-05-14 15:20:51 -040023import android.content.ComponentName;
Daniel Sandler5feceeb2013-03-22 18:29:23 -070024import android.content.Context;
25import android.content.Intent;
Christoph Studercee44ba2014-05-20 18:36:43 +020026import android.content.pm.ParceledListSlice;
Daniel Sandler5feceeb2013-03-22 18:29:23 -070027import android.os.IBinder;
Christoph Studer05ad4822014-05-16 14:16:03 +020028import android.os.Parcel;
29import android.os.Parcelable;
Chris Wrenf9536642014-04-17 10:01:54 -040030import android.os.RemoteException;
Daniel Sandler5feceeb2013-03-22 18:29:23 -070031import android.os.ServiceManager;
Christoph Studerd0694b62014-06-04 16:36:01 +020032import android.util.ArrayMap;
Daniel Sandler5feceeb2013-03-22 18:29:23 -070033import android.util.Log;
34
Christoph Studercee44ba2014-05-20 18:36:43 +020035import java.util.List;
36
Scott Main04667da2013-04-25 16:57:16 -070037/**
Christoph Studer05ad4822014-05-16 14:16:03 +020038 * A service that receives calls from the system when new notifications are
39 * posted or removed, or their ranking changed.
Scott Main04667da2013-04-25 16:57:16 -070040 * <p>To extend this class, you must declare the service in your manifest file with
41 * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission
42 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
43 * <pre>
44 * &lt;service android:name=".NotificationListener"
45 * android:label="&#64;string/service_name"
46 * android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
47 * &lt;intent-filter>
48 * &lt;action android:name="android.service.notification.NotificationListenerService" />
49 * &lt;/intent-filter>
50 * &lt;/service></pre>
51 */
Daniel Sandler5feceeb2013-03-22 18:29:23 -070052public abstract class NotificationListenerService extends Service {
53 // TAG = "NotificationListenerService[MySubclass]"
54 private final String TAG = NotificationListenerService.class.getSimpleName()
55 + "[" + getClass().getSimpleName() + "]";
56
57 private INotificationListenerWrapper mWrapper = null;
Christoph Studerd0694b62014-06-04 16:36:01 +020058 private RankingMap mRankingMap;
Daniel Sandler5feceeb2013-03-22 18:29:23 -070059
60 private INotificationManager mNoMan;
61
Chris Wren1941fc72014-05-14 15:20:51 -040062 /** Only valid after a successful call to (@link registerAsService}. */
63 private int mCurrentUser;
64
Daniel Sandler5feceeb2013-03-22 18:29:23 -070065 /**
66 * The {@link Intent} that must be declared as handled by the service.
67 */
68 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
69 public static final String SERVICE_INTERFACE
70 = "android.service.notification.NotificationListenerService";
71
72 /**
73 * Implement this method to learn about new notifications as they are posted by apps.
74 *
75 * @param sbn A data structure encapsulating the original {@link android.app.Notification}
76 * object as well as its identifying information (tag and id) and source
77 * (package name).
78 */
Christoph Studerd0694b62014-06-04 16:36:01 +020079 public void onNotificationPosted(StatusBarNotification sbn) {
80 // optional
81 }
82
83 /**
84 * Implement this method to learn about new notifications as they are posted by apps.
85 *
86 * @param sbn A data structure encapsulating the original {@link android.app.Notification}
87 * object as well as its identifying information (tag and id) and source
88 * (package name).
89 * @param rankingMap The current ranking map that can be used to retrieve ranking information
90 * for active notifications, including the newly posted one.
91 */
92 public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
93 onNotificationPosted(sbn);
94 }
Daniel Sandler5feceeb2013-03-22 18:29:23 -070095
96 /**
97 * Implement this method to learn when notifications are removed.
98 * <P>
99 * This might occur because the user has dismissed the notification using system UI (or another
100 * notification listener) or because the app has withdrawn the notification.
Daniel Sandler1a497d32013-04-18 14:52:45 -0400101 * <P>
102 * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
Scott Main04667da2013-04-25 16:57:16 -0700103 * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
Daniel Sandler1a497d32013-04-18 14:52:45 -0400104 * fields such as {@link android.app.Notification#contentView} and
105 * {@link android.app.Notification#largeIcon}. However, all other fields on
106 * {@link StatusBarNotification}, sufficient to match this call with a prior call to
107 * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700108 *
Daniel Sandler1a497d32013-04-18 14:52:45 -0400109 * @param sbn A data structure encapsulating at least the original information (tag and id)
110 * and source (package name) used to post the {@link android.app.Notification} that
111 * was just removed.
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700112 */
Christoph Studerd0694b62014-06-04 16:36:01 +0200113 public void onNotificationRemoved(StatusBarNotification sbn) {
114 // optional
115 }
116
117 /**
118 * Implement this method to learn when notifications are removed.
119 * <P>
120 * This might occur because the user has dismissed the notification using system UI (or another
121 * notification listener) or because the app has withdrawn the notification.
122 * <P>
123 * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
124 * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
125 * fields such as {@link android.app.Notification#contentView} and
126 * {@link android.app.Notification#largeIcon}. However, all other fields on
127 * {@link StatusBarNotification}, sufficient to match this call with a prior call to
128 * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
129 *
130 * @param sbn A data structure encapsulating at least the original information (tag and id)
131 * and source (package name) used to post the {@link android.app.Notification} that
132 * was just removed.
133 * @param rankingMap The current ranking map that can be used to retrieve ranking information
134 * for active notifications.
135 *
136 */
137 public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
138 onNotificationRemoved(sbn);
139 }
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700140
John Spurlocka4294292014-03-24 18:02:32 -0400141 /**
142 * Implement this method to learn about when the listener is enabled and connected to
Christoph Studercee44ba2014-05-20 18:36:43 +0200143 * the notification manager. You are safe to call {@link #getActiveNotifications()}
John Spurlocka4294292014-03-24 18:02:32 -0400144 * at this time.
John Spurlocka4294292014-03-24 18:02:32 -0400145 */
Christoph Studercee44ba2014-05-20 18:36:43 +0200146 public void onListenerConnected() {
John Spurlocka4294292014-03-24 18:02:32 -0400147 // optional
148 }
149
Chris Wrenf9536642014-04-17 10:01:54 -0400150 /**
Christoph Studer05ad4822014-05-16 14:16:03 +0200151 * Implement this method to be notified when the notification ranking changes.
Christoph Studerd0694b62014-06-04 16:36:01 +0200152 *
153 * @param rankingMap The current ranking map that can be used to retrieve ranking information
154 * for active notifications.
Chris Wrenf9536642014-04-17 10:01:54 -0400155 */
Christoph Studerd0694b62014-06-04 16:36:01 +0200156 public void onNotificationRankingUpdate(RankingMap rankingMap) {
Chris Wrenf9536642014-04-17 10:01:54 -0400157 // optional
158 }
159
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700160 private final INotificationManager getNotificationInterface() {
161 if (mNoMan == null) {
162 mNoMan = INotificationManager.Stub.asInterface(
163 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
164 }
165 return mNoMan;
166 }
167
168 /**
169 * Inform the notification manager about dismissal of a single notification.
170 * <p>
171 * Use this if your listener has a user interface that allows the user to dismiss individual
172 * notifications, similar to the behavior of Android's status bar and notification panel.
173 * It should be called after the user dismisses a single notification using your UI;
174 * upon being informed, the notification manager will actually remove the notification
175 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
176 * <P>
177 * <b>Note:</b> If your listener allows the user to fire a notification's
178 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
179 * this method at that time <i>if</i> the Notification in question has the
180 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
181 *
182 * @param pkg Package of the notifying app.
183 * @param tag Tag of the notification as specified by the notifying app in
184 * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
185 * @param id ID of the notification as specified by the notifying app in
186 * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
Kenny Guya263e4e2014-03-03 18:24:03 +0000187 * <p>
188 * @deprecated Use {@link #cancelNotification(String key)}
189 * instead. Beginning with {@link android.os.Build.VERSION_CODES#L} this method will no longer
190 * cancel the notification. It will continue to cancel the notification for applications
191 * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#L}.
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700192 */
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400193 public final void cancelNotification(String pkg, String tag, int id) {
John Spurlockda9a3be2014-02-12 12:12:26 -0500194 if (!isBound()) return;
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700195 try {
Kenny Guya263e4e2014-03-03 18:24:03 +0000196 getNotificationInterface().cancelNotificationFromListener(
197 mWrapper, pkg, tag, id);
198 } catch (android.os.RemoteException ex) {
199 Log.v(TAG, "Unable to contact notification manager", ex);
200 }
201 }
202
203 /**
204 * Inform the notification manager about dismissal of a single notification.
205 * <p>
206 * Use this if your listener has a user interface that allows the user to dismiss individual
207 * notifications, similar to the behavior of Android's status bar and notification panel.
208 * It should be called after the user dismisses a single notification using your UI;
209 * upon being informed, the notification manager will actually remove the notification
210 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
211 * <P>
212 * <b>Note:</b> If your listener allows the user to fire a notification's
213 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
214 * this method at that time <i>if</i> the Notification in question has the
215 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
216 * <p>
217 * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}.
218 */
219 public final void cancelNotification(String key) {
220 if (!isBound()) return;
221 try {
222 getNotificationInterface().cancelNotificationsFromListener(mWrapper,
223 new String[] {key});
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700224 } catch (android.os.RemoteException ex) {
225 Log.v(TAG, "Unable to contact notification manager", ex);
226 }
227 }
228
229 /**
230 * Inform the notification manager about dismissal of all notifications.
231 * <p>
232 * Use this if your listener has a user interface that allows the user to dismiss all
233 * notifications, similar to the behavior of Android's status bar and notification panel.
234 * It should be called after the user invokes the "dismiss all" function of your UI;
235 * upon being informed, the notification manager will actually remove all active notifications
236 * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks.
237 *
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400238 * {@see #cancelNotification(String, String, int)}
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700239 */
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400240 public final void cancelAllNotifications() {
John Spurlocka4294292014-03-24 18:02:32 -0400241 cancelNotifications(null /*all*/);
242 }
243
244 /**
245 * Inform the notification manager about dismissal of specific notifications.
246 * <p>
247 * Use this if your listener has a user interface that allows the user to dismiss
248 * multiple notifications at once.
249 *
250 * @param keys Notifications to dismiss, or {@code null} to dismiss all.
251 *
252 * {@see #cancelNotification(String, String, int)}
253 */
254 public final void cancelNotifications(String[] keys) {
John Spurlockda9a3be2014-02-12 12:12:26 -0500255 if (!isBound()) return;
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700256 try {
John Spurlocka4294292014-03-24 18:02:32 -0400257 getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys);
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700258 } catch (android.os.RemoteException ex) {
259 Log.v(TAG, "Unable to contact notification manager", ex);
260 }
261 }
262
Daniel Sandler25cf8ce2013-04-24 15:34:57 -0400263 /**
264 * Request the list of outstanding notifications (that is, those that are visible to the
John Spurlocka4294292014-03-24 18:02:32 -0400265 * current user). Useful when you don't know what's already been posted.
Daniel Sandler25cf8ce2013-04-24 15:34:57 -0400266 *
Chris Wrenf9536642014-04-17 10:01:54 -0400267 * @return An array of active notifications, sorted in natural order.
Daniel Sandler25cf8ce2013-04-24 15:34:57 -0400268 */
269 public StatusBarNotification[] getActiveNotifications() {
John Spurlockda9a3be2014-02-12 12:12:26 -0500270 if (!isBound()) return null;
Daniel Sandler25cf8ce2013-04-24 15:34:57 -0400271 try {
Christoph Studercee44ba2014-05-20 18:36:43 +0200272 ParceledListSlice<StatusBarNotification> parceledList =
273 getNotificationInterface().getActiveNotificationsFromListener(mWrapper);
274 List<StatusBarNotification> list = parceledList.getList();
275 return list.toArray(new StatusBarNotification[list.size()]);
John Spurlocka4294292014-03-24 18:02:32 -0400276 } catch (android.os.RemoteException ex) {
277 Log.v(TAG, "Unable to contact notification manager", ex);
278 }
279 return null;
280 }
281
282 /**
Christoph Studer05ad4822014-05-16 14:16:03 +0200283 * Returns current ranking information.
John Spurlocka4294292014-03-24 18:02:32 -0400284 *
Christoph Studer05ad4822014-05-16 14:16:03 +0200285 * <p>
286 * The returned object represents the current ranking snapshot and only
Christoph Studerd0694b62014-06-04 16:36:01 +0200287 * applies for currently active notifications.
288 * <p>
289 * Generally you should use the RankingMap that is passed with events such
290 * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)},
291 * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and
292 * so on. This method should only be used when needing access outside of
293 * such events, for example to retrieve the RankingMap right after
294 * initialization.
Christoph Studer05ad4822014-05-16 14:16:03 +0200295 *
Christoph Studerd0694b62014-06-04 16:36:01 +0200296 * @return A {@link RankingMap} object providing access to ranking information
John Spurlocka4294292014-03-24 18:02:32 -0400297 */
Christoph Studerd0694b62014-06-04 16:36:01 +0200298 public RankingMap getCurrentRanking() {
299 return mRankingMap;
Daniel Sandler25cf8ce2013-04-24 15:34:57 -0400300 }
301
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700302 @Override
303 public IBinder onBind(Intent intent) {
304 if (mWrapper == null) {
305 mWrapper = new INotificationListenerWrapper();
306 }
307 return mWrapper;
308 }
309
John Spurlockda9a3be2014-02-12 12:12:26 -0500310 private boolean isBound() {
311 if (mWrapper == null) {
312 Log.w(TAG, "Notification listener service not yet bound.");
313 return false;
314 }
315 return true;
316 }
317
Chris Wren1941fc72014-05-14 15:20:51 -0400318 /**
319 * Directly register this service with the Notification Manager.
320 *
321 * <p>Only system services may use this call. It will fail for non-system callers.
322 * Apps should ask the user to add their listener in Settings.
323 *
324 * @param componentName the component that will consume the notification information
325 * @param currentUser the user to use as the stream filter
326 * @hide
327 */
Jeff Brown5c507c12014-06-05 17:14:39 -0700328 @SystemApi
Chris Wren1941fc72014-05-14 15:20:51 -0400329 public void registerAsSystemService(ComponentName componentName, int currentUser)
330 throws RemoteException {
331 if (mWrapper == null) {
332 mWrapper = new INotificationListenerWrapper();
333 }
334 INotificationManager noMan = getNotificationInterface();
335 noMan.registerListener(mWrapper, componentName, currentUser);
336 mCurrentUser = currentUser;
337 }
338
339 /**
340 * Directly unregister this service from the Notification Manager.
341 *
342 * <P>This method will fail for listeners that were not registered
343 * with (@link registerAsService).
344 * @hide
345 */
Jeff Brown5c507c12014-06-05 17:14:39 -0700346 @SystemApi
Chris Wren1941fc72014-05-14 15:20:51 -0400347 public void unregisterAsSystemService() throws RemoteException {
348 if (mWrapper != null) {
349 INotificationManager noMan = getNotificationInterface();
350 noMan.unregisterListener(mWrapper, mCurrentUser);
351 }
352 }
353
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700354 private class INotificationListenerWrapper extends INotificationListener.Stub {
355 @Override
Chris Wrenf9536642014-04-17 10:01:54 -0400356 public void onNotificationPosted(StatusBarNotification sbn,
Christoph Studer05ad4822014-05-16 14:16:03 +0200357 NotificationRankingUpdate update) {
358 // protect subclass from concurrent modifications of (@link mNotificationKeys}.
359 synchronized (mWrapper) {
360 applyUpdate(update);
361 try {
Christoph Studerd0694b62014-06-04 16:36:01 +0200362 NotificationListenerService.this.onNotificationPosted(sbn, mRankingMap);
Christoph Studer05ad4822014-05-16 14:16:03 +0200363 } catch (Throwable t) {
364 Log.w(TAG, "Error running onNotificationPosted", t);
Chris Wrenf9536642014-04-17 10:01:54 -0400365 }
John Spurlockc133ab82013-06-10 15:16:22 -0400366 }
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700367 }
368 @Override
Chris Wrenf9536642014-04-17 10:01:54 -0400369 public void onNotificationRemoved(StatusBarNotification sbn,
Christoph Studer05ad4822014-05-16 14:16:03 +0200370 NotificationRankingUpdate update) {
371 // protect subclass from concurrent modifications of (@link mNotificationKeys}.
372 synchronized (mWrapper) {
373 applyUpdate(update);
374 try {
Christoph Studerd0694b62014-06-04 16:36:01 +0200375 NotificationListenerService.this.onNotificationRemoved(sbn, mRankingMap);
Christoph Studer05ad4822014-05-16 14:16:03 +0200376 } catch (Throwable t) {
377 Log.w(TAG, "Error running onNotificationRemoved", t);
Chris Wrenf9536642014-04-17 10:01:54 -0400378 }
John Spurlockc133ab82013-06-10 15:16:22 -0400379 }
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700380 }
John Spurlocka4294292014-03-24 18:02:32 -0400381 @Override
Christoph Studer05ad4822014-05-16 14:16:03 +0200382 public void onListenerConnected(NotificationRankingUpdate update) {
383 // protect subclass from concurrent modifications of (@link mNotificationKeys}.
384 synchronized (mWrapper) {
385 applyUpdate(update);
386 try {
Christoph Studercee44ba2014-05-20 18:36:43 +0200387 NotificationListenerService.this.onListenerConnected();
Christoph Studer05ad4822014-05-16 14:16:03 +0200388 } catch (Throwable t) {
389 Log.w(TAG, "Error running onListenerConnected", t);
Chris Wrenf9536642014-04-17 10:01:54 -0400390 }
John Spurlocka4294292014-03-24 18:02:32 -0400391 }
392 }
Chris Wrenf9536642014-04-17 10:01:54 -0400393 @Override
Christoph Studer05ad4822014-05-16 14:16:03 +0200394 public void onNotificationRankingUpdate(NotificationRankingUpdate update)
Chris Wrenf9536642014-04-17 10:01:54 -0400395 throws RemoteException {
Christoph Studer05ad4822014-05-16 14:16:03 +0200396 // protect subclass from concurrent modifications of (@link mNotificationKeys}.
397 synchronized (mWrapper) {
398 applyUpdate(update);
399 try {
Christoph Studerd0694b62014-06-04 16:36:01 +0200400 NotificationListenerService.this.onNotificationRankingUpdate(mRankingMap);
Christoph Studer05ad4822014-05-16 14:16:03 +0200401 } catch (Throwable t) {
402 Log.w(TAG, "Error running onNotificationRankingUpdate", t);
Chris Wrenf9536642014-04-17 10:01:54 -0400403 }
Chris Wrenf9536642014-04-17 10:01:54 -0400404 }
405 }
406 }
407
Christoph Studer05ad4822014-05-16 14:16:03 +0200408 private void applyUpdate(NotificationRankingUpdate update) {
Christoph Studerd0694b62014-06-04 16:36:01 +0200409 mRankingMap = new RankingMap(update);
410 }
411
412 /**
413 * Provides access to ranking information on a currently active
414 * notification.
415 *
416 * <p>
417 * Note that this object is not updated on notification events (such as
418 * {@link #onNotificationPosted(StatusBarNotification, RankingMap)},
419 * {@link #onNotificationRemoved(StatusBarNotification)}, etc.). Make sure
420 * to retrieve a new Ranking from the current {@link RankingMap} whenever
421 * a notification event occurs.
422 */
423 public static class Ranking {
424 private final String mKey;
425 private final int mRank;
426 private final boolean mIsAmbient;
427 private final boolean mIsInterceptedByDnd;
428
429 private Ranking(String key, int rank, boolean isAmbient, boolean isInterceptedByDnd) {
430 mKey = key;
431 mRank = rank;
432 mIsAmbient = isAmbient;
433 mIsInterceptedByDnd = isInterceptedByDnd;
434 }
435
436 /**
437 * Returns the key of the notification this Ranking applies to.
438 */
439 public String getKey() {
440 return mKey;
441 }
442
443 /**
444 * Returns the rank of the notification.
445 *
446 * @return the rank of the notification, that is the 0-based index in
447 * the list of active notifications.
448 */
449 public int getRank() {
450 return mRank;
451 }
452
453 /**
454 * Returns whether the notification is an ambient notification, that is
455 * a notification that doesn't require the user's immediate attention.
456 */
457 public boolean isAmbient() {
458 return mIsAmbient;
459 }
460
461 /**
462 * Returns whether the notification was intercepted by
463 * &quot;Do not disturb&quot;.
464 */
465 public boolean isInterceptedByDoNotDisturb() {
466 return mIsInterceptedByDnd;
467 }
Christoph Studer05ad4822014-05-16 14:16:03 +0200468 }
469
470 /**
471 * Provides access to ranking information on currently active
472 * notifications.
473 *
474 * <p>
475 * Note that this object represents a ranking snapshot that only applies to
476 * notifications active at the time of retrieval.
477 */
Christoph Studerd0694b62014-06-04 16:36:01 +0200478 public static class RankingMap implements Parcelable {
Christoph Studer05ad4822014-05-16 14:16:03 +0200479 private final NotificationRankingUpdate mRankingUpdate;
Christoph Studerd0694b62014-06-04 16:36:01 +0200480 private final ArrayMap<String, Ranking> mRankingCache;
481 private boolean mRankingCacheInitialized;
Christoph Studer05ad4822014-05-16 14:16:03 +0200482
Christoph Studerd0694b62014-06-04 16:36:01 +0200483 private RankingMap(NotificationRankingUpdate rankingUpdate) {
Christoph Studer05ad4822014-05-16 14:16:03 +0200484 mRankingUpdate = rankingUpdate;
Christoph Studerd0694b62014-06-04 16:36:01 +0200485 mRankingCache = new ArrayMap<>(rankingUpdate.getOrderedKeys().length);
Christoph Studer05ad4822014-05-16 14:16:03 +0200486 }
487
488 /**
489 * Request the list of notification keys in their current ranking
490 * order.
491 *
492 * @return An array of active notification keys, in their ranking order.
493 */
494 public String[] getOrderedKeys() {
495 return mRankingUpdate.getOrderedKeys();
496 }
497
498 /**
Christoph Studerd0694b62014-06-04 16:36:01 +0200499 * Returns the Ranking for the notification with the given key.
Christoph Studer05ad4822014-05-16 14:16:03 +0200500 *
Christoph Studerd0694b62014-06-04 16:36:01 +0200501 * @return the Ranking of the notification with the given key;
502 * <code>null</code> when the key is unknown.
Christoph Studer05ad4822014-05-16 14:16:03 +0200503 */
Christoph Studerd0694b62014-06-04 16:36:01 +0200504 public Ranking getRanking(String key) {
505 synchronized (mRankingCache) {
506 if (!mRankingCacheInitialized) {
507 initializeRankingCache();
508 mRankingCacheInitialized = true;
509 }
510 }
511 return mRankingCache.get(key);
512 }
513
514 private void initializeRankingCache() {
Christoph Studer05ad4822014-05-16 14:16:03 +0200515 String[] orderedKeys = mRankingUpdate.getOrderedKeys();
Christoph Studer05ad4822014-05-16 14:16:03 +0200516 int firstAmbientIndex = mRankingUpdate.getFirstAmbientIndex();
Christoph Studerd0694b62014-06-04 16:36:01 +0200517 for (int i = 0; i < orderedKeys.length; i++) {
518 String key = orderedKeys[i];
519 boolean isAmbient = firstAmbientIndex > -1 && firstAmbientIndex <= i;
520 boolean isInterceptedByDnd = false;
521 // TODO: Optimize.
522 for (String s : mRankingUpdate.getDndInterceptedKeys()) {
523 if (s.equals(key)) {
524 isInterceptedByDnd = true;
525 break;
526 }
Christoph Studer05ad4822014-05-16 14:16:03 +0200527 }
Christoph Studerd0694b62014-06-04 16:36:01 +0200528 mRankingCache.put(key, new Ranking(key, i, isAmbient, isInterceptedByDnd));
Christoph Studer05ad4822014-05-16 14:16:03 +0200529 }
Christoph Studer05ad4822014-05-16 14:16:03 +0200530 }
531
532 // ----------- Parcelable
533
534 @Override
535 public int describeContents() {
536 return 0;
537 }
538
539 @Override
540 public void writeToParcel(Parcel dest, int flags) {
541 dest.writeParcelable(mRankingUpdate, flags);
542 }
543
Christoph Studerd0694b62014-06-04 16:36:01 +0200544 public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() {
Christoph Studer05ad4822014-05-16 14:16:03 +0200545 @Override
Christoph Studerd0694b62014-06-04 16:36:01 +0200546 public RankingMap createFromParcel(Parcel source) {
Christoph Studer05ad4822014-05-16 14:16:03 +0200547 NotificationRankingUpdate rankingUpdate = source.readParcelable(null);
Christoph Studerd0694b62014-06-04 16:36:01 +0200548 return new RankingMap(rankingUpdate);
Christoph Studer05ad4822014-05-16 14:16:03 +0200549 }
550
551 @Override
Christoph Studerd0694b62014-06-04 16:36:01 +0200552 public RankingMap[] newArray(int size) {
553 return new RankingMap[size];
Christoph Studer05ad4822014-05-16 14:16:03 +0200554 }
555 };
Daniel Sandler5feceeb2013-03-22 18:29:23 -0700556 }
557}