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