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