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