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