NotificationListenerService.java revision 807749301fcbda892dfc8a5832b19acf7d1cf53b
1/*
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
19import android.annotation.SystemApi;
20import android.annotation.SdkConstant;
21import android.app.INotificationManager;
22import android.app.Notification;
23import android.app.Notification.Builder;
24import android.app.NotificationManager;
25import android.app.Service;
26import android.content.ComponentName;
27import android.content.Context;
28import android.content.Intent;
29import android.content.pm.ParceledListSlice;
30import android.os.Bundle;
31import android.os.IBinder;
32import android.os.Parcel;
33import android.os.Parcelable;
34import android.os.RemoteException;
35import android.os.ServiceManager;
36import android.util.ArrayMap;
37import android.util.ArraySet;
38import android.util.Log;
39
40import java.util.Collections;
41import java.util.List;
42
43/**
44 * A service that receives calls from the system when new notifications are
45 * posted or removed, or their ranking changed.
46 * <p>To extend this class, you must declare the service in your manifest file with
47 * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission
48 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
49 * <pre>
50 * &lt;service android:name=".NotificationListener"
51 *          android:label="&#64;string/service_name"
52 *          android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
53 *     &lt;intent-filter>
54 *         &lt;action android:name="android.service.notification.NotificationListenerService" />
55 *     &lt;/intent-filter>
56 * &lt;/service></pre>
57 */
58public abstract class NotificationListenerService extends Service {
59    // TAG = "NotificationListenerService[MySubclass]"
60    private final String TAG = NotificationListenerService.class.getSimpleName()
61            + "[" + getClass().getSimpleName() + "]";
62
63    /**
64     * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
65     *     Normal interruption filter.
66     */
67    public static final int INTERRUPTION_FILTER_ALL
68            = NotificationManager.INTERRUPTION_FILTER_ALL;
69
70    /**
71     * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
72     *     Priority interruption filter.
73     */
74    public static final int INTERRUPTION_FILTER_PRIORITY
75            = NotificationManager.INTERRUPTION_FILTER_PRIORITY;
76
77    /**
78     * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
79     *     No interruptions filter.
80     */
81    public static final int INTERRUPTION_FILTER_NONE
82            = NotificationManager.INTERRUPTION_FILTER_NONE;
83
84    /**
85     * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
86     *     Alarms only interruption filter.
87     */
88    public static final int INTERRUPTION_FILTER_ALARMS
89            = NotificationManager.INTERRUPTION_FILTER_ALARMS;
90
91    /** {@link #getCurrentInterruptionFilter() Interruption filter} constant - returned when
92     * the value is unavailable for any reason.  For example, before the notification listener
93     * is connected.
94     *
95     * {@see #onListenerConnected()}
96     */
97    public static final int INTERRUPTION_FILTER_UNKNOWN
98            = NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
99
100    /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
101     * should disable notification sound, vibrating and other visual or aural effects.
102     * This does not change the interruption filter, only the effects. **/
103    public static final int HINT_HOST_DISABLE_EFFECTS = 1;
104
105    /**
106     * The full trim of the StatusBarNotification including all its features.
107     *
108     * @hide
109     */
110    @SystemApi
111    public static final int TRIM_FULL = 0;
112
113    /**
114     * A light trim of the StatusBarNotification excluding the following features:
115     *
116     * <ol>
117     *     <li>{@link Notification#tickerView tickerView}</li>
118     *     <li>{@link Notification#contentView contentView}</li>
119     *     <li>{@link Notification#largeIcon largeIcon}</li>
120     *     <li>{@link Notification#bigContentView bigContentView}</li>
121     *     <li>{@link Notification#headsUpContentView headsUpContentView}</li>
122     *     <li>{@link Notification#EXTRA_LARGE_ICON extras[EXTRA_LARGE_ICON]}</li>
123     *     <li>{@link Notification#EXTRA_LARGE_ICON_BIG extras[EXTRA_LARGE_ICON_BIG]}</li>
124     *     <li>{@link Notification#EXTRA_PICTURE extras[EXTRA_PICTURE]}</li>
125     *     <li>{@link Notification#EXTRA_BIG_TEXT extras[EXTRA_BIG_TEXT]}</li>
126     * </ol>
127     *
128     * @hide
129     */
130    @SystemApi
131    public static final int TRIM_LIGHT = 1;
132
133    private INotificationListenerWrapper mWrapper = null;
134    private RankingMap mRankingMap;
135
136    private INotificationManager mNoMan;
137
138    /** Only valid after a successful call to (@link registerAsService}. */
139    private int mCurrentUser;
140
141
142    // This context is required for system services since NotificationListenerService isn't
143    // started as a real Service and hence no context is available.
144    private Context mSystemContext;
145
146    /**
147     * The {@link Intent} that must be declared as handled by the service.
148     */
149    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
150    public static final String SERVICE_INTERFACE
151            = "android.service.notification.NotificationListenerService";
152
153    /**
154     * Implement this method to learn about new notifications as they are posted by apps.
155     *
156     * @param sbn A data structure encapsulating the original {@link android.app.Notification}
157     *            object as well as its identifying information (tag and id) and source
158     *            (package name).
159     */
160    public void onNotificationPosted(StatusBarNotification sbn) {
161        // optional
162    }
163
164    /**
165     * Implement this method to learn about new notifications as they are posted by apps.
166     *
167     * @param sbn A data structure encapsulating the original {@link android.app.Notification}
168     *            object as well as its identifying information (tag and id) and source
169     *            (package name).
170     * @param rankingMap The current ranking map that can be used to retrieve ranking information
171     *                   for active notifications, including the newly posted one.
172     */
173    public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
174        onNotificationPosted(sbn);
175    }
176
177    /**
178     * Implement this method to learn when notifications are removed.
179     * <P>
180     * This might occur because the user has dismissed the notification using system UI (or another
181     * notification listener) or because the app has withdrawn the notification.
182     * <P>
183     * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
184     * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
185     * fields such as {@link android.app.Notification#contentView} and
186     * {@link android.app.Notification#largeIcon}. However, all other fields on
187     * {@link StatusBarNotification}, sufficient to match this call with a prior call to
188     * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
189     *
190     * @param sbn A data structure encapsulating at least the original information (tag and id)
191     *            and source (package name) used to post the {@link android.app.Notification} that
192     *            was just removed.
193     */
194    public void onNotificationRemoved(StatusBarNotification sbn) {
195        // optional
196    }
197
198    /**
199     * Implement this method to learn when notifications are removed.
200     * <P>
201     * This might occur because the user has dismissed the notification using system UI (or another
202     * notification listener) or because the app has withdrawn the notification.
203     * <P>
204     * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
205     * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
206     * fields such as {@link android.app.Notification#contentView} and
207     * {@link android.app.Notification#largeIcon}. However, all other fields on
208     * {@link StatusBarNotification}, sufficient to match this call with a prior call to
209     * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
210     *
211     * @param sbn A data structure encapsulating at least the original information (tag and id)
212     *            and source (package name) used to post the {@link android.app.Notification} that
213     *            was just removed.
214     * @param rankingMap The current ranking map that can be used to retrieve ranking information
215     *                   for active notifications.
216     *
217     */
218    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
219        onNotificationRemoved(sbn);
220    }
221
222    /**
223     * Implement this method to learn about when the listener is enabled and connected to
224     * the notification manager.  You are safe to call {@link #getActiveNotifications()}
225     * at this time.
226     */
227    public void onListenerConnected() {
228        // optional
229    }
230
231    /**
232     * Implement this method to be notified when the notification ranking changes.
233     *
234     * @param rankingMap The current ranking map that can be used to retrieve ranking information
235     *                   for active notifications.
236     */
237    public void onNotificationRankingUpdate(RankingMap rankingMap) {
238        // optional
239    }
240
241    /**
242     * Implement this method to be notified when the
243     * {@link #getCurrentListenerHints() Listener hints} change.
244     *
245     * @param hints The current {@link #getCurrentListenerHints() listener hints}.
246     */
247    public void onListenerHintsChanged(int hints) {
248        // optional
249    }
250
251    /**
252     * Implement this method to be notified when the
253     * {@link #getCurrentInterruptionFilter() interruption filter} changed.
254     *
255     * @param interruptionFilter The current
256     *     {@link #getCurrentInterruptionFilter() interruption filter}.
257     */
258    public void onInterruptionFilterChanged(int interruptionFilter) {
259        // optional
260    }
261
262    private final INotificationManager getNotificationInterface() {
263        if (mNoMan == null) {
264            mNoMan = INotificationManager.Stub.asInterface(
265                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
266        }
267        return mNoMan;
268    }
269
270    /**
271     * Inform the notification manager about dismissal of a single notification.
272     * <p>
273     * Use this if your listener has a user interface that allows the user to dismiss individual
274     * notifications, similar to the behavior of Android's status bar and notification panel.
275     * It should be called after the user dismisses a single notification using your UI;
276     * upon being informed, the notification manager will actually remove the notification
277     * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
278     * <P>
279     * <b>Note:</b> If your listener allows the user to fire a notification's
280     * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
281     * this method at that time <i>if</i> the Notification in question has the
282     * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
283     *
284     * @param pkg Package of the notifying app.
285     * @param tag Tag of the notification as specified by the notifying app in
286     *     {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
287     * @param id  ID of the notification as specified by the notifying app in
288     *     {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
289     * <p>
290     * @deprecated Use {@link #cancelNotification(String key)}
291     * instead. Beginning with {@link android.os.Build.VERSION_CODES#LOLLIPOP} this method will no longer
292     * cancel the notification. It will continue to cancel the notification for applications
293     * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
294     */
295    public final void cancelNotification(String pkg, String tag, int id) {
296        if (!isBound()) return;
297        try {
298            getNotificationInterface().cancelNotificationFromListener(
299                    mWrapper, pkg, tag, id);
300        } catch (android.os.RemoteException ex) {
301            Log.v(TAG, "Unable to contact notification manager", ex);
302        }
303    }
304
305    /**
306     * Inform the notification manager about dismissal of a single notification.
307     * <p>
308     * Use this if your listener has a user interface that allows the user to dismiss individual
309     * notifications, similar to the behavior of Android's status bar and notification panel.
310     * It should be called after the user dismisses a single notification using your UI;
311     * upon being informed, the notification manager will actually remove the notification
312     * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
313     * <P>
314     * <b>Note:</b> If your listener allows the user to fire a notification's
315     * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
316     * this method at that time <i>if</i> the Notification in question has the
317     * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
318     * <p>
319     * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}.
320     */
321    public final void cancelNotification(String key) {
322        if (!isBound()) return;
323        try {
324            getNotificationInterface().cancelNotificationsFromListener(mWrapper,
325                    new String[] {key});
326        } catch (android.os.RemoteException ex) {
327            Log.v(TAG, "Unable to contact notification manager", ex);
328        }
329    }
330
331    /**
332     * Inform the notification manager about dismissal of all notifications.
333     * <p>
334     * Use this if your listener has a user interface that allows the user to dismiss all
335     * notifications, similar to the behavior of Android's status bar and notification panel.
336     * It should be called after the user invokes the "dismiss all" function of your UI;
337     * upon being informed, the notification manager will actually remove all active notifications
338     * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks.
339     *
340     * {@see #cancelNotification(String, String, int)}
341     */
342    public final void cancelAllNotifications() {
343        cancelNotifications(null /*all*/);
344    }
345
346    /**
347     * Inform the notification manager about dismissal of specific notifications.
348     * <p>
349     * Use this if your listener has a user interface that allows the user to dismiss
350     * multiple notifications at once.
351     *
352     * @param keys Notifications to dismiss, or {@code null} to dismiss all.
353     *
354     * {@see #cancelNotification(String, String, int)}
355     */
356    public final void cancelNotifications(String[] keys) {
357        if (!isBound()) return;
358        try {
359            getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys);
360        } catch (android.os.RemoteException ex) {
361            Log.v(TAG, "Unable to contact notification manager", ex);
362        }
363    }
364
365    /**
366     * Inform the notification manager that these notifications have been viewed by the
367     * user.
368     * @param keys Notifications to mark as seen.
369     */
370    public final void setNotificationsShown(String[] keys) {
371        if (!isBound()) return;
372        try {
373            getNotificationInterface().setNotificationsShownFromListener(mWrapper, keys);
374        } catch (android.os.RemoteException ex) {
375            Log.v(TAG, "Unable to contact notification manager", ex);
376        }
377    }
378
379    /**
380     * Sets the notification trim that will be received via {@link #onNotificationPosted}.
381     *
382     * <p>
383     * Setting a trim other than {@link #TRIM_FULL} enables listeners that don't need access to the
384     * full notification features right away to reduce their memory footprint. Full notifications
385     * can be requested on-demand via {@link #getActiveNotifications(int)}.
386     *
387     * <p>
388     * Set to {@link #TRIM_FULL} initially.
389     *
390     * @hide
391     *
392     * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}.
393     *             See <code>TRIM_*</code> constants.
394     */
395    @SystemApi
396    public final void setOnNotificationPostedTrim(int trim) {
397        if (!isBound()) return;
398        try {
399            getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim);
400        } catch (RemoteException ex) {
401            Log.v(TAG, "Unable to contact notification manager", ex);
402        }
403    }
404
405    /**
406     * Request the list of outstanding notifications (that is, those that are visible to the
407     * current user). Useful when you don't know what's already been posted.
408     *
409     * @return An array of active notifications, sorted in natural order.
410     */
411    public StatusBarNotification[] getActiveNotifications() {
412        return getActiveNotifications(null, TRIM_FULL);
413    }
414
415    /**
416     * Request the list of outstanding notifications (that is, those that are visible to the
417     * current user). Useful when you don't know what's already been posted.
418     *
419     * @hide
420     *
421     * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
422     * @return An array of active notifications, sorted in natural order.
423     */
424    @SystemApi
425    public StatusBarNotification[] getActiveNotifications(int trim) {
426        return getActiveNotifications(null, trim);
427    }
428
429    /**
430     * Request one or more notifications by key. Useful if you have been keeping track of
431     * notifications but didn't want to retain the bits, and now need to go back and extract
432     * more data out of those notifications.
433     *
434     * @param keys the keys of the notifications to request
435     * @return An array of notifications corresponding to the requested keys, in the
436     * same order as the key list.
437     */
438    public StatusBarNotification[] getActiveNotifications(String[] keys) {
439        return getActiveNotifications(keys, TRIM_FULL);
440    }
441
442    /**
443     * Request one or more notifications by key. Useful if you have been keeping track of
444     * notifications but didn't want to retain the bits, and now need to go back and extract
445     * more data out of those notifications.
446     *
447     * @hide
448     *
449     * @param keys the keys of the notifications to request
450     * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
451     * @return An array of notifications corresponding to the requested keys, in the
452     * same order as the key list.
453     */
454    @SystemApi
455    public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) {
456        if (!isBound())
457            return null;
458        try {
459            ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
460                    .getActiveNotificationsFromListener(mWrapper, keys, trim);
461            List<StatusBarNotification> list = parceledList.getList();
462
463            int N = list.size();
464            for (int i = 0; i < N; i++) {
465                Notification notification = list.get(i).getNotification();
466                Builder.rebuild(getContext(), notification);
467            }
468            return list.toArray(new StatusBarNotification[N]);
469        } catch (android.os.RemoteException ex) {
470            Log.v(TAG, "Unable to contact notification manager", ex);
471        }
472        return null;
473    }
474
475    /**
476     * Gets the set of hints representing current state.
477     *
478     * <p>
479     * The current state may differ from the requested state if the hint represents state
480     * shared across all listeners or a feature the notification host does not support or refuses
481     * to grant.
482     *
483     * @return Zero or more of the HINT_ constants.
484     */
485    public final int getCurrentListenerHints() {
486        if (!isBound()) return 0;
487        try {
488            return getNotificationInterface().getHintsFromListener(mWrapper);
489        } catch (android.os.RemoteException ex) {
490            Log.v(TAG, "Unable to contact notification manager", ex);
491            return 0;
492        }
493    }
494
495    /**
496     * Gets the current notification interruption filter active on the host.
497     *
498     * <p>
499     * The interruption filter defines which notifications are allowed to interrupt the user
500     * (e.g. via sound &amp; vibration) and is applied globally. Listeners can find out whether
501     * a specific notification matched the interruption filter via
502     * {@link Ranking#matchesInterruptionFilter()}.
503     * <p>
504     * The current filter may differ from the previously requested filter if the notification host
505     * does not support or refuses to apply the requested filter, or if another component changed
506     * the filter in the meantime.
507     * <p>
508     * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
509     *
510     * @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when
511     * unavailable.
512     */
513    public final int getCurrentInterruptionFilter() {
514        if (!isBound()) return INTERRUPTION_FILTER_UNKNOWN;
515        try {
516            return getNotificationInterface().getInterruptionFilterFromListener(mWrapper);
517        } catch (android.os.RemoteException ex) {
518            Log.v(TAG, "Unable to contact notification manager", ex);
519            return INTERRUPTION_FILTER_UNKNOWN;
520        }
521    }
522
523    /**
524     * Sets the desired {@link #getCurrentListenerHints() listener hints}.
525     *
526     * <p>
527     * This is merely a request, the host may or may not choose to take action depending
528     * on other listener requests or other global state.
529     * <p>
530     * Listen for updates using {@link #onListenerHintsChanged(int)}.
531     *
532     * @param hints One or more of the HINT_ constants.
533     */
534    public final void requestListenerHints(int hints) {
535        if (!isBound()) return;
536        try {
537            getNotificationInterface().requestHintsFromListener(mWrapper, hints);
538        } catch (android.os.RemoteException ex) {
539            Log.v(TAG, "Unable to contact notification manager", ex);
540        }
541    }
542
543    /**
544     * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}.
545     *
546     * <p>
547     * This is merely a request, the host may or may not choose to apply the requested
548     * interruption filter depending on other listener requests or other global state.
549     * <p>
550     * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
551     *
552     * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants.
553     */
554    public final void requestInterruptionFilter(int interruptionFilter) {
555        if (!isBound()) return;
556        try {
557            getNotificationInterface()
558                    .requestInterruptionFilterFromListener(mWrapper, interruptionFilter);
559        } catch (android.os.RemoteException ex) {
560            Log.v(TAG, "Unable to contact notification manager", ex);
561        }
562    }
563
564    /**
565     * Returns current ranking information.
566     *
567     * <p>
568     * The returned object represents the current ranking snapshot and only
569     * applies for currently active notifications.
570     * <p>
571     * Generally you should use the RankingMap that is passed with events such
572     * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)},
573     * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and
574     * so on. This method should only be used when needing access outside of
575     * such events, for example to retrieve the RankingMap right after
576     * initialization.
577     *
578     * @return A {@link RankingMap} object providing access to ranking information
579     */
580    public RankingMap getCurrentRanking() {
581        return mRankingMap;
582    }
583
584    @Override
585    public IBinder onBind(Intent intent) {
586        if (mWrapper == null) {
587            mWrapper = new INotificationListenerWrapper();
588        }
589        return mWrapper;
590    }
591
592    private boolean isBound() {
593        if (mWrapper == null) {
594            Log.w(TAG, "Notification listener service not yet bound.");
595            return false;
596        }
597        return true;
598    }
599
600    /**
601     * Directly register this service with the Notification Manager.
602     *
603     * <p>Only system services may use this call. It will fail for non-system callers.
604     * Apps should ask the user to add their listener in Settings.
605     *
606     * @param context Context required for accessing resources. Since this service isn't
607     *    launched as a real Service when using this method, a context has to be passed in.
608     * @param componentName the component that will consume the notification information
609     * @param currentUser the user to use as the stream filter
610     * @hide
611     */
612    @SystemApi
613    public void registerAsSystemService(Context context, ComponentName componentName,
614            int currentUser) throws RemoteException {
615        mSystemContext = context;
616        if (mWrapper == null) {
617            mWrapper = new INotificationListenerWrapper();
618        }
619        INotificationManager noMan = getNotificationInterface();
620        noMan.registerListener(mWrapper, componentName, currentUser);
621        mCurrentUser = currentUser;
622    }
623
624    /**
625     * Directly unregister this service from the Notification Manager.
626     *
627     * <P>This method will fail for listeners that were not registered
628     * with (@link registerAsService).
629     * @hide
630     */
631    @SystemApi
632    public void unregisterAsSystemService() throws RemoteException {
633        if (mWrapper != null) {
634            INotificationManager noMan = getNotificationInterface();
635            noMan.unregisterListener(mWrapper, mCurrentUser);
636        }
637    }
638
639    private class INotificationListenerWrapper extends INotificationListener.Stub {
640        @Override
641        public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
642                NotificationRankingUpdate update) {
643            StatusBarNotification sbn;
644            try {
645                sbn = sbnHolder.get();
646            } catch (RemoteException e) {
647                Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
648                return;
649            }
650            Notification.Builder.rebuild(getContext(), sbn.getNotification());
651
652            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
653            synchronized (mWrapper) {
654                applyUpdate(update);
655                try {
656                    NotificationListenerService.this.onNotificationPosted(sbn, mRankingMap);
657                } catch (Throwable t) {
658                    Log.w(TAG, "Error running onNotificationPosted", t);
659                }
660            }
661        }
662        @Override
663        public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
664                NotificationRankingUpdate update) {
665            StatusBarNotification sbn;
666            try {
667                sbn = sbnHolder.get();
668            } catch (RemoteException e) {
669                Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e);
670                return;
671            }
672            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
673            synchronized (mWrapper) {
674                applyUpdate(update);
675                try {
676                    NotificationListenerService.this.onNotificationRemoved(sbn, mRankingMap);
677                } catch (Throwable t) {
678                    Log.w(TAG, "Error running onNotificationRemoved", t);
679                }
680            }
681        }
682        @Override
683        public void onListenerConnected(NotificationRankingUpdate update) {
684            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
685            synchronized (mWrapper) {
686                applyUpdate(update);
687                try {
688                    NotificationListenerService.this.onListenerConnected();
689                } catch (Throwable t) {
690                    Log.w(TAG, "Error running onListenerConnected", t);
691                }
692            }
693        }
694        @Override
695        public void onNotificationRankingUpdate(NotificationRankingUpdate update)
696                throws RemoteException {
697            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
698            synchronized (mWrapper) {
699                applyUpdate(update);
700                try {
701                    NotificationListenerService.this.onNotificationRankingUpdate(mRankingMap);
702                } catch (Throwable t) {
703                    Log.w(TAG, "Error running onNotificationRankingUpdate", t);
704                }
705            }
706        }
707        @Override
708        public void onListenerHintsChanged(int hints) throws RemoteException {
709            try {
710                NotificationListenerService.this.onListenerHintsChanged(hints);
711            } catch (Throwable t) {
712                Log.w(TAG, "Error running onListenerHintsChanged", t);
713            }
714        }
715
716        @Override
717        public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException {
718            try {
719                NotificationListenerService.this.onInterruptionFilterChanged(interruptionFilter);
720            } catch (Throwable t) {
721                Log.w(TAG, "Error running onInterruptionFilterChanged", t);
722            }
723        }
724    }
725
726    private void applyUpdate(NotificationRankingUpdate update) {
727        mRankingMap = new RankingMap(update);
728    }
729
730    private Context getContext() {
731        if (mSystemContext != null) {
732            return mSystemContext;
733        }
734        return this;
735    }
736
737    /**
738     * Stores ranking related information on a currently active notification.
739     *
740     * <p>
741     * Ranking objects aren't automatically updated as notification events
742     * occur. Instead, ranking information has to be retrieved again via the
743     * current {@link RankingMap}.
744     */
745    public static class Ranking {
746        /** Value signifying that the user has not expressed a per-app visibility override value.
747         * @hide */
748        public static final int VISIBILITY_NO_OVERRIDE = -1000;
749
750        private String mKey;
751        private int mRank = -1;
752        private boolean mIsAmbient;
753        private boolean mMatchesInterruptionFilter;
754        private int mVisibilityOverride;
755
756        public Ranking() {}
757
758        /**
759         * Returns the key of the notification this Ranking applies to.
760         */
761        public String getKey() {
762            return mKey;
763        }
764
765        /**
766         * Returns the rank of the notification.
767         *
768         * @return the rank of the notification, that is the 0-based index in
769         *     the list of active notifications.
770         */
771        public int getRank() {
772            return mRank;
773        }
774
775        /**
776         * Returns whether the notification is an ambient notification, that is
777         * a notification that doesn't require the user's immediate attention.
778         */
779        public boolean isAmbient() {
780            return mIsAmbient;
781        }
782
783        /**
784         * Returns the user specificed visibility for the package that posted
785         * this notification, or
786         * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
787         * no such preference has been expressed.
788         * @hide
789         */
790        public int getVisibilityOverride() {
791            return mVisibilityOverride;
792        }
793
794
795        /**
796         * Returns whether the notification matches the user's interruption
797         * filter.
798         *
799         * @return {@code true} if the notification is allowed by the filter, or
800         * {@code false} if it is blocked.
801         */
802        public boolean matchesInterruptionFilter() {
803            return mMatchesInterruptionFilter;
804        }
805
806        private void populate(String key, int rank, boolean isAmbient,
807                boolean matchesInterruptionFilter, int visibilityOverride) {
808            mKey = key;
809            mRank = rank;
810            mIsAmbient = isAmbient;
811            mMatchesInterruptionFilter = matchesInterruptionFilter;
812            mVisibilityOverride = visibilityOverride;
813        }
814    }
815
816    /**
817     * Provides access to ranking information on currently active
818     * notifications.
819     *
820     * <p>
821     * Note that this object represents a ranking snapshot that only applies to
822     * notifications active at the time of retrieval.
823     */
824    public static class RankingMap implements Parcelable {
825        private final NotificationRankingUpdate mRankingUpdate;
826        private ArrayMap<String,Integer> mRanks;
827        private ArraySet<Object> mIntercepted;
828        private ArrayMap<String, Integer> mVisibilityOverrides;
829
830        private RankingMap(NotificationRankingUpdate rankingUpdate) {
831            mRankingUpdate = rankingUpdate;
832        }
833
834        /**
835         * Request the list of notification keys in their current ranking
836         * order.
837         *
838         * @return An array of active notification keys, in their ranking order.
839         */
840        public String[] getOrderedKeys() {
841            return mRankingUpdate.getOrderedKeys();
842        }
843
844        /**
845         * Populates outRanking with ranking information for the notification
846         * with the given key.
847         *
848         * @return true if a valid key has been passed and outRanking has
849         *     been populated; false otherwise
850         */
851        public boolean getRanking(String key, Ranking outRanking) {
852            int rank = getRank(key);
853            outRanking.populate(key, rank, isAmbient(key), !isIntercepted(key),
854                    getVisibilityOverride(key));
855            return rank >= 0;
856        }
857
858        private int getRank(String key) {
859            synchronized (this) {
860                if (mRanks == null) {
861                    buildRanksLocked();
862                }
863            }
864            Integer rank = mRanks.get(key);
865            return rank != null ? rank : -1;
866        }
867
868        private boolean isAmbient(String key) {
869            int firstAmbientIndex = mRankingUpdate.getFirstAmbientIndex();
870            if (firstAmbientIndex < 0) {
871                return false;
872            }
873            int rank = getRank(key);
874            return rank >= 0 && rank >= firstAmbientIndex;
875        }
876
877        private boolean isIntercepted(String key) {
878            synchronized (this) {
879                if (mIntercepted == null) {
880                    buildInterceptedSetLocked();
881                }
882            }
883            return mIntercepted.contains(key);
884        }
885
886        private int getVisibilityOverride(String key) {
887            synchronized (this) {
888                if (mVisibilityOverrides == null) {
889                    buildVisibilityOverridesLocked();
890                }
891            }
892            Integer overide = mVisibilityOverrides.get(key);
893            if (overide == null) {
894                return Ranking.VISIBILITY_NO_OVERRIDE;
895            }
896            return overide.intValue();
897        }
898
899        // Locked by 'this'
900        private void buildRanksLocked() {
901            String[] orderedKeys = mRankingUpdate.getOrderedKeys();
902            mRanks = new ArrayMap<>(orderedKeys.length);
903            for (int i = 0; i < orderedKeys.length; i++) {
904                String key = orderedKeys[i];
905                mRanks.put(key, i);
906            }
907        }
908
909        // Locked by 'this'
910        private void buildInterceptedSetLocked() {
911            String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys();
912            mIntercepted = new ArraySet<>(dndInterceptedKeys.length);
913            Collections.addAll(mIntercepted, dndInterceptedKeys);
914        }
915
916        // Locked by 'this'
917        private void buildVisibilityOverridesLocked() {
918            Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides();
919            mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size());
920            for (String key: visibilityBundle.keySet()) {
921               mVisibilityOverrides.put(key, visibilityBundle.getInt(key));
922            }
923        }
924
925        // ----------- Parcelable
926
927        @Override
928        public int describeContents() {
929            return 0;
930        }
931
932        @Override
933        public void writeToParcel(Parcel dest, int flags) {
934            dest.writeParcelable(mRankingUpdate, flags);
935        }
936
937        public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() {
938            @Override
939            public RankingMap createFromParcel(Parcel source) {
940                NotificationRankingUpdate rankingUpdate = source.readParcelable(null);
941                return new RankingMap(rankingUpdate);
942            }
943
944            @Override
945            public RankingMap[] newArray(int size) {
946                return new RankingMap[size];
947            }
948        };
949    }
950}
951