NotificationListenerService.java revision 0edb50c4bf0a4ae7a40f241c92fa57483ca73552
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.IntDef;
20import android.annotation.SystemApi;
21import android.annotation.SdkConstant;
22import android.app.INotificationManager;
23import android.app.Notification;
24import android.app.Notification.Builder;
25import android.app.NotificationManager;
26import android.app.Service;
27import android.content.ComponentName;
28import android.content.Context;
29import android.content.Intent;
30import android.content.pm.ParceledListSlice;
31import android.graphics.drawable.BitmapDrawable;
32import android.graphics.drawable.Drawable;
33import android.graphics.drawable.Icon;
34import android.graphics.Bitmap;
35import android.os.Build;
36import android.os.Bundle;
37import android.os.IBinder;
38import android.os.Parcel;
39import android.os.Parcelable;
40import android.os.RemoteException;
41import android.os.ServiceManager;
42import android.util.ArrayMap;
43import android.util.ArraySet;
44import android.util.Log;
45
46import java.lang.annotation.Retention;
47import java.lang.annotation.RetentionPolicy;
48import java.util.ArrayList;
49import java.util.Collections;
50import java.util.List;
51
52/**
53 * A service that receives calls from the system when new notifications are
54 * posted or removed, or their ranking changed.
55 * <p>To extend this class, you must declare the service in your manifest file with
56 * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission
57 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
58 * <pre>
59 * &lt;service android:name=".NotificationListener"
60 *          android:label="&#64;string/service_name"
61 *          android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
62 *     &lt;intent-filter>
63 *         &lt;action android:name="android.service.notification.NotificationListenerService" />
64 *     &lt;/intent-filter>
65 * &lt;/service></pre>
66 * <p> Typically, while enabled in user settings, this service will be bound on boot or when a
67 * settings change occurs that could affect whether this service should run.  However, for some
68 * system usage modes, the you may instead specify that this service is instead bound and unbound
69 * in response to mode changes by including a category in the intent filter.  Currently
70 * supported categories are:
71 * <ul>
72 *   <li>{@link #CATEGORY_VR_NOTIFICATIONS} - this service is bound when an Activity has enabled
73 *   VR mode. {@see android.app.Activity#setVrMode(boolean)}.</li>
74 * </ul>
75 * </p>
76 */
77public abstract class NotificationListenerService extends Service {
78    // TAG = "NotificationListenerService[MySubclass]"
79    private final String TAG = NotificationListenerService.class.getSimpleName()
80            + "[" + getClass().getSimpleName() + "]";
81
82    /**
83     * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
84     *     Normal interruption filter.
85     */
86    public static final int INTERRUPTION_FILTER_ALL
87            = NotificationManager.INTERRUPTION_FILTER_ALL;
88
89    /**
90     * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
91     *     Priority interruption filter.
92     */
93    public static final int INTERRUPTION_FILTER_PRIORITY
94            = NotificationManager.INTERRUPTION_FILTER_PRIORITY;
95
96    /**
97     * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
98     *     No interruptions filter.
99     */
100    public static final int INTERRUPTION_FILTER_NONE
101            = NotificationManager.INTERRUPTION_FILTER_NONE;
102
103    /**
104     * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
105     *     Alarms only interruption filter.
106     */
107    public static final int INTERRUPTION_FILTER_ALARMS
108            = NotificationManager.INTERRUPTION_FILTER_ALARMS;
109
110    /** {@link #getCurrentInterruptionFilter() Interruption filter} constant - returned when
111     * the value is unavailable for any reason.  For example, before the notification listener
112     * is connected.
113     *
114     * {@see #onListenerConnected()}
115     */
116    public static final int INTERRUPTION_FILTER_UNKNOWN
117            = NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
118
119    /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
120     * should disable notification sound, vibrating and other visual or aural effects.
121     * This does not change the interruption filter, only the effects. **/
122    public static final int HINT_HOST_DISABLE_EFFECTS = 1;
123
124    /**
125     * Whether notification suppressed by DND should not interruption visually when the screen is
126     * off.
127     */
128    public static final int SUPPRESSED_EFFECT_SCREEN_OFF =
129            NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
130    /**
131     * Whether notification suppressed by DND should not interruption visually when the screen is
132     * on.
133     */
134    public static final int SUPPRESSED_EFFECT_SCREEN_ON =
135            NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
136
137    /**
138     * The full trim of the StatusBarNotification including all its features.
139     *
140     * @hide
141     */
142    @SystemApi
143    public static final int TRIM_FULL = 0;
144
145    /**
146     * A light trim of the StatusBarNotification excluding the following features:
147     *
148     * <ol>
149     *     <li>{@link Notification#tickerView tickerView}</li>
150     *     <li>{@link Notification#contentView contentView}</li>
151     *     <li>{@link Notification#largeIcon largeIcon}</li>
152     *     <li>{@link Notification#bigContentView bigContentView}</li>
153     *     <li>{@link Notification#headsUpContentView headsUpContentView}</li>
154     *     <li>{@link Notification#EXTRA_LARGE_ICON extras[EXTRA_LARGE_ICON]}</li>
155     *     <li>{@link Notification#EXTRA_LARGE_ICON_BIG extras[EXTRA_LARGE_ICON_BIG]}</li>
156     *     <li>{@link Notification#EXTRA_PICTURE extras[EXTRA_PICTURE]}</li>
157     *     <li>{@link Notification#EXTRA_BIG_TEXT extras[EXTRA_BIG_TEXT]}</li>
158     * </ol>
159     *
160     * @hide
161     */
162    @SystemApi
163    public static final int TRIM_LIGHT = 1;
164
165    /** @hide */
166    protected NotificationListenerWrapper mWrapper = null;
167    private RankingMap mRankingMap;
168
169    private INotificationManager mNoMan;
170
171    /** Only valid after a successful call to (@link registerAsService}. */
172    private int mCurrentUser;
173
174
175    // This context is required for system services since NotificationListenerService isn't
176    // started as a real Service and hence no context is available.
177    private Context mSystemContext;
178
179    /**
180     * The {@link Intent} that must be declared as handled by the service.
181     */
182    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
183    public static final String SERVICE_INTERFACE
184            = "android.service.notification.NotificationListenerService";
185
186    /**
187     * If this category is declared in the application manifest for a service of this type, this
188     * service will be bound when VR mode is enabled, and unbound when VR mode is disabled rather
189     * than the normal lifecycle for a notification service.
190     *
191     * {@see android.app.Activity#setVrMode(boolean)}
192     */
193    @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY)
194    public static final String CATEGORY_VR_NOTIFICATIONS =
195        "android.intent.category.vr.notifications";
196
197    /**
198     * Implement this method to learn about new notifications as they are posted by apps.
199     *
200     * @param sbn A data structure encapsulating the original {@link android.app.Notification}
201     *            object as well as its identifying information (tag and id) and source
202     *            (package name).
203     */
204    public void onNotificationPosted(StatusBarNotification sbn) {
205        // optional
206    }
207
208    /**
209     * Implement this method to learn about new notifications as they are posted by apps.
210     *
211     * @param sbn A data structure encapsulating the original {@link android.app.Notification}
212     *            object as well as its identifying information (tag and id) and source
213     *            (package name).
214     * @param rankingMap The current ranking map that can be used to retrieve ranking information
215     *                   for active notifications, including the newly posted one.
216     */
217    public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
218        onNotificationPosted(sbn);
219    }
220
221    /**
222     * Implement this method to learn when notifications are removed.
223     * <P>
224     * This might occur because the user has dismissed the notification using system UI (or another
225     * notification listener) or because the app has withdrawn the notification.
226     * <P>
227     * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
228     * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
229     * fields such as {@link android.app.Notification#contentView} and
230     * {@link android.app.Notification#largeIcon}. However, all other fields on
231     * {@link StatusBarNotification}, sufficient to match this call with a prior call to
232     * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
233     *
234     * @param sbn A data structure encapsulating at least the original information (tag and id)
235     *            and source (package name) used to post the {@link android.app.Notification} that
236     *            was just removed.
237     */
238    public void onNotificationRemoved(StatusBarNotification sbn) {
239        // optional
240    }
241
242    /**
243     * Implement this method to learn when notifications are removed.
244     * <P>
245     * This might occur because the user has dismissed the notification using system UI (or another
246     * notification listener) or because the app has withdrawn the notification.
247     * <P>
248     * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
249     * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
250     * fields such as {@link android.app.Notification#contentView} and
251     * {@link android.app.Notification#largeIcon}. However, all other fields on
252     * {@link StatusBarNotification}, sufficient to match this call with a prior call to
253     * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
254     *
255     * @param sbn A data structure encapsulating at least the original information (tag and id)
256     *            and source (package name) used to post the {@link android.app.Notification} that
257     *            was just removed.
258     * @param rankingMap The current ranking map that can be used to retrieve ranking information
259     *                   for active notifications.
260     *
261     */
262    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
263        onNotificationRemoved(sbn);
264    }
265
266    /**
267     * Implement this method to learn about when the listener is enabled and connected to
268     * the notification manager.  You are safe to call {@link #getActiveNotifications()}
269     * at this time.
270     */
271    public void onListenerConnected() {
272        // optional
273    }
274
275    /**
276     * Implement this method to be notified when the notification ranking changes.
277     *
278     * @param rankingMap The current ranking map that can be used to retrieve ranking information
279     *                   for active notifications.
280     */
281    public void onNotificationRankingUpdate(RankingMap rankingMap) {
282        // optional
283    }
284
285    /**
286     * Implement this method to be notified when the
287     * {@link #getCurrentListenerHints() Listener hints} change.
288     *
289     * @param hints The current {@link #getCurrentListenerHints() listener hints}.
290     */
291    public void onListenerHintsChanged(int hints) {
292        // optional
293    }
294
295    /**
296     * Implement this method to be notified when the
297     * {@link #getCurrentInterruptionFilter() interruption filter} changed.
298     *
299     * @param interruptionFilter The current
300     *     {@link #getCurrentInterruptionFilter() interruption filter}.
301     */
302    public void onInterruptionFilterChanged(int interruptionFilter) {
303        // optional
304    }
305
306    /** @hide */
307    protected final INotificationManager getNotificationInterface() {
308        if (mNoMan == null) {
309            mNoMan = INotificationManager.Stub.asInterface(
310                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
311        }
312        return mNoMan;
313    }
314
315    /**
316     * Inform the notification manager about dismissal of a single notification.
317     * <p>
318     * Use this if your listener has a user interface that allows the user to dismiss individual
319     * notifications, similar to the behavior of Android's status bar and notification panel.
320     * It should be called after the user dismisses a single notification using your UI;
321     * upon being informed, the notification manager will actually remove the notification
322     * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
323     * <P>
324     * <b>Note:</b> If your listener allows the user to fire a notification's
325     * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
326     * this method at that time <i>if</i> the Notification in question has the
327     * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
328     *
329     * @param pkg Package of the notifying app.
330     * @param tag Tag of the notification as specified by the notifying app in
331     *     {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
332     * @param id  ID of the notification as specified by the notifying app in
333     *     {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
334     * <p>
335     * @deprecated Use {@link #cancelNotification(String key)}
336     * instead. Beginning with {@link android.os.Build.VERSION_CODES#LOLLIPOP} this method will no longer
337     * cancel the notification. It will continue to cancel the notification for applications
338     * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
339     */
340    public final void cancelNotification(String pkg, String tag, int id) {
341        if (!isBound()) return;
342        try {
343            getNotificationInterface().cancelNotificationFromListener(
344                    mWrapper, pkg, tag, id);
345        } catch (android.os.RemoteException ex) {
346            Log.v(TAG, "Unable to contact notification manager", ex);
347        }
348    }
349
350    /**
351     * Inform the notification manager about dismissal of a single notification.
352     * <p>
353     * Use this if your listener has a user interface that allows the user to dismiss individual
354     * notifications, similar to the behavior of Android's status bar and notification panel.
355     * It should be called after the user dismisses a single notification using your UI;
356     * upon being informed, the notification manager will actually remove the notification
357     * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
358     * <P>
359     * <b>Note:</b> If your listener allows the user to fire a notification's
360     * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
361     * this method at that time <i>if</i> the Notification in question has the
362     * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
363     * <p>
364     * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}.
365     */
366    public final void cancelNotification(String key) {
367        if (!isBound()) return;
368        try {
369            getNotificationInterface().cancelNotificationsFromListener(mWrapper,
370                    new String[] { key });
371        } catch (android.os.RemoteException ex) {
372            Log.v(TAG, "Unable to contact notification manager", ex);
373        }
374    }
375
376    /**
377     * Inform the notification manager about dismissal of all notifications.
378     * <p>
379     * Use this if your listener has a user interface that allows the user to dismiss all
380     * notifications, similar to the behavior of Android's status bar and notification panel.
381     * It should be called after the user invokes the "dismiss all" function of your UI;
382     * upon being informed, the notification manager will actually remove all active notifications
383     * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks.
384     *
385     * {@see #cancelNotification(String, String, int)}
386     */
387    public final void cancelAllNotifications() {
388        cancelNotifications(null /*all*/);
389    }
390
391    /**
392     * Inform the notification manager about dismissal of specific notifications.
393     * <p>
394     * Use this if your listener has a user interface that allows the user to dismiss
395     * multiple notifications at once.
396     *
397     * @param keys Notifications to dismiss, or {@code null} to dismiss all.
398     *
399     * {@see #cancelNotification(String, String, int)}
400     */
401    public final void cancelNotifications(String[] keys) {
402        if (!isBound()) return;
403        try {
404            getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys);
405        } catch (android.os.RemoteException ex) {
406            Log.v(TAG, "Unable to contact notification manager", ex);
407        }
408    }
409
410    /**
411     * Inform the notification manager that these notifications have been viewed by the
412     * user. This should only be called when there is sufficient confidence that the user is
413     * looking at the notifications, such as when the notifications appear on the screen due to
414     * an explicit user interaction.
415     * @param keys Notifications to mark as seen.
416     */
417    public final void setNotificationsShown(String[] keys) {
418        if (!isBound()) return;
419        try {
420            getNotificationInterface().setNotificationsShownFromListener(mWrapper, keys);
421        } catch (android.os.RemoteException ex) {
422            Log.v(TAG, "Unable to contact notification manager", ex);
423        }
424    }
425
426    /**
427     * Sets the notification trim that will be received via {@link #onNotificationPosted}.
428     *
429     * <p>
430     * Setting a trim other than {@link #TRIM_FULL} enables listeners that don't need access to the
431     * full notification features right away to reduce their memory footprint. Full notifications
432     * can be requested on-demand via {@link #getActiveNotifications(int)}.
433     *
434     * <p>
435     * Set to {@link #TRIM_FULL} initially.
436     *
437     * @hide
438     *
439     * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}.
440     *             See <code>TRIM_*</code> constants.
441     */
442    @SystemApi
443    public final void setOnNotificationPostedTrim(int trim) {
444        if (!isBound()) return;
445        try {
446            getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim);
447        } catch (RemoteException ex) {
448            Log.v(TAG, "Unable to contact notification manager", ex);
449        }
450    }
451
452    /**
453     * Request the list of outstanding notifications (that is, those that are visible to the
454     * current user). Useful when you don't know what's already been posted.
455     *
456     * @return An array of active notifications, sorted in natural order.
457     */
458    public StatusBarNotification[] getActiveNotifications() {
459        return getActiveNotifications(null, TRIM_FULL);
460    }
461
462    /**
463     * Request the list of outstanding notifications (that is, those that are visible to the
464     * current user). Useful when you don't know what's already been posted.
465     *
466     * @hide
467     *
468     * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
469     * @return An array of active notifications, sorted in natural order.
470     */
471    @SystemApi
472    public StatusBarNotification[] getActiveNotifications(int trim) {
473        return getActiveNotifications(null, trim);
474    }
475
476    /**
477     * Request one or more notifications by key. Useful if you have been keeping track of
478     * notifications but didn't want to retain the bits, and now need to go back and extract
479     * more data out of those notifications.
480     *
481     * @param keys the keys of the notifications to request
482     * @return An array of notifications corresponding to the requested keys, in the
483     * same order as the key list.
484     */
485    public StatusBarNotification[] getActiveNotifications(String[] keys) {
486        return getActiveNotifications(keys, TRIM_FULL);
487    }
488
489    /**
490     * Request one or more notifications by key. Useful if you have been keeping track of
491     * notifications but didn't want to retain the bits, and now need to go back and extract
492     * more data out of those notifications.
493     *
494     * @hide
495     *
496     * @param keys the keys of the notifications to request
497     * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
498     * @return An array of notifications corresponding to the requested keys, in the
499     * same order as the key list.
500     */
501    @SystemApi
502    public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) {
503        if (!isBound())
504            return null;
505        try {
506            ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
507                    .getActiveNotificationsFromListener(mWrapper, keys, trim);
508            List<StatusBarNotification> list = parceledList.getList();
509            ArrayList<StatusBarNotification> corruptNotifications = null;
510            int N = list.size();
511            for (int i = 0; i < N; i++) {
512                StatusBarNotification sbn = list.get(i);
513                Notification notification = sbn.getNotification();
514                try {
515                    // convert icon metadata to legacy format for older clients
516                    createLegacyIconExtras(notification);
517                    // populate remote views for older clients.
518                    maybePopulateRemoteViews(notification);
519                } catch (IllegalArgumentException e) {
520                    if (corruptNotifications == null) {
521                        corruptNotifications = new ArrayList<>(N);
522                    }
523                    corruptNotifications.add(sbn);
524                    Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
525                            sbn.getPackageName());
526                }
527            }
528            if (corruptNotifications != null) {
529                list.removeAll(corruptNotifications);
530            }
531            return list.toArray(new StatusBarNotification[list.size()]);
532        } catch (android.os.RemoteException ex) {
533            Log.v(TAG, "Unable to contact notification manager", ex);
534        }
535        return null;
536    }
537
538    /**
539     * Gets the set of hints representing current state.
540     *
541     * <p>
542     * The current state may differ from the requested state if the hint represents state
543     * shared across all listeners or a feature the notification host does not support or refuses
544     * to grant.
545     *
546     * @return Zero or more of the HINT_ constants.
547     */
548    public final int getCurrentListenerHints() {
549        if (!isBound()) return 0;
550        try {
551            return getNotificationInterface().getHintsFromListener(mWrapper);
552        } catch (android.os.RemoteException ex) {
553            Log.v(TAG, "Unable to contact notification manager", ex);
554            return 0;
555        }
556    }
557
558    /**
559     * Gets the current notification interruption filter active on the host.
560     *
561     * <p>
562     * The interruption filter defines which notifications are allowed to interrupt the user
563     * (e.g. via sound &amp; vibration) and is applied globally. Listeners can find out whether
564     * a specific notification matched the interruption filter via
565     * {@link Ranking#matchesInterruptionFilter()}.
566     * <p>
567     * The current filter may differ from the previously requested filter if the notification host
568     * does not support or refuses to apply the requested filter, or if another component changed
569     * the filter in the meantime.
570     * <p>
571     * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
572     *
573     * @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when
574     * unavailable.
575     */
576    public final int getCurrentInterruptionFilter() {
577        if (!isBound()) return INTERRUPTION_FILTER_UNKNOWN;
578        try {
579            return getNotificationInterface().getInterruptionFilterFromListener(mWrapper);
580        } catch (android.os.RemoteException ex) {
581            Log.v(TAG, "Unable to contact notification manager", ex);
582            return INTERRUPTION_FILTER_UNKNOWN;
583        }
584    }
585
586    /**
587     * Sets the desired {@link #getCurrentListenerHints() listener hints}.
588     *
589     * <p>
590     * This is merely a request, the host may or may not choose to take action depending
591     * on other listener requests or other global state.
592     * <p>
593     * Listen for updates using {@link #onListenerHintsChanged(int)}.
594     *
595     * @param hints One or more of the HINT_ constants.
596     */
597    public final void requestListenerHints(int hints) {
598        if (!isBound()) return;
599        try {
600            getNotificationInterface().requestHintsFromListener(mWrapper, hints);
601        } catch (android.os.RemoteException ex) {
602            Log.v(TAG, "Unable to contact notification manager", ex);
603        }
604    }
605
606    /**
607     * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}.
608     *
609     * <p>
610     * This is merely a request, the host may or may not choose to apply the requested
611     * interruption filter depending on other listener requests or other global state.
612     * <p>
613     * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
614     *
615     * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants.
616     */
617    public final void requestInterruptionFilter(int interruptionFilter) {
618        if (!isBound()) return;
619        try {
620            getNotificationInterface()
621                    .requestInterruptionFilterFromListener(mWrapper, interruptionFilter);
622        } catch (android.os.RemoteException ex) {
623            Log.v(TAG, "Unable to contact notification manager", ex);
624        }
625    }
626
627    /**
628     * Returns current ranking information.
629     *
630     * <p>
631     * The returned object represents the current ranking snapshot and only
632     * applies for currently active notifications.
633     * <p>
634     * Generally you should use the RankingMap that is passed with events such
635     * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)},
636     * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and
637     * so on. This method should only be used when needing access outside of
638     * such events, for example to retrieve the RankingMap right after
639     * initialization.
640     *
641     * @return A {@link RankingMap} object providing access to ranking information
642     */
643    public RankingMap getCurrentRanking() {
644        return mRankingMap;
645    }
646
647    @Override
648    public IBinder onBind(Intent intent) {
649        if (mWrapper == null) {
650            mWrapper = new NotificationListenerWrapper();
651        }
652        return mWrapper;
653    }
654
655    /** @hide */
656    protected boolean isBound() {
657        if (mWrapper == null) {
658            Log.w(TAG, "Notification listener service not yet bound.");
659            return false;
660        }
661        return true;
662    }
663
664    /**
665     * Directly register this service with the Notification Manager.
666     *
667     * <p>Only system services may use this call. It will fail for non-system callers.
668     * Apps should ask the user to add their listener in Settings.
669     *
670     * @param context Context required for accessing resources. Since this service isn't
671     *    launched as a real Service when using this method, a context has to be passed in.
672     * @param componentName the component that will consume the notification information
673     * @param currentUser the user to use as the stream filter
674     * @hide
675     */
676    @SystemApi
677    public void registerAsSystemService(Context context, ComponentName componentName,
678            int currentUser) throws RemoteException {
679        mSystemContext = context;
680        if (mWrapper == null) {
681            mWrapper = new NotificationListenerWrapper();
682        }
683        INotificationManager noMan = getNotificationInterface();
684        noMan.registerListener(mWrapper, componentName, currentUser);
685        mCurrentUser = currentUser;
686    }
687
688    /**
689     * Directly unregister this service from the Notification Manager.
690     *
691     * <P>This method will fail for listeners that were not registered
692     * with (@link registerAsService).
693     * @hide
694     */
695    @SystemApi
696    public void unregisterAsSystemService() throws RemoteException {
697        if (mWrapper != null) {
698            INotificationManager noMan = getNotificationInterface();
699            noMan.unregisterListener(mWrapper, mCurrentUser);
700        }
701    }
702
703    /**
704     * Request that the listener be rebound, after a previous call to (@link requestUnbind).
705     *
706     * <P>This method will fail for listeners that have
707     * not been granted the permission by the user.
708     *
709     * <P>The service should wait for the {@link #onListenerConnected()} event
710     * before performing any operations.
711     */
712    public static final void requestRebind(ComponentName componentName)
713            throws RemoteException {
714        INotificationManager noMan = INotificationManager.Stub.asInterface(
715                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
716        noMan.requestBindListener(componentName);
717    }
718
719    /**
720     * Request that the service be unbound.
721     *
722     * <P>This will no longer receive updates until
723     * {@link #requestRebind(ComponentName)} is called.
724     * The service will likely be kiled by the system after this call.
725     */
726    public final void requestUnbind() throws RemoteException {
727        if (mWrapper != null) {
728            INotificationManager noMan = getNotificationInterface();
729            noMan.requestUnbindListener(mWrapper);
730        }
731    }
732
733    /** Convert new-style Icons to legacy representations for pre-M clients. */
734    private void createLegacyIconExtras(Notification n) {
735        Icon smallIcon = n.getSmallIcon();
736        Icon largeIcon = n.getLargeIcon();
737        if (smallIcon != null && smallIcon.getType() == Icon.TYPE_RESOURCE) {
738            n.extras.putInt(Notification.EXTRA_SMALL_ICON, smallIcon.getResId());
739            n.icon = smallIcon.getResId();
740        }
741        if (largeIcon != null) {
742            Drawable d = largeIcon.loadDrawable(getContext());
743            if (d != null && d instanceof BitmapDrawable) {
744                final Bitmap largeIconBits = ((BitmapDrawable) d).getBitmap();
745                n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, largeIconBits);
746                n.largeIcon = largeIconBits;
747            }
748        }
749    }
750
751    /**
752     * Populates remote views for pre-N targeting apps.
753     */
754    private void maybePopulateRemoteViews(Notification notification) {
755        if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
756            Builder builder = Builder.recoverBuilder(getContext(), notification);
757            notification.contentView = builder.createContentView();
758            notification.bigContentView = builder.createBigContentView();
759            notification.headsUpContentView = builder.createHeadsUpContentView();
760        }
761    }
762
763    /** @hide */
764    protected class NotificationListenerWrapper extends INotificationListener.Stub {
765        @Override
766        public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
767                NotificationRankingUpdate update) {
768            StatusBarNotification sbn;
769            try {
770                sbn = sbnHolder.get();
771            } catch (RemoteException e) {
772                Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
773                return;
774            }
775
776            try {
777                Notification notification = sbn.getNotification();
778                // convert icon metadata to legacy format for older clients
779                createLegacyIconExtras(sbn.getNotification());
780                maybePopulateRemoteViews(sbn.getNotification());
781            } catch (IllegalArgumentException e) {
782                // warn and drop corrupt notification
783                Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
784                        sbn.getPackageName());
785                sbn = null;
786            }
787
788            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
789            synchronized (mWrapper) {
790                applyUpdate(update);
791                try {
792                    if (sbn != null) {
793                        NotificationListenerService.this.onNotificationPosted(sbn, mRankingMap);
794                    } else {
795                        // still pass along the ranking map, it may contain other information
796                        NotificationListenerService.this.onNotificationRankingUpdate(mRankingMap);
797                    }
798                } catch (Throwable t) {
799                    Log.w(TAG, "Error running onNotificationPosted", t);
800                }
801            }
802        }
803        @Override
804        public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
805                NotificationRankingUpdate update) {
806            StatusBarNotification sbn;
807            try {
808                sbn = sbnHolder.get();
809            } catch (RemoteException e) {
810                Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e);
811                return;
812            }
813            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
814            synchronized (mWrapper) {
815                applyUpdate(update);
816                try {
817                    NotificationListenerService.this.onNotificationRemoved(sbn, mRankingMap);
818                } catch (Throwable t) {
819                    Log.w(TAG, "Error running onNotificationRemoved", t);
820                }
821            }
822        }
823        @Override
824        public void onListenerConnected(NotificationRankingUpdate update) {
825            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
826            synchronized (mWrapper) {
827                applyUpdate(update);
828                try {
829                    NotificationListenerService.this.onListenerConnected();
830                } catch (Throwable t) {
831                    Log.w(TAG, "Error running onListenerConnected", t);
832                }
833            }
834        }
835        @Override
836        public void onNotificationRankingUpdate(NotificationRankingUpdate update)
837                throws RemoteException {
838            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
839            synchronized (mWrapper) {
840                applyUpdate(update);
841                try {
842                    NotificationListenerService.this.onNotificationRankingUpdate(mRankingMap);
843                } catch (Throwable t) {
844                    Log.w(TAG, "Error running onNotificationRankingUpdate", t);
845                }
846            }
847        }
848        @Override
849        public void onListenerHintsChanged(int hints) throws RemoteException {
850            try {
851                NotificationListenerService.this.onListenerHintsChanged(hints);
852            } catch (Throwable t) {
853                Log.w(TAG, "Error running onListenerHintsChanged", t);
854            }
855        }
856
857        @Override
858        public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException {
859            try {
860                NotificationListenerService.this.onInterruptionFilterChanged(interruptionFilter);
861            } catch (Throwable t) {
862                Log.w(TAG, "Error running onInterruptionFilterChanged", t);
863            }
864        }
865
866        @Override
867        public void onNotificationEnqueued(IStatusBarNotificationHolder notificationHolder,
868                                           int importance, boolean user) throws RemoteException {
869            // no-op in the listener
870        }
871
872        @Override
873        public void onNotificationVisibilityChanged(String key, long time, boolean visible)
874                throws RemoteException {
875            // no-op in the listener
876        }
877
878        @Override
879        public void onNotificationClick(String key, long time) throws RemoteException {
880            // no-op in the listener
881        }
882
883        @Override
884        public void onNotificationActionClick(String key, long time, int actionIndex)
885                throws RemoteException {
886            // no-op in the listener
887        }
888
889        @Override
890        public void onNotificationRemovedReason(String key, long time, int reason)
891                throws RemoteException {
892            // no-op in the listener
893        }
894    }
895
896    private void applyUpdate(NotificationRankingUpdate update) {
897        mRankingMap = new RankingMap(update);
898    }
899
900    private Context getContext() {
901        if (mSystemContext != null) {
902            return mSystemContext;
903        }
904        return this;
905    }
906
907    /**
908     * Stores ranking related information on a currently active notification.
909     *
910     * <p>
911     * Ranking objects aren't automatically updated as notification events
912     * occur. Instead, ranking information has to be retrieved again via the
913     * current {@link RankingMap}.
914     */
915    public static class Ranking {
916
917        /** @hide */
918        @IntDef({VISIBILITY_NO_OVERRIDE, IMPORTANCE_UNSPECIFIED, IMPORTANCE_NONE,
919                IMPORTANCE_MIN, IMPORTANCE_LOW, IMPORTANCE_DEFAULT, IMPORTANCE_HIGH,
920                IMPORTANCE_MAX})
921        @Retention(RetentionPolicy.SOURCE)
922        public @interface Importance {}
923
924        /** Value signifying that the user has not expressed a per-app visibility override value.
925         * @hide */
926        public static final int VISIBILITY_NO_OVERRIDE = -1000;
927
928        /**
929         * Value signifying that the user has not expressed an importance.
930         *
931         * This value is for persisting preferences, and should never be associated with
932         * an actual notification.
933         */
934        public static final int IMPORTANCE_UNSPECIFIED = -1000;
935
936        /**
937         * A notification with no importance: shows nowhere, is blocked.
938         */
939        public static final int IMPORTANCE_NONE = 0;
940
941        /**
942         * Min notification importance: only shows in the shade, below the fold.
943         */
944        public static final int IMPORTANCE_MIN = 1;
945
946        /**
947         * Low notification importance: shows everywhere, but is not intrusive.
948         */
949        public static final int IMPORTANCE_LOW = 2;
950
951        /**
952         * Default notification importance: shows everywhere, allowed to makes noise,
953         * but does not visually intrude.
954         */
955        public static final int IMPORTANCE_DEFAULT = 3;
956
957        /**
958         * Higher notification importance: shows everywhere, allowed to makes noise and peek.
959         */
960        public static final int IMPORTANCE_HIGH = 4;
961
962        /**
963         * Highest notification importance: shows everywhere, allowed to makes noise, peek, and
964         * use full screen intents.
965         */
966        public static final int IMPORTANCE_MAX = 5;
967
968        private String mKey;
969        private int mRank = -1;
970        private boolean mIsAmbient;
971        private boolean mMatchesInterruptionFilter;
972        private int mVisibilityOverride;
973        private int mSuppressedVisualEffects;
974        private @Importance int mImportance;
975        private CharSequence mImportanceExplanation;
976
977        public Ranking() {}
978
979        /**
980         * Returns the key of the notification this Ranking applies to.
981         */
982        public String getKey() {
983            return mKey;
984        }
985
986        /**
987         * Returns the rank of the notification.
988         *
989         * @return the rank of the notification, that is the 0-based index in
990         *     the list of active notifications.
991         */
992        public int getRank() {
993            return mRank;
994        }
995
996        /**
997         * Returns whether the notification is an ambient notification, that is
998         * a notification that doesn't require the user's immediate attention.
999         */
1000        public boolean isAmbient() {
1001            return mIsAmbient;
1002        }
1003
1004        /**
1005         * Returns the user specificed visibility for the package that posted
1006         * this notification, or
1007         * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
1008         * no such preference has been expressed.
1009         * @hide
1010         */
1011        public int getVisibilityOverride() {
1012            return mVisibilityOverride;
1013        }
1014
1015        /**
1016         * Returns the type(s) of visual effects that should be suppressed for this notification.
1017         * See {@link #SUPPRESSED_EFFECT_SCREEN_OFF}, {@link #SUPPRESSED_EFFECT_SCREEN_ON}.
1018         */
1019        public int getSuppressedVisualEffects() {
1020            return mSuppressedVisualEffects;
1021        }
1022
1023        /**
1024         * Returns whether the notification matches the user's interruption
1025         * filter.
1026         *
1027         * @return {@code true} if the notification is allowed by the filter, or
1028         * {@code false} if it is blocked.
1029         */
1030        public boolean matchesInterruptionFilter() {
1031            return mMatchesInterruptionFilter;
1032        }
1033
1034        /**
1035         * Returns the importance of the notification, which dictates its
1036         * modes of presentation, see: {@link #IMPORTANCE_DEFAULT}, etc.
1037         *
1038         * @return the rank of the notification
1039         */
1040        public @Importance int getImportance() {
1041            return mImportance;
1042        }
1043
1044        /**
1045         * If the importance has been overriden by user preference, then this will be non-null,
1046         * and should be displayed to the user.
1047         *
1048         * @return the explanation for the importance, or null if it is the natural importance
1049         */
1050        public CharSequence getImportanceExplanation() {
1051            return mImportanceExplanation;
1052        }
1053
1054        private void populate(String key, int rank, boolean matchesInterruptionFilter,
1055                int visibilityOverride, int suppressedVisualEffects, int importance,
1056                CharSequence explanation) {
1057            mKey = key;
1058            mRank = rank;
1059            mIsAmbient = importance < IMPORTANCE_LOW;
1060            mMatchesInterruptionFilter = matchesInterruptionFilter;
1061            mVisibilityOverride = visibilityOverride;
1062            mSuppressedVisualEffects = suppressedVisualEffects;
1063            mImportance = importance;
1064            mImportanceExplanation = explanation;
1065        }
1066
1067        /**
1068         * {@hide}
1069         */
1070        public static String importanceToString(int importance) {
1071            switch (importance) {
1072                case IMPORTANCE_UNSPECIFIED:
1073                    return "UNSPECIFIED";
1074                case IMPORTANCE_NONE:
1075                    return "NONE";
1076                case IMPORTANCE_MIN:
1077                    return "MIN";
1078                case IMPORTANCE_LOW:
1079                    return "LOW";
1080                case IMPORTANCE_DEFAULT:
1081                    return "DEFAULT";
1082                case IMPORTANCE_HIGH:
1083                    return "HIGH";
1084                case IMPORTANCE_MAX:
1085                    return "MAX";
1086                default:
1087                    return "UNKNOWN(" + String.valueOf(importance) + ")";
1088            }
1089        }
1090    }
1091
1092    /**
1093     * Provides access to ranking information on currently active
1094     * notifications.
1095     *
1096     * <p>
1097     * Note that this object represents a ranking snapshot that only applies to
1098     * notifications active at the time of retrieval.
1099     */
1100    public static class RankingMap implements Parcelable {
1101        private final NotificationRankingUpdate mRankingUpdate;
1102        private ArrayMap<String,Integer> mRanks;
1103        private ArraySet<Object> mIntercepted;
1104        private ArrayMap<String, Integer> mVisibilityOverrides;
1105        private ArrayMap<String, Integer> mSuppressedVisualEffects;
1106        private ArrayMap<String, Integer> mImportance;
1107        private ArrayMap<String, String> mImportanceExplanation;
1108
1109        private RankingMap(NotificationRankingUpdate rankingUpdate) {
1110            mRankingUpdate = rankingUpdate;
1111        }
1112
1113        /**
1114         * Request the list of notification keys in their current ranking
1115         * order.
1116         *
1117         * @return An array of active notification keys, in their ranking order.
1118         */
1119        public String[] getOrderedKeys() {
1120            return mRankingUpdate.getOrderedKeys();
1121        }
1122
1123        /**
1124         * Populates outRanking with ranking information for the notification
1125         * with the given key.
1126         *
1127         * @return true if a valid key has been passed and outRanking has
1128         *     been populated; false otherwise
1129         */
1130        public boolean getRanking(String key, Ranking outRanking) {
1131            int rank = getRank(key);
1132            outRanking.populate(key, rank, !isIntercepted(key),
1133                    getVisibilityOverride(key), getSuppressedVisualEffects(key),
1134                    getImportance(key), getImportanceExplanation(key));
1135            return rank >= 0;
1136        }
1137
1138        private int getRank(String key) {
1139            synchronized (this) {
1140                if (mRanks == null) {
1141                    buildRanksLocked();
1142                }
1143            }
1144            Integer rank = mRanks.get(key);
1145            return rank != null ? rank : -1;
1146        }
1147
1148        private boolean isIntercepted(String key) {
1149            synchronized (this) {
1150                if (mIntercepted == null) {
1151                    buildInterceptedSetLocked();
1152                }
1153            }
1154            return mIntercepted.contains(key);
1155        }
1156
1157        private int getVisibilityOverride(String key) {
1158            synchronized (this) {
1159                if (mVisibilityOverrides == null) {
1160                    buildVisibilityOverridesLocked();
1161                }
1162            }
1163            Integer override = mVisibilityOverrides.get(key);
1164            if (override == null) {
1165                return Ranking.VISIBILITY_NO_OVERRIDE;
1166            }
1167            return override.intValue();
1168        }
1169
1170        private int getSuppressedVisualEffects(String key) {
1171            synchronized (this) {
1172                if (mSuppressedVisualEffects == null) {
1173                    buildSuppressedVisualEffectsLocked();
1174                }
1175            }
1176            Integer suppressed = mSuppressedVisualEffects.get(key);
1177            if (suppressed == null) {
1178                return 0;
1179            }
1180            return suppressed.intValue();
1181        }
1182
1183        private int getImportance(String key) {
1184            synchronized (this) {
1185                if (mImportance == null) {
1186                    buildImportanceLocked();
1187                }
1188            }
1189            Integer importance = mImportance.get(key);
1190            if (importance == null) {
1191                return Ranking.IMPORTANCE_DEFAULT;
1192            }
1193            return importance.intValue();
1194        }
1195
1196        private String getImportanceExplanation(String key) {
1197            synchronized (this) {
1198                if (mImportanceExplanation == null) {
1199                    buildImportanceExplanationLocked();
1200                }
1201            }
1202            return mImportanceExplanation.get(key);
1203        }
1204
1205        // Locked by 'this'
1206        private void buildRanksLocked() {
1207            String[] orderedKeys = mRankingUpdate.getOrderedKeys();
1208            mRanks = new ArrayMap<>(orderedKeys.length);
1209            for (int i = 0; i < orderedKeys.length; i++) {
1210                String key = orderedKeys[i];
1211                mRanks.put(key, i);
1212            }
1213        }
1214
1215        // Locked by 'this'
1216        private void buildInterceptedSetLocked() {
1217            String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys();
1218            mIntercepted = new ArraySet<>(dndInterceptedKeys.length);
1219            Collections.addAll(mIntercepted, dndInterceptedKeys);
1220        }
1221
1222        // Locked by 'this'
1223        private void buildVisibilityOverridesLocked() {
1224            Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides();
1225            mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size());
1226            for (String key: visibilityBundle.keySet()) {
1227               mVisibilityOverrides.put(key, visibilityBundle.getInt(key));
1228            }
1229        }
1230
1231        // Locked by 'this'
1232        private void buildSuppressedVisualEffectsLocked() {
1233            Bundle suppressedBundle = mRankingUpdate.getSuppressedVisualEffects();
1234            mSuppressedVisualEffects = new ArrayMap<>(suppressedBundle.size());
1235            for (String key: suppressedBundle.keySet()) {
1236                mSuppressedVisualEffects.put(key, suppressedBundle.getInt(key));
1237            }
1238        }
1239        // Locked by 'this'
1240        private void buildImportanceLocked() {
1241            String[] orderedKeys = mRankingUpdate.getOrderedKeys();
1242            int[] importance = mRankingUpdate.getImportance();
1243            mImportance = new ArrayMap<>(orderedKeys.length);
1244            for (int i = 0; i < orderedKeys.length; i++) {
1245                String key = orderedKeys[i];
1246                mImportance.put(key, importance[i]);
1247            }
1248        }
1249
1250        // Locked by 'this'
1251        private void buildImportanceExplanationLocked() {
1252            Bundle explanationBundle = mRankingUpdate.getImportanceExplanation();
1253            mImportanceExplanation = new ArrayMap<>(explanationBundle.size());
1254            for (String key: explanationBundle.keySet()) {
1255                mImportanceExplanation.put(key, explanationBundle.getString(key));
1256            }
1257        }
1258
1259        // ----------- Parcelable
1260
1261        @Override
1262        public int describeContents() {
1263            return 0;
1264        }
1265
1266        @Override
1267        public void writeToParcel(Parcel dest, int flags) {
1268            dest.writeParcelable(mRankingUpdate, flags);
1269        }
1270
1271        public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() {
1272            @Override
1273            public RankingMap createFromParcel(Parcel source) {
1274                NotificationRankingUpdate rankingUpdate = source.readParcelable(null);
1275                return new RankingMap(rankingUpdate);
1276            }
1277
1278            @Override
1279            public RankingMap[] newArray(int size) {
1280                return new RankingMap[size];
1281            }
1282        };
1283    }
1284}
1285