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