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