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