NotificationListenerService.java revision f5a7838e19c1cb6437a8b32ba9980ac1ce8804e2
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            }
472            return list.toArray(new StatusBarNotification[N]);
473        } catch (android.os.RemoteException ex) {
474            Log.v(TAG, "Unable to contact notification manager", ex);
475        }
476        return null;
477    }
478
479    /**
480     * Gets the set of hints representing current state.
481     *
482     * <p>
483     * The current state may differ from the requested state if the hint represents state
484     * shared across all listeners or a feature the notification host does not support or refuses
485     * to grant.
486     *
487     * @return Zero or more of the HINT_ constants.
488     */
489    public final int getCurrentListenerHints() {
490        if (!isBound()) return 0;
491        try {
492            return getNotificationInterface().getHintsFromListener(mWrapper);
493        } catch (android.os.RemoteException ex) {
494            Log.v(TAG, "Unable to contact notification manager", ex);
495            return 0;
496        }
497    }
498
499    /**
500     * Gets the current notification interruption filter active on the host.
501     *
502     * <p>
503     * The interruption filter defines which notifications are allowed to interrupt the user
504     * (e.g. via sound &amp; vibration) and is applied globally. Listeners can find out whether
505     * a specific notification matched the interruption filter via
506     * {@link Ranking#matchesInterruptionFilter()}.
507     * <p>
508     * The current filter may differ from the previously requested filter if the notification host
509     * does not support or refuses to apply the requested filter, or if another component changed
510     * the filter in the meantime.
511     * <p>
512     * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
513     *
514     * @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when
515     * unavailable.
516     */
517    public final int getCurrentInterruptionFilter() {
518        if (!isBound()) return INTERRUPTION_FILTER_UNKNOWN;
519        try {
520            return getNotificationInterface().getInterruptionFilterFromListener(mWrapper);
521        } catch (android.os.RemoteException ex) {
522            Log.v(TAG, "Unable to contact notification manager", ex);
523            return INTERRUPTION_FILTER_UNKNOWN;
524        }
525    }
526
527    /**
528     * Sets the desired {@link #getCurrentListenerHints() listener hints}.
529     *
530     * <p>
531     * This is merely a request, the host may or may not choose to take action depending
532     * on other listener requests or other global state.
533     * <p>
534     * Listen for updates using {@link #onListenerHintsChanged(int)}.
535     *
536     * @param hints One or more of the HINT_ constants.
537     */
538    public final void requestListenerHints(int hints) {
539        if (!isBound()) return;
540        try {
541            getNotificationInterface().requestHintsFromListener(mWrapper, hints);
542        } catch (android.os.RemoteException ex) {
543            Log.v(TAG, "Unable to contact notification manager", ex);
544        }
545    }
546
547    /**
548     * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}.
549     *
550     * <p>
551     * This is merely a request, the host may or may not choose to apply the requested
552     * interruption filter depending on other listener requests or other global state.
553     * <p>
554     * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
555     *
556     * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants.
557     */
558    public final void requestInterruptionFilter(int interruptionFilter) {
559        if (!isBound()) return;
560        try {
561            getNotificationInterface()
562                    .requestInterruptionFilterFromListener(mWrapper, interruptionFilter);
563        } catch (android.os.RemoteException ex) {
564            Log.v(TAG, "Unable to contact notification manager", ex);
565        }
566    }
567
568    /**
569     * Returns current ranking information.
570     *
571     * <p>
572     * The returned object represents the current ranking snapshot and only
573     * applies for currently active notifications.
574     * <p>
575     * Generally you should use the RankingMap that is passed with events such
576     * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)},
577     * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and
578     * so on. This method should only be used when needing access outside of
579     * such events, for example to retrieve the RankingMap right after
580     * initialization.
581     *
582     * @return A {@link RankingMap} object providing access to ranking information
583     */
584    public RankingMap getCurrentRanking() {
585        return mRankingMap;
586    }
587
588    @Override
589    public IBinder onBind(Intent intent) {
590        if (mWrapper == null) {
591            mWrapper = new INotificationListenerWrapper();
592        }
593        return mWrapper;
594    }
595
596    private boolean isBound() {
597        if (mWrapper == null) {
598            Log.w(TAG, "Notification listener service not yet bound.");
599            return false;
600        }
601        return true;
602    }
603
604    /**
605     * Directly register this service with the Notification Manager.
606     *
607     * <p>Only system services may use this call. It will fail for non-system callers.
608     * Apps should ask the user to add their listener in Settings.
609     *
610     * @param context Context required for accessing resources. Since this service isn't
611     *    launched as a real Service when using this method, a context has to be passed in.
612     * @param componentName the component that will consume the notification information
613     * @param currentUser the user to use as the stream filter
614     * @hide
615     */
616    @SystemApi
617    public void registerAsSystemService(Context context, ComponentName componentName,
618            int currentUser) throws RemoteException {
619        mSystemContext = context;
620        if (mWrapper == null) {
621            mWrapper = new INotificationListenerWrapper();
622        }
623        INotificationManager noMan = getNotificationInterface();
624        noMan.registerListener(mWrapper, componentName, currentUser);
625        mCurrentUser = currentUser;
626    }
627
628    /**
629     * Directly unregister this service from the Notification Manager.
630     *
631     * <P>This method will fail for listeners that were not registered
632     * with (@link registerAsService).
633     * @hide
634     */
635    @SystemApi
636    public void unregisterAsSystemService() throws RemoteException {
637        if (mWrapper != null) {
638            INotificationManager noMan = getNotificationInterface();
639            noMan.unregisterListener(mWrapper, mCurrentUser);
640        }
641    }
642
643    /** Convert new-style Icons to legacy representations for pre-M clients. */
644    private void createLegacyIconExtras(Notification n) {
645        Icon smallIcon = n.getSmallIcon();
646        Icon largeIcon = n.getLargeIcon();
647        if (smallIcon.getType() == Icon.TYPE_RESOURCE) {
648            n.extras.putInt(Notification.EXTRA_SMALL_ICON, smallIcon.getResId());
649            n.icon = smallIcon.getResId();
650        }
651        if (largeIcon != null) {
652            Drawable d = largeIcon.loadDrawable(getContext());
653            if (d != null && d instanceof BitmapDrawable) {
654                final Bitmap largeIconBits = ((BitmapDrawable) d).getBitmap();
655                n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, largeIconBits);
656                n.largeIcon = largeIconBits;
657            }
658        }
659    }
660
661    private class INotificationListenerWrapper extends INotificationListener.Stub {
662        @Override
663        public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
664                NotificationRankingUpdate update) {
665            StatusBarNotification sbn;
666            try {
667                sbn = sbnHolder.get();
668            } catch (RemoteException e) {
669                Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
670                return;
671            }
672            Notification.Builder.rebuild(getContext(), sbn.getNotification());
673
674            // convert icon metadata to legacy format for older clients
675            createLegacyIconExtras(sbn.getNotification());
676
677            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
678            synchronized (mWrapper) {
679                applyUpdate(update);
680                try {
681                    NotificationListenerService.this.onNotificationPosted(sbn, mRankingMap);
682                } catch (Throwable t) {
683                    Log.w(TAG, "Error running onNotificationPosted", t);
684                }
685            }
686        }
687        @Override
688        public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
689                NotificationRankingUpdate update) {
690            StatusBarNotification sbn;
691            try {
692                sbn = sbnHolder.get();
693            } catch (RemoteException e) {
694                Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e);
695                return;
696            }
697            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
698            synchronized (mWrapper) {
699                applyUpdate(update);
700                try {
701                    NotificationListenerService.this.onNotificationRemoved(sbn, mRankingMap);
702                } catch (Throwable t) {
703                    Log.w(TAG, "Error running onNotificationRemoved", t);
704                }
705            }
706        }
707        @Override
708        public void onListenerConnected(NotificationRankingUpdate update) {
709            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
710            synchronized (mWrapper) {
711                applyUpdate(update);
712                try {
713                    NotificationListenerService.this.onListenerConnected();
714                } catch (Throwable t) {
715                    Log.w(TAG, "Error running onListenerConnected", t);
716                }
717            }
718        }
719        @Override
720        public void onNotificationRankingUpdate(NotificationRankingUpdate update)
721                throws RemoteException {
722            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
723            synchronized (mWrapper) {
724                applyUpdate(update);
725                try {
726                    NotificationListenerService.this.onNotificationRankingUpdate(mRankingMap);
727                } catch (Throwable t) {
728                    Log.w(TAG, "Error running onNotificationRankingUpdate", t);
729                }
730            }
731        }
732        @Override
733        public void onListenerHintsChanged(int hints) throws RemoteException {
734            try {
735                NotificationListenerService.this.onListenerHintsChanged(hints);
736            } catch (Throwable t) {
737                Log.w(TAG, "Error running onListenerHintsChanged", t);
738            }
739        }
740
741        @Override
742        public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException {
743            try {
744                NotificationListenerService.this.onInterruptionFilterChanged(interruptionFilter);
745            } catch (Throwable t) {
746                Log.w(TAG, "Error running onInterruptionFilterChanged", t);
747            }
748        }
749    }
750
751    private void applyUpdate(NotificationRankingUpdate update) {
752        mRankingMap = new RankingMap(update);
753    }
754
755    private Context getContext() {
756        if (mSystemContext != null) {
757            return mSystemContext;
758        }
759        return this;
760    }
761
762    /**
763     * Stores ranking related information on a currently active notification.
764     *
765     * <p>
766     * Ranking objects aren't automatically updated as notification events
767     * occur. Instead, ranking information has to be retrieved again via the
768     * current {@link RankingMap}.
769     */
770    public static class Ranking {
771        /** Value signifying that the user has not expressed a per-app visibility override value.
772         * @hide */
773        public static final int VISIBILITY_NO_OVERRIDE = -1000;
774
775        private String mKey;
776        private int mRank = -1;
777        private boolean mIsAmbient;
778        private boolean mMatchesInterruptionFilter;
779        private int mVisibilityOverride;
780
781        public Ranking() {}
782
783        /**
784         * Returns the key of the notification this Ranking applies to.
785         */
786        public String getKey() {
787            return mKey;
788        }
789
790        /**
791         * Returns the rank of the notification.
792         *
793         * @return the rank of the notification, that is the 0-based index in
794         *     the list of active notifications.
795         */
796        public int getRank() {
797            return mRank;
798        }
799
800        /**
801         * Returns whether the notification is an ambient notification, that is
802         * a notification that doesn't require the user's immediate attention.
803         */
804        public boolean isAmbient() {
805            return mIsAmbient;
806        }
807
808        /**
809         * Returns the user specificed visibility for the package that posted
810         * this notification, or
811         * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
812         * no such preference has been expressed.
813         * @hide
814         */
815        public int getVisibilityOverride() {
816            return mVisibilityOverride;
817        }
818
819
820        /**
821         * Returns whether the notification matches the user's interruption
822         * filter.
823         *
824         * @return {@code true} if the notification is allowed by the filter, or
825         * {@code false} if it is blocked.
826         */
827        public boolean matchesInterruptionFilter() {
828            return mMatchesInterruptionFilter;
829        }
830
831        private void populate(String key, int rank, boolean isAmbient,
832                boolean matchesInterruptionFilter, int visibilityOverride) {
833            mKey = key;
834            mRank = rank;
835            mIsAmbient = isAmbient;
836            mMatchesInterruptionFilter = matchesInterruptionFilter;
837            mVisibilityOverride = visibilityOverride;
838        }
839    }
840
841    /**
842     * Provides access to ranking information on currently active
843     * notifications.
844     *
845     * <p>
846     * Note that this object represents a ranking snapshot that only applies to
847     * notifications active at the time of retrieval.
848     */
849    public static class RankingMap implements Parcelable {
850        private final NotificationRankingUpdate mRankingUpdate;
851        private ArrayMap<String,Integer> mRanks;
852        private ArraySet<Object> mIntercepted;
853        private ArrayMap<String, Integer> mVisibilityOverrides;
854
855        private RankingMap(NotificationRankingUpdate rankingUpdate) {
856            mRankingUpdate = rankingUpdate;
857        }
858
859        /**
860         * Request the list of notification keys in their current ranking
861         * order.
862         *
863         * @return An array of active notification keys, in their ranking order.
864         */
865        public String[] getOrderedKeys() {
866            return mRankingUpdate.getOrderedKeys();
867        }
868
869        /**
870         * Populates outRanking with ranking information for the notification
871         * with the given key.
872         *
873         * @return true if a valid key has been passed and outRanking has
874         *     been populated; false otherwise
875         */
876        public boolean getRanking(String key, Ranking outRanking) {
877            int rank = getRank(key);
878            outRanking.populate(key, rank, isAmbient(key), !isIntercepted(key),
879                    getVisibilityOverride(key));
880            return rank >= 0;
881        }
882
883        private int getRank(String key) {
884            synchronized (this) {
885                if (mRanks == null) {
886                    buildRanksLocked();
887                }
888            }
889            Integer rank = mRanks.get(key);
890            return rank != null ? rank : -1;
891        }
892
893        private boolean isAmbient(String key) {
894            int firstAmbientIndex = mRankingUpdate.getFirstAmbientIndex();
895            if (firstAmbientIndex < 0) {
896                return false;
897            }
898            int rank = getRank(key);
899            return rank >= 0 && rank >= firstAmbientIndex;
900        }
901
902        private boolean isIntercepted(String key) {
903            synchronized (this) {
904                if (mIntercepted == null) {
905                    buildInterceptedSetLocked();
906                }
907            }
908            return mIntercepted.contains(key);
909        }
910
911        private int getVisibilityOverride(String key) {
912            synchronized (this) {
913                if (mVisibilityOverrides == null) {
914                    buildVisibilityOverridesLocked();
915                }
916            }
917            Integer overide = mVisibilityOverrides.get(key);
918            if (overide == null) {
919                return Ranking.VISIBILITY_NO_OVERRIDE;
920            }
921            return overide.intValue();
922        }
923
924        // Locked by 'this'
925        private void buildRanksLocked() {
926            String[] orderedKeys = mRankingUpdate.getOrderedKeys();
927            mRanks = new ArrayMap<>(orderedKeys.length);
928            for (int i = 0; i < orderedKeys.length; i++) {
929                String key = orderedKeys[i];
930                mRanks.put(key, i);
931            }
932        }
933
934        // Locked by 'this'
935        private void buildInterceptedSetLocked() {
936            String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys();
937            mIntercepted = new ArraySet<>(dndInterceptedKeys.length);
938            Collections.addAll(mIntercepted, dndInterceptedKeys);
939        }
940
941        // Locked by 'this'
942        private void buildVisibilityOverridesLocked() {
943            Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides();
944            mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size());
945            for (String key: visibilityBundle.keySet()) {
946               mVisibilityOverrides.put(key, visibilityBundle.getInt(key));
947            }
948        }
949
950        // ----------- Parcelable
951
952        @Override
953        public int describeContents() {
954            return 0;
955        }
956
957        @Override
958        public void writeToParcel(Parcel dest, int flags) {
959            dest.writeParcelable(mRankingUpdate, flags);
960        }
961
962        public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() {
963            @Override
964            public RankingMap createFromParcel(Parcel source) {
965                NotificationRankingUpdate rankingUpdate = source.readParcelable(null);
966                return new RankingMap(rankingUpdate);
967            }
968
969            @Override
970            public RankingMap[] newArray(int size) {
971                return new RankingMap[size];
972            }
973        };
974    }
975}
976