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