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.IntDef;
     20 import android.annotation.NonNull;
     21 import android.annotation.SdkConstant;
     22 import android.annotation.SystemApi;
     23 import android.app.INotificationManager;
     24 import android.app.Notification;
     25 import android.app.Notification.Builder;
     26 import android.app.NotificationChannel;
     27 import android.app.NotificationChannelGroup;
     28 import android.app.NotificationManager;
     29 import android.app.Service;
     30 import android.companion.CompanionDeviceManager;
     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.Bitmap;
     36 import android.graphics.drawable.BitmapDrawable;
     37 import android.graphics.drawable.Drawable;
     38 import android.graphics.drawable.Icon;
     39 import android.os.Build;
     40 import android.os.Bundle;
     41 import android.os.Handler;
     42 import android.os.IBinder;
     43 import android.os.Looper;
     44 import android.os.Message;
     45 import android.os.Parcel;
     46 import android.os.Parcelable;
     47 import android.os.RemoteException;
     48 import android.os.ServiceManager;
     49 import android.os.UserHandle;
     50 import android.util.ArrayMap;
     51 import android.util.ArraySet;
     52 import android.util.Log;
     53 import android.widget.RemoteViews;
     54 
     55 import com.android.internal.annotations.GuardedBy;
     56 import com.android.internal.os.SomeArgs;
     57 
     58 import java.lang.annotation.Retention;
     59 import java.lang.annotation.RetentionPolicy;
     60 import java.util.ArrayList;
     61 import java.util.Collections;
     62 import java.util.List;
     63 
     64 /**
     65  * A service that receives calls from the system when new notifications are
     66  * posted or removed, or their ranking changed.
     67  * <p>To extend this class, you must declare the service in your manifest file with
     68  * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission
     69  * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
     70  * <pre>
     71  * &lt;service android:name=".NotificationListener"
     72  *          android:label="&#64;string/service_name"
     73  *          android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
     74  *     &lt;intent-filter>
     75  *         &lt;action android:name="android.service.notification.NotificationListenerService" />
     76  *     &lt;/intent-filter>
     77  * &lt;/service></pre>
     78  *
     79  * <p>The service should wait for the {@link #onListenerConnected()} event
     80  * before performing any operations. The {@link #requestRebind(ComponentName)}
     81  * method is the <i>only</i> one that is safe to call before {@link #onListenerConnected()}
     82  * or after {@link #onListenerDisconnected()}.
     83  * </p>
     84  */
     85 public abstract class NotificationListenerService extends Service {
     86 
     87     private final String TAG = getClass().getSimpleName();
     88 
     89     /**
     90      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
     91      *     Normal interruption filter.
     92      */
     93     public static final int INTERRUPTION_FILTER_ALL
     94             = NotificationManager.INTERRUPTION_FILTER_ALL;
     95 
     96     /**
     97      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
     98      *     Priority interruption filter.
     99      */
    100     public static final int INTERRUPTION_FILTER_PRIORITY
    101             = NotificationManager.INTERRUPTION_FILTER_PRIORITY;
    102 
    103     /**
    104      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
    105      *     No interruptions filter.
    106      */
    107     public static final int INTERRUPTION_FILTER_NONE
    108             = NotificationManager.INTERRUPTION_FILTER_NONE;
    109 
    110     /**
    111      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
    112      *     Alarms only interruption filter.
    113      */
    114     public static final int INTERRUPTION_FILTER_ALARMS
    115             = NotificationManager.INTERRUPTION_FILTER_ALARMS;
    116 
    117     /** {@link #getCurrentInterruptionFilter() Interruption filter} constant - returned when
    118      * the value is unavailable for any reason.  For example, before the notification listener
    119      * is connected.
    120      *
    121      * {@see #onListenerConnected()}
    122      */
    123     public static final int INTERRUPTION_FILTER_UNKNOWN
    124             = NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
    125 
    126     /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
    127      * should disable notification sound, vibrating and other visual or aural effects.
    128      * This does not change the interruption filter, only the effects. **/
    129     public static final int HINT_HOST_DISABLE_EFFECTS = 1;
    130 
    131     /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
    132      * should disable notification sound, but not phone calls.
    133      * This does not change the interruption filter, only the effects. **/
    134     public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 1 << 1;
    135 
    136     /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
    137      * should disable phone call sounds, buyt not notification sound.
    138      * This does not change the interruption filter, only the effects. **/
    139     public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 1 << 2;
    140 
    141     /**
    142      * Whether notification suppressed by DND should not interruption visually when the screen is
    143      * off.
    144      */
    145     public static final int SUPPRESSED_EFFECT_SCREEN_OFF =
    146             NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
    147     /**
    148      * Whether notification suppressed by DND should not interruption visually when the screen is
    149      * on.
    150      */
    151     public static final int SUPPRESSED_EFFECT_SCREEN_ON =
    152             NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
    153 
    154 
    155     // Notification cancellation reasons
    156 
    157     /** Notification was canceled by the status bar reporting a notification click. */
    158     public static final int REASON_CLICK = 1;
    159     /** Notification was canceled by the status bar reporting a user dismissal. */
    160     public static final int REASON_CANCEL = 2;
    161     /** Notification was canceled by the status bar reporting a user dismiss all. */
    162     public static final int REASON_CANCEL_ALL = 3;
    163     /** Notification was canceled by the status bar reporting an inflation error. */
    164     public static final int REASON_ERROR = 4;
    165     /** Notification was canceled by the package manager modifying the package. */
    166     public static final int REASON_PACKAGE_CHANGED = 5;
    167     /** Notification was canceled by the owning user context being stopped. */
    168     public static final int REASON_USER_STOPPED = 6;
    169     /** Notification was canceled by the user banning the package. */
    170     public static final int REASON_PACKAGE_BANNED = 7;
    171     /** Notification was canceled by the app canceling this specific notification. */
    172     public static final int REASON_APP_CANCEL = 8;
    173     /** Notification was canceled by the app cancelling all its notifications. */
    174     public static final int REASON_APP_CANCEL_ALL = 9;
    175     /** Notification was canceled by a listener reporting a user dismissal. */
    176     public static final int REASON_LISTENER_CANCEL = 10;
    177     /** Notification was canceled by a listener reporting a user dismiss all. */
    178     public static final int REASON_LISTENER_CANCEL_ALL = 11;
    179     /** Notification was canceled because it was a member of a canceled group. */
    180     public static final int REASON_GROUP_SUMMARY_CANCELED = 12;
    181     /** Notification was canceled because it was an invisible member of a group. */
    182     public static final int REASON_GROUP_OPTIMIZATION = 13;
    183     /** Notification was canceled by the device administrator suspending the package. */
    184     public static final int REASON_PACKAGE_SUSPENDED = 14;
    185     /** Notification was canceled by the owning managed profile being turned off. */
    186     public static final int REASON_PROFILE_TURNED_OFF = 15;
    187     /** Autobundled summary notification was canceled because its group was unbundled */
    188     public static final int REASON_UNAUTOBUNDLED = 16;
    189     /** Notification was canceled by the user banning the channel. */
    190     public static final int REASON_CHANNEL_BANNED = 17;
    191     /** Notification was snoozed. */
    192     public static final int REASON_SNOOZED = 18;
    193     /** Notification was canceled due to timeout */
    194     public static final int REASON_TIMEOUT = 19;
    195 
    196     /**
    197      * The full trim of the StatusBarNotification including all its features.
    198      *
    199      * @hide
    200      * @removed
    201      */
    202     @SystemApi
    203     public static final int TRIM_FULL = 0;
    204 
    205     /**
    206      * A light trim of the StatusBarNotification excluding the following features:
    207      *
    208      * <ol>
    209      *     <li>{@link Notification#tickerView tickerView}</li>
    210      *     <li>{@link Notification#contentView contentView}</li>
    211      *     <li>{@link Notification#largeIcon largeIcon}</li>
    212      *     <li>{@link Notification#bigContentView bigContentView}</li>
    213      *     <li>{@link Notification#headsUpContentView headsUpContentView}</li>
    214      *     <li>{@link Notification#EXTRA_LARGE_ICON extras[EXTRA_LARGE_ICON]}</li>
    215      *     <li>{@link Notification#EXTRA_LARGE_ICON_BIG extras[EXTRA_LARGE_ICON_BIG]}</li>
    216      *     <li>{@link Notification#EXTRA_PICTURE extras[EXTRA_PICTURE]}</li>
    217      *     <li>{@link Notification#EXTRA_BIG_TEXT extras[EXTRA_BIG_TEXT]}</li>
    218      * </ol>
    219      *
    220      * @hide
    221      * @removed
    222      */
    223     @SystemApi
    224     public static final int TRIM_LIGHT = 1;
    225 
    226 
    227     /** @hide */
    228     @IntDef({NOTIFICATION_CHANNEL_OR_GROUP_ADDED, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED,
    229             NOTIFICATION_CHANNEL_OR_GROUP_DELETED})
    230     @Retention(RetentionPolicy.SOURCE)
    231     public @interface ChannelOrGroupModificationTypes {}
    232 
    233     /**
    234      * Channel or group modification reason provided to
    235      * {@link #onNotificationChannelModified(String, UserHandle,NotificationChannel, int)} or
    236      * {@link #onNotificationChannelGroupModified(String, UserHandle, NotificationChannelGroup,
    237      * int)}- the provided object was created.
    238      */
    239     public static final int NOTIFICATION_CHANNEL_OR_GROUP_ADDED = 1;
    240 
    241     /**
    242      * Channel or group modification reason provided to
    243      * {@link #onNotificationChannelModified(String, UserHandle, NotificationChannel, int)} or
    244      * {@link #onNotificationChannelGroupModified(String, UserHandle,NotificationChannelGroup, int)}
    245      * - the provided object was updated.
    246      */
    247     public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2;
    248 
    249     /**
    250      * Channel or group modification reason provided to
    251      * {@link #onNotificationChannelModified(String, UserHandle, NotificationChannel, int)} or
    252      * {@link #onNotificationChannelGroupModified(String, UserHandle, NotificationChannelGroup,
    253      * int)}- the provided object was deleted.
    254      */
    255     public static final int NOTIFICATION_CHANNEL_OR_GROUP_DELETED = 3;
    256 
    257     private final Object mLock = new Object();
    258 
    259     private Handler mHandler;
    260 
    261     /** @hide */
    262     protected NotificationListenerWrapper mWrapper = null;
    263     private boolean isConnected = false;
    264 
    265     @GuardedBy("mLock")
    266     private RankingMap mRankingMap;
    267 
    268     private INotificationManager mNoMan;
    269 
    270     /**
    271      * Only valid after a successful call to (@link registerAsService}.
    272      * @hide
    273      */
    274     protected int mCurrentUser;
    275 
    276     /**
    277      * This context is required for system services since NotificationListenerService isn't
    278      * started as a real Service and hence no context is available..
    279      * @hide
    280      */
    281     protected Context mSystemContext;
    282 
    283     /**
    284      * The {@link Intent} that must be declared as handled by the service.
    285      */
    286     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
    287     public static final String SERVICE_INTERFACE
    288             = "android.service.notification.NotificationListenerService";
    289 
    290     @Override
    291     protected void attachBaseContext(Context base) {
    292         super.attachBaseContext(base);
    293         mHandler = new MyHandler(getMainLooper());
    294     }
    295 
    296     /**
    297      * Implement this method to learn about new notifications as they are posted by apps.
    298      *
    299      * @param sbn A data structure encapsulating the original {@link android.app.Notification}
    300      *            object as well as its identifying information (tag and id) and source
    301      *            (package name).
    302      */
    303     public void onNotificationPosted(StatusBarNotification sbn) {
    304         // optional
    305     }
    306 
    307     /**
    308      * Implement this method to learn about new notifications as they are posted by apps.
    309      *
    310      * @param sbn A data structure encapsulating the original {@link android.app.Notification}
    311      *            object as well as its identifying information (tag and id) and source
    312      *            (package name).
    313      * @param rankingMap The current ranking map that can be used to retrieve ranking information
    314      *                   for active notifications, including the newly posted one.
    315      */
    316     public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
    317         onNotificationPosted(sbn);
    318     }
    319 
    320     /**
    321      * Implement this method to learn when notifications are removed.
    322      * <p>
    323      * This might occur because the user has dismissed the notification using system UI (or another
    324      * notification listener) or because the app has withdrawn the notification.
    325      * <p>
    326      * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
    327      * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
    328      * fields such as {@link android.app.Notification#contentView} and
    329      * {@link android.app.Notification#largeIcon}. However, all other fields on
    330      * {@link StatusBarNotification}, sufficient to match this call with a prior call to
    331      * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
    332      *
    333      * @param sbn A data structure encapsulating at least the original information (tag and id)
    334      *            and source (package name) used to post the {@link android.app.Notification} that
    335      *            was just removed.
    336      */
    337     public void onNotificationRemoved(StatusBarNotification sbn) {
    338         // optional
    339     }
    340 
    341     /**
    342      * Implement this method to learn when notifications are removed.
    343      * <p>
    344      * This might occur because the user has dismissed the notification using system UI (or another
    345      * notification listener) or because the app has withdrawn the notification.
    346      * <p>
    347      * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
    348      * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
    349      * fields such as {@link android.app.Notification#contentView} and
    350      * {@link android.app.Notification#largeIcon}. However, all other fields on
    351      * {@link StatusBarNotification}, sufficient to match this call with a prior call to
    352      * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
    353      *
    354      * @param sbn A data structure encapsulating at least the original information (tag and id)
    355      *            and source (package name) used to post the {@link android.app.Notification} that
    356      *            was just removed.
    357      * @param rankingMap The current ranking map that can be used to retrieve ranking information
    358      *                   for active notifications.
    359      *
    360      */
    361     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
    362         onNotificationRemoved(sbn);
    363     }
    364 
    365 
    366     /**
    367      * Implement this method to learn when notifications are removed and why.
    368      * <p>
    369      * This might occur because the user has dismissed the notification using system UI (or another
    370      * notification listener) or because the app has withdrawn the notification.
    371      * <p>
    372      * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
    373      * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
    374      * fields such as {@link android.app.Notification#contentView} and
    375      * {@link android.app.Notification#largeIcon}. However, all other fields on
    376      * {@link StatusBarNotification}, sufficient to match this call with a prior call to
    377      * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
    378      *
    379      ** @param sbn A data structure encapsulating at least the original information (tag and id)
    380      *            and source (package name) used to post the {@link android.app.Notification} that
    381      *            was just removed.
    382      * @param rankingMap The current ranking map that can be used to retrieve ranking information
    383      *                   for active notifications.
    384      * @param reason see {@link #REASON_LISTENER_CANCEL}, etc.
    385      */
    386     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
    387             int reason) {
    388         onNotificationRemoved(sbn, rankingMap);
    389     }
    390 
    391     /**
    392      * Implement this method to learn about when the listener is enabled and connected to
    393      * the notification manager.  You are safe to call {@link #getActiveNotifications()}
    394      * at this time.
    395      */
    396     public void onListenerConnected() {
    397         // optional
    398     }
    399 
    400     /**
    401      * Implement this method to learn about when the listener is disconnected from the
    402      * notification manager.You will not receive any events after this call, and may only
    403      * call {@link #requestRebind(ComponentName)} at this time.
    404      */
    405     public void onListenerDisconnected() {
    406         // optional
    407     }
    408 
    409     /**
    410      * Implement this method to be notified when the notification ranking changes.
    411      *
    412      * @param rankingMap The current ranking map that can be used to retrieve ranking information
    413      *                   for active notifications.
    414      */
    415     public void onNotificationRankingUpdate(RankingMap rankingMap) {
    416         // optional
    417     }
    418 
    419     /**
    420      * Implement this method to be notified when the
    421      * {@link #getCurrentListenerHints() Listener hints} change.
    422      *
    423      * @param hints The current {@link #getCurrentListenerHints() listener hints}.
    424      */
    425     public void onListenerHintsChanged(int hints) {
    426         // optional
    427     }
    428 
    429     /**
    430      * Implement this method to learn about notification channel modifications.
    431      *
    432      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
    433      * device} in order to receive this callback.
    434      *
    435      * @param pkg The package the channel belongs to.
    436      * @param user The user on which the change was made.
    437      * @param channel The channel that has changed.
    438      * @param modificationType One of {@link #NOTIFICATION_CHANNEL_OR_GROUP_ADDED},
    439      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
    440      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
    441      */
    442     public void onNotificationChannelModified(String pkg, UserHandle user,
    443             NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType) {
    444         // optional
    445     }
    446 
    447     /**
    448      * Implement this method to learn about notification channel group modifications.
    449      *
    450      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
    451      * device} in order to receive this callback.
    452      *
    453      * @param pkg The package the group belongs to.
    454      * @param user The user on which the change was made.
    455      * @param group The group that has changed.
    456      * @param modificationType One of {@link #NOTIFICATION_CHANNEL_OR_GROUP_ADDED},
    457      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
    458      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
    459      */
    460     public void onNotificationChannelGroupModified(String pkg, UserHandle user,
    461             NotificationChannelGroup group, @ChannelOrGroupModificationTypes int modificationType) {
    462         // optional
    463     }
    464 
    465     /**
    466      * Implement this method to be notified when the
    467      * {@link #getCurrentInterruptionFilter() interruption filter} changed.
    468      *
    469      * @param interruptionFilter The current
    470      *     {@link #getCurrentInterruptionFilter() interruption filter}.
    471      */
    472     public void onInterruptionFilterChanged(int interruptionFilter) {
    473         // optional
    474     }
    475 
    476     /** @hide */
    477     protected final INotificationManager getNotificationInterface() {
    478         if (mNoMan == null) {
    479             mNoMan = INotificationManager.Stub.asInterface(
    480                     ServiceManager.getService(Context.NOTIFICATION_SERVICE));
    481         }
    482         return mNoMan;
    483     }
    484 
    485     /**
    486      * Inform the notification manager about dismissal of a single notification.
    487      * <p>
    488      * Use this if your listener has a user interface that allows the user to dismiss individual
    489      * notifications, similar to the behavior of Android's status bar and notification panel.
    490      * It should be called after the user dismisses a single notification using your UI;
    491      * upon being informed, the notification manager will actually remove the notification
    492      * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
    493      * <p>
    494      * <b>Note:</b> If your listener allows the user to fire a notification's
    495      * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
    496      * this method at that time <i>if</i> the Notification in question has the
    497      * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
    498      *
    499      * <p>The service should wait for the {@link #onListenerConnected()} event
    500      * before performing this operation.
    501      *
    502      * @param pkg Package of the notifying app.
    503      * @param tag Tag of the notification as specified by the notifying app in
    504      *     {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
    505      * @param id  ID of the notification as specified by the notifying app in
    506      *     {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
    507      * <p>
    508      * @deprecated Use {@link #cancelNotification(String key)}
    509      * instead. Beginning with {@link android.os.Build.VERSION_CODES#LOLLIPOP} this method will no longer
    510      * cancel the notification. It will continue to cancel the notification for applications
    511      * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
    512      */
    513     @Deprecated
    514     public final void cancelNotification(String pkg, String tag, int id) {
    515         if (!isBound()) return;
    516         try {
    517             getNotificationInterface().cancelNotificationFromListener(
    518                     mWrapper, pkg, tag, id);
    519         } catch (android.os.RemoteException ex) {
    520             Log.v(TAG, "Unable to contact notification manager", ex);
    521         }
    522     }
    523 
    524     /**
    525      * Inform the notification manager about dismissal of a single notification.
    526      * <p>
    527      * Use this if your listener has a user interface that allows the user to dismiss individual
    528      * notifications, similar to the behavior of Android's status bar and notification panel.
    529      * It should be called after the user dismisses a single notification using your UI;
    530      * upon being informed, the notification manager will actually remove the notification
    531      * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
    532      * <p>
    533      * <b>Note:</b> If your listener allows the user to fire a notification's
    534      * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
    535      * this method at that time <i>if</i> the Notification in question has the
    536      * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
    537      * <p>
    538      *
    539      * <p>The service should wait for the {@link #onListenerConnected()} event
    540      * before performing this operation.
    541      *
    542      * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}.
    543      */
    544     public final void cancelNotification(String key) {
    545         if (!isBound()) return;
    546         try {
    547             getNotificationInterface().cancelNotificationsFromListener(mWrapper,
    548                     new String[] { key });
    549         } catch (android.os.RemoteException ex) {
    550             Log.v(TAG, "Unable to contact notification manager", ex);
    551         }
    552     }
    553 
    554     /**
    555      * Inform the notification manager about dismissal of all notifications.
    556      * <p>
    557      * Use this if your listener has a user interface that allows the user to dismiss all
    558      * notifications, similar to the behavior of Android's status bar and notification panel.
    559      * It should be called after the user invokes the "dismiss all" function of your UI;
    560      * upon being informed, the notification manager will actually remove all active notifications
    561      * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks.
    562      *
    563      * <p>The service should wait for the {@link #onListenerConnected()} event
    564      * before performing this operation.
    565      *
    566      * {@see #cancelNotification(String, String, int)}
    567      */
    568     public final void cancelAllNotifications() {
    569         cancelNotifications(null /*all*/);
    570     }
    571 
    572     /**
    573      * Inform the notification manager about dismissal of specific notifications.
    574      * <p>
    575      * Use this if your listener has a user interface that allows the user to dismiss
    576      * multiple notifications at once.
    577      *
    578      * <p>The service should wait for the {@link #onListenerConnected()} event
    579      * before performing this operation.
    580      *
    581      * @param keys Notifications to dismiss, or {@code null} to dismiss all.
    582      *
    583      * {@see #cancelNotification(String, String, int)}
    584      */
    585     public final void cancelNotifications(String[] keys) {
    586         if (!isBound()) return;
    587         try {
    588             getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys);
    589         } catch (android.os.RemoteException ex) {
    590             Log.v(TAG, "Unable to contact notification manager", ex);
    591         }
    592     }
    593 
    594     /**
    595      * Inform the notification manager about snoozing a specific notification.
    596      * <p>
    597      * Use this if your listener has a user interface that allows the user to snooze a notification
    598      * until a given {@link SnoozeCriterion}. It should be called after the user snoozes a single
    599      * notification using your UI; upon being informed, the notification manager will actually
    600      * remove the notification and you will get an
    601      * {@link #onNotificationRemoved(StatusBarNotification)} callback. When the snoozing period
    602      * expires, you will get a {@link #onNotificationPosted(StatusBarNotification, RankingMap)}
    603      * callback for the notification.
    604      * @param key The key of the notification to snooze
    605      * @param snoozeCriterionId The{@link SnoozeCriterion#getId()} of a context to snooze the
    606      *                          notification until.
    607      * @hide
    608      * @removed
    609      */
    610     @SystemApi
    611     public final void snoozeNotification(String key, String snoozeCriterionId) {
    612         if (!isBound()) return;
    613         try {
    614             getNotificationInterface().snoozeNotificationUntilContextFromListener(
    615                     mWrapper, key, snoozeCriterionId);
    616         } catch (android.os.RemoteException ex) {
    617             Log.v(TAG, "Unable to contact notification manager", ex);
    618         }
    619     }
    620 
    621     /**
    622      * Inform the notification manager about snoozing a specific notification.
    623      * <p>
    624      * Use this if your listener has a user interface that allows the user to snooze a notification
    625      * for a time. It should be called after the user snoozes a single notification using
    626      * your UI; upon being informed, the notification manager will actually remove the notification
    627      * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. When the
    628      * snoozing period expires, you will get a
    629      * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
    630      * notification.
    631      * @param key The key of the notification to snooze
    632      * @param durationMs A duration to snooze the notification for, in milliseconds.
    633      */
    634     public final void snoozeNotification(String key, long durationMs) {
    635         if (!isBound()) return;
    636         try {
    637             getNotificationInterface().snoozeNotificationUntilFromListener(
    638                     mWrapper, key, durationMs);
    639         } catch (android.os.RemoteException ex) {
    640             Log.v(TAG, "Unable to contact notification manager", ex);
    641         }
    642     }
    643 
    644 
    645     /**
    646      * Inform the notification manager that these notifications have been viewed by the
    647      * user. This should only be called when there is sufficient confidence that the user is
    648      * looking at the notifications, such as when the notifications appear on the screen due to
    649      * an explicit user interaction.
    650      *
    651      * <p>The service should wait for the {@link #onListenerConnected()} event
    652      * before performing this operation.
    653      *
    654      * @param keys Notifications to mark as seen.
    655      */
    656     public final void setNotificationsShown(String[] keys) {
    657         if (!isBound()) return;
    658         try {
    659             getNotificationInterface().setNotificationsShownFromListener(mWrapper, keys);
    660         } catch (android.os.RemoteException ex) {
    661             Log.v(TAG, "Unable to contact notification manager", ex);
    662         }
    663     }
    664 
    665 
    666     /**
    667      * Updates a notification channel for a given package for a given user. This should only be used
    668      * to reflect changes a user has made to the channel via the listener's user interface.
    669      *
    670      * <p>This method will throw a security exception if you don't have access to notifications
    671      * for the given user.</p>
    672      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
    673      * device} in order to use this method.
    674      *
    675      * @param pkg The package the channel belongs to.
    676      * @param user The user the channel belongs to.
    677      * @param channel the channel to update.
    678      */
    679     public final void updateNotificationChannel(@NonNull String pkg, @NonNull UserHandle user,
    680             @NonNull NotificationChannel channel) {
    681         if (!isBound()) return;
    682         try {
    683             getNotificationInterface().updateNotificationChannelFromPrivilegedListener(
    684                     mWrapper, pkg, user, channel);
    685         } catch (RemoteException e) {
    686             Log.v(TAG, "Unable to contact notification manager", e);
    687             throw e.rethrowFromSystemServer();
    688         }
    689     }
    690 
    691     /**
    692      * Returns all notification channels belonging to the given package for a given user.
    693      *
    694      * <p>This method will throw a security exception if you don't have access to notifications
    695      * for the given user.</p>
    696      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
    697      * device} in order to use this method.
    698      *
    699      * @param pkg The package to retrieve channels for.
    700      */
    701     public final List<NotificationChannel> getNotificationChannels(@NonNull String pkg,
    702             @NonNull UserHandle user) {
    703         if (!isBound()) return null;
    704         try {
    705 
    706             return getNotificationInterface().getNotificationChannelsFromPrivilegedListener(
    707                     mWrapper, pkg, user).getList();
    708         } catch (RemoteException e) {
    709             Log.v(TAG, "Unable to contact notification manager", e);
    710             throw e.rethrowFromSystemServer();
    711         }
    712     }
    713 
    714     /**
    715      * Returns all notification channel groups belonging to the given package for a given user.
    716      *
    717      * <p>This method will throw a security exception if you don't have access to notifications
    718      * for the given user.</p>
    719      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
    720      * device} in order to use this method.
    721      *
    722      * @param pkg The package to retrieve channel groups for.
    723      */
    724     public final List<NotificationChannelGroup> getNotificationChannelGroups(@NonNull String pkg,
    725             @NonNull UserHandle user) {
    726         if (!isBound()) return null;
    727         try {
    728 
    729             return getNotificationInterface().getNotificationChannelGroupsFromPrivilegedListener(
    730                     mWrapper, pkg, user).getList();
    731         } catch (RemoteException e) {
    732             Log.v(TAG, "Unable to contact notification manager", e);
    733             throw e.rethrowFromSystemServer();
    734         }
    735     }
    736 
    737     /**
    738      * Sets the notification trim that will be received via {@link #onNotificationPosted}.
    739      *
    740      * <p>
    741      * Setting a trim other than {@link #TRIM_FULL} enables listeners that don't need access to the
    742      * full notification features right away to reduce their memory footprint. Full notifications
    743      * can be requested on-demand via {@link #getActiveNotifications(int)}.
    744      *
    745      * <p>
    746      * Set to {@link #TRIM_FULL} initially.
    747      *
    748      * <p>The service should wait for the {@link #onListenerConnected()} event
    749      * before performing this operation.
    750      *
    751      * @hide
    752      * @removed
    753      *
    754      * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}.
    755      *             See <code>TRIM_*</code> constants.
    756      */
    757     @SystemApi
    758     public final void setOnNotificationPostedTrim(int trim) {
    759         if (!isBound()) return;
    760         try {
    761             getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim);
    762         } catch (RemoteException ex) {
    763             Log.v(TAG, "Unable to contact notification manager", ex);
    764         }
    765     }
    766 
    767     /**
    768      * Request the list of outstanding notifications (that is, those that are visible to the
    769      * current user). Useful when you don't know what's already been posted.
    770      *
    771      * <p>The service should wait for the {@link #onListenerConnected()} event
    772      * before performing this operation.
    773      *
    774      * @return An array of active notifications, sorted in natural order.
    775      */
    776     public StatusBarNotification[] getActiveNotifications() {
    777         return getActiveNotifications(null, TRIM_FULL);
    778     }
    779 
    780     /**
    781      * Like {@link #getActiveNotifications()}, but returns the list of currently snoozed
    782      * notifications, for all users this listener has access to.
    783      *
    784      * <p>The service should wait for the {@link #onListenerConnected()} event
    785      * before performing this operation.
    786      *
    787      * @return An array of snoozed notifications, sorted in natural order.
    788      */
    789     public final StatusBarNotification[] getSnoozedNotifications() {
    790         try {
    791             ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
    792                     .getSnoozedNotificationsFromListener(mWrapper, TRIM_FULL);
    793             return cleanUpNotificationList(parceledList);
    794         } catch (android.os.RemoteException ex) {
    795             Log.v(TAG, "Unable to contact notification manager", ex);
    796         }
    797         return null;
    798     }
    799 
    800     /**
    801      * Request the list of outstanding notifications (that is, those that are visible to the
    802      * current user). Useful when you don't know what's already been posted.
    803      *
    804      * @hide
    805      * @removed
    806      *
    807      * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
    808      * @return An array of active notifications, sorted in natural order.
    809      */
    810     @SystemApi
    811     public StatusBarNotification[] getActiveNotifications(int trim) {
    812         return getActiveNotifications(null, trim);
    813     }
    814 
    815     /**
    816      * Request one or more notifications by key. Useful if you have been keeping track of
    817      * notifications but didn't want to retain the bits, and now need to go back and extract
    818      * more data out of those notifications.
    819      *
    820      * <p>The service should wait for the {@link #onListenerConnected()} event
    821      * before performing this operation.
    822      *
    823      * @param keys the keys of the notifications to request
    824      * @return An array of notifications corresponding to the requested keys, in the
    825      * same order as the key list.
    826      */
    827     public StatusBarNotification[] getActiveNotifications(String[] keys) {
    828         return getActiveNotifications(keys, TRIM_FULL);
    829     }
    830 
    831     /**
    832      * Request one or more notifications by key. Useful if you have been keeping track of
    833      * notifications but didn't want to retain the bits, and now need to go back and extract
    834      * more data out of those notifications.
    835      *
    836      * @hide
    837      * @removed
    838      *
    839      * @param keys the keys of the notifications to request
    840      * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
    841      * @return An array of notifications corresponding to the requested keys, in the
    842      * same order as the key list.
    843      */
    844     @SystemApi
    845     public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) {
    846         if (!isBound())
    847             return null;
    848         try {
    849             ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
    850                     .getActiveNotificationsFromListener(mWrapper, keys, trim);
    851             return cleanUpNotificationList(parceledList);
    852         } catch (android.os.RemoteException ex) {
    853             Log.v(TAG, "Unable to contact notification manager", ex);
    854         }
    855         return null;
    856     }
    857 
    858     private StatusBarNotification[] cleanUpNotificationList(
    859             ParceledListSlice<StatusBarNotification> parceledList) {
    860         List<StatusBarNotification> list = parceledList.getList();
    861         ArrayList<StatusBarNotification> corruptNotifications = null;
    862         int N = list.size();
    863         for (int i = 0; i < N; i++) {
    864             StatusBarNotification sbn = list.get(i);
    865             Notification notification = sbn.getNotification();
    866             try {
    867                 // convert icon metadata to legacy format for older clients
    868                 createLegacyIconExtras(notification);
    869                 // populate remote views for older clients.
    870                 maybePopulateRemoteViews(notification);
    871             } catch (IllegalArgumentException e) {
    872                 if (corruptNotifications == null) {
    873                     corruptNotifications = new ArrayList<>(N);
    874                 }
    875                 corruptNotifications.add(sbn);
    876                 Log.w(TAG, "get(Active/Snoozed)Notifications: can't rebuild notification from " +
    877                         sbn.getPackageName());
    878             }
    879         }
    880         if (corruptNotifications != null) {
    881             list.removeAll(corruptNotifications);
    882         }
    883         return list.toArray(new StatusBarNotification[list.size()]);
    884     }
    885 
    886     /**
    887      * Gets the set of hints representing current state.
    888      *
    889      * <p>
    890      * The current state may differ from the requested state if the hint represents state
    891      * shared across all listeners or a feature the notification host does not support or refuses
    892      * to grant.
    893      *
    894      * <p>The service should wait for the {@link #onListenerConnected()} event
    895      * before performing this operation.
    896      *
    897      * @return Zero or more of the HINT_ constants.
    898      */
    899     public final int getCurrentListenerHints() {
    900         if (!isBound()) return 0;
    901         try {
    902             return getNotificationInterface().getHintsFromListener(mWrapper);
    903         } catch (android.os.RemoteException ex) {
    904             Log.v(TAG, "Unable to contact notification manager", ex);
    905             return 0;
    906         }
    907     }
    908 
    909     /**
    910      * Gets the current notification interruption filter active on the host.
    911      *
    912      * <p>
    913      * The interruption filter defines which notifications are allowed to interrupt the user
    914      * (e.g. via sound &amp; vibration) and is applied globally. Listeners can find out whether
    915      * a specific notification matched the interruption filter via
    916      * {@link Ranking#matchesInterruptionFilter()}.
    917      * <p>
    918      * The current filter may differ from the previously requested filter if the notification host
    919      * does not support or refuses to apply the requested filter, or if another component changed
    920      * the filter in the meantime.
    921      * <p>
    922      * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
    923      *
    924      * <p>The service should wait for the {@link #onListenerConnected()} event
    925      * before performing this operation.
    926      *
    927      * @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when
    928      * unavailable.
    929      */
    930     public final int getCurrentInterruptionFilter() {
    931         if (!isBound()) return INTERRUPTION_FILTER_UNKNOWN;
    932         try {
    933             return getNotificationInterface().getInterruptionFilterFromListener(mWrapper);
    934         } catch (android.os.RemoteException ex) {
    935             Log.v(TAG, "Unable to contact notification manager", ex);
    936             return INTERRUPTION_FILTER_UNKNOWN;
    937         }
    938     }
    939 
    940     /**
    941      * Sets the desired {@link #getCurrentListenerHints() listener hints}.
    942      *
    943      * <p>
    944      * This is merely a request, the host may or may not choose to take action depending
    945      * on other listener requests or other global state.
    946      * <p>
    947      * Listen for updates using {@link #onListenerHintsChanged(int)}.
    948      *
    949      * <p>The service should wait for the {@link #onListenerConnected()} event
    950      * before performing this operation.
    951      *
    952      * @param hints One or more of the HINT_ constants.
    953      */
    954     public final void requestListenerHints(int hints) {
    955         if (!isBound()) return;
    956         try {
    957             getNotificationInterface().requestHintsFromListener(mWrapper, hints);
    958         } catch (android.os.RemoteException ex) {
    959             Log.v(TAG, "Unable to contact notification manager", ex);
    960         }
    961     }
    962 
    963     /**
    964      * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}.
    965      *
    966      * <p>
    967      * This is merely a request, the host may or may not choose to apply the requested
    968      * interruption filter depending on other listener requests or other global state.
    969      * <p>
    970      * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
    971      *
    972      * <p>The service should wait for the {@link #onListenerConnected()} event
    973      * before performing this operation.
    974      *
    975      * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants.
    976      */
    977     public final void requestInterruptionFilter(int interruptionFilter) {
    978         if (!isBound()) return;
    979         try {
    980             getNotificationInterface()
    981                     .requestInterruptionFilterFromListener(mWrapper, interruptionFilter);
    982         } catch (android.os.RemoteException ex) {
    983             Log.v(TAG, "Unable to contact notification manager", ex);
    984         }
    985     }
    986 
    987     /**
    988      * Returns current ranking information.
    989      *
    990      * <p>
    991      * The returned object represents the current ranking snapshot and only
    992      * applies for currently active notifications.
    993      * <p>
    994      * Generally you should use the RankingMap that is passed with events such
    995      * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)},
    996      * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and
    997      * so on. This method should only be used when needing access outside of
    998      * such events, for example to retrieve the RankingMap right after
    999      * initialization.
   1000      *
   1001      * <p>The service should wait for the {@link #onListenerConnected()} event
   1002      * before performing this operation.
   1003      *
   1004      * @return A {@link RankingMap} object providing access to ranking information
   1005      */
   1006     public RankingMap getCurrentRanking() {
   1007         synchronized (mLock) {
   1008             return mRankingMap;
   1009         }
   1010     }
   1011 
   1012     /**
   1013      * This is not the lifecycle event you are looking for.
   1014      *
   1015      * <p>The service should wait for the {@link #onListenerConnected()} event
   1016      * before performing any operations.
   1017      */
   1018     @Override
   1019     public IBinder onBind(Intent intent) {
   1020         if (mWrapper == null) {
   1021             mWrapper = new NotificationListenerWrapper();
   1022         }
   1023         return mWrapper;
   1024     }
   1025 
   1026     /** @hide */
   1027     protected boolean isBound() {
   1028         if (mWrapper == null) {
   1029             Log.w(TAG, "Notification listener service not yet bound.");
   1030             return false;
   1031         }
   1032         return true;
   1033     }
   1034 
   1035     @Override
   1036     public void onDestroy() {
   1037         onListenerDisconnected();
   1038         super.onDestroy();
   1039     }
   1040 
   1041     /**
   1042      * Directly register this service with the Notification Manager.
   1043      *
   1044      * <p>Only system services may use this call. It will fail for non-system callers.
   1045      * Apps should ask the user to add their listener in Settings.
   1046      *
   1047      * @param context Context required for accessing resources. Since this service isn't
   1048      *    launched as a real Service when using this method, a context has to be passed in.
   1049      * @param componentName the component that will consume the notification information
   1050      * @param currentUser the user to use as the stream filter
   1051      * @hide
   1052      * @removed
   1053      */
   1054     @SystemApi
   1055     public void registerAsSystemService(Context context, ComponentName componentName,
   1056             int currentUser) throws RemoteException {
   1057         if (mWrapper == null) {
   1058             mWrapper = new NotificationListenerWrapper();
   1059         }
   1060         mSystemContext = context;
   1061         INotificationManager noMan = getNotificationInterface();
   1062         mHandler = new MyHandler(context.getMainLooper());
   1063         mCurrentUser = currentUser;
   1064         noMan.registerListener(mWrapper, componentName, currentUser);
   1065     }
   1066 
   1067     /**
   1068      * Directly unregister this service from the Notification Manager.
   1069      *
   1070      * <p>This method will fail for listeners that were not registered
   1071      * with (@link registerAsService).
   1072      * @hide
   1073      * @removed
   1074      */
   1075     @SystemApi
   1076     public void unregisterAsSystemService() throws RemoteException {
   1077         if (mWrapper != null) {
   1078             INotificationManager noMan = getNotificationInterface();
   1079             noMan.unregisterListener(mWrapper, mCurrentUser);
   1080         }
   1081     }
   1082 
   1083     /**
   1084      * Request that the listener be rebound, after a previous call to {@link #requestUnbind}.
   1085      *
   1086      * <p>This method will fail for listeners that have
   1087      * not been granted the permission by the user.
   1088      */
   1089     public static void requestRebind(ComponentName componentName) {
   1090         INotificationManager noMan = INotificationManager.Stub.asInterface(
   1091                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
   1092         try {
   1093             noMan.requestBindListener(componentName);
   1094         } catch (RemoteException ex) {
   1095             throw ex.rethrowFromSystemServer();
   1096         }
   1097     }
   1098 
   1099     /**
   1100      * Request that the service be unbound.
   1101      *
   1102      * <p>Once this is called, you will no longer receive updates and no method calls are
   1103      * guaranteed to be successful, until you next receive the {@link #onListenerConnected()} event.
   1104      * The service will likely be killed by the system after this call.
   1105      *
   1106      * <p>The service should wait for the {@link #onListenerConnected()} event
   1107      * before performing this operation. I know it's tempting, but you must wait.
   1108      */
   1109     public final void requestUnbind() {
   1110         if (mWrapper != null) {
   1111             INotificationManager noMan = getNotificationInterface();
   1112             try {
   1113                 noMan.requestUnbindListener(mWrapper);
   1114                 // Disable future messages.
   1115                 isConnected = false;
   1116             } catch (RemoteException ex) {
   1117                 throw ex.rethrowFromSystemServer();
   1118             }
   1119         }
   1120     }
   1121 
   1122     /** Convert new-style Icons to legacy representations for pre-M clients. */
   1123     private void createLegacyIconExtras(Notification n) {
   1124         Icon smallIcon = n.getSmallIcon();
   1125         Icon largeIcon = n.getLargeIcon();
   1126         if (smallIcon != null && smallIcon.getType() == Icon.TYPE_RESOURCE) {
   1127             n.extras.putInt(Notification.EXTRA_SMALL_ICON, smallIcon.getResId());
   1128             n.icon = smallIcon.getResId();
   1129         }
   1130         if (largeIcon != null) {
   1131             Drawable d = largeIcon.loadDrawable(getContext());
   1132             if (d != null && d instanceof BitmapDrawable) {
   1133                 final Bitmap largeIconBits = ((BitmapDrawable) d).getBitmap();
   1134                 n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, largeIconBits);
   1135                 n.largeIcon = largeIconBits;
   1136             }
   1137         }
   1138     }
   1139 
   1140     /**
   1141      * Populates remote views for pre-N targeting apps.
   1142      */
   1143     private void maybePopulateRemoteViews(Notification notification) {
   1144         if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
   1145             Builder builder = Builder.recoverBuilder(getContext(), notification);
   1146 
   1147             // Some styles wrap Notification's contentView, bigContentView and headsUpContentView.
   1148             // First inflate them all, only then set them to avoid recursive wrapping.
   1149             RemoteViews content = builder.createContentView();
   1150             RemoteViews big = builder.createBigContentView();
   1151             RemoteViews headsUp = builder.createHeadsUpContentView();
   1152 
   1153             notification.contentView = content;
   1154             notification.bigContentView = big;
   1155             notification.headsUpContentView = headsUp;
   1156         }
   1157     }
   1158 
   1159     /** @hide */
   1160     protected class NotificationListenerWrapper extends INotificationListener.Stub {
   1161         @Override
   1162         public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
   1163                 NotificationRankingUpdate update) {
   1164             StatusBarNotification sbn;
   1165             try {
   1166                 sbn = sbnHolder.get();
   1167             } catch (RemoteException e) {
   1168                 Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
   1169                 return;
   1170             }
   1171 
   1172             try {
   1173                 // convert icon metadata to legacy format for older clients
   1174                 createLegacyIconExtras(sbn.getNotification());
   1175                 maybePopulateRemoteViews(sbn.getNotification());
   1176             } catch (IllegalArgumentException e) {
   1177                 // warn and drop corrupt notification
   1178                 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
   1179                         sbn.getPackageName());
   1180                 sbn = null;
   1181             }
   1182 
   1183             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
   1184             synchronized (mLock) {
   1185                 applyUpdateLocked(update);
   1186                 if (sbn != null) {
   1187                     SomeArgs args = SomeArgs.obtain();
   1188                     args.arg1 = sbn;
   1189                     args.arg2 = mRankingMap;
   1190                     mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
   1191                             args).sendToTarget();
   1192                 } else {
   1193                     // still pass along the ranking map, it may contain other information
   1194                     mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
   1195                             mRankingMap).sendToTarget();
   1196                 }
   1197             }
   1198 
   1199         }
   1200 
   1201         @Override
   1202         public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
   1203                 NotificationRankingUpdate update, int reason) {
   1204             StatusBarNotification sbn;
   1205             try {
   1206                 sbn = sbnHolder.get();
   1207             } catch (RemoteException e) {
   1208                 Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e);
   1209                 return;
   1210             }
   1211             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
   1212             synchronized (mLock) {
   1213                 applyUpdateLocked(update);
   1214                 SomeArgs args = SomeArgs.obtain();
   1215                 args.arg1 = sbn;
   1216                 args.arg2 = mRankingMap;
   1217                 args.arg3 = reason;
   1218                 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED,
   1219                         args).sendToTarget();
   1220             }
   1221 
   1222         }
   1223 
   1224         @Override
   1225         public void onListenerConnected(NotificationRankingUpdate update) {
   1226             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
   1227             synchronized (mLock) {
   1228                 applyUpdateLocked(update);
   1229             }
   1230             isConnected = true;
   1231             mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_CONNECTED).sendToTarget();
   1232         }
   1233 
   1234         @Override
   1235         public void onNotificationRankingUpdate(NotificationRankingUpdate update)
   1236                 throws RemoteException {
   1237             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
   1238             synchronized (mLock) {
   1239                 applyUpdateLocked(update);
   1240                 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
   1241                         mRankingMap).sendToTarget();
   1242             }
   1243 
   1244         }
   1245 
   1246         @Override
   1247         public void onListenerHintsChanged(int hints) throws RemoteException {
   1248             mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_HINTS_CHANGED,
   1249                     hints, 0).sendToTarget();
   1250         }
   1251 
   1252         @Override
   1253         public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException {
   1254             mHandler.obtainMessage(MyHandler.MSG_ON_INTERRUPTION_FILTER_CHANGED,
   1255                     interruptionFilter, 0).sendToTarget();
   1256         }
   1257 
   1258         @Override
   1259         public void onNotificationEnqueued(IStatusBarNotificationHolder notificationHolder)
   1260                 throws RemoteException {
   1261             // no-op in the listener
   1262         }
   1263 
   1264         @Override
   1265         public void onNotificationSnoozedUntilContext(
   1266                 IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId)
   1267                 throws RemoteException {
   1268             // no-op in the listener
   1269         }
   1270 
   1271         @Override
   1272         public void onNotificationChannelModification(String pkgName, UserHandle user,
   1273                 NotificationChannel channel,
   1274                 @ChannelOrGroupModificationTypes int modificationType) {
   1275             SomeArgs args = SomeArgs.obtain();
   1276             args.arg1 = pkgName;
   1277             args.arg2 = user;
   1278             args.arg3 = channel;
   1279             args.arg4 = modificationType;
   1280             mHandler.obtainMessage(
   1281                     MyHandler.MSG_ON_NOTIFICATION_CHANNEL_MODIFIED, args).sendToTarget();
   1282         }
   1283 
   1284         @Override
   1285         public void onNotificationChannelGroupModification(String pkgName, UserHandle user,
   1286                 NotificationChannelGroup group,
   1287                 @ChannelOrGroupModificationTypes int modificationType) {
   1288             SomeArgs args = SomeArgs.obtain();
   1289             args.arg1 = pkgName;
   1290             args.arg2 = user;
   1291             args.arg3 = group;
   1292             args.arg4 = modificationType;
   1293             mHandler.obtainMessage(
   1294                     MyHandler.MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED, args).sendToTarget();
   1295         }
   1296     }
   1297 
   1298     /**
   1299      * @hide
   1300      */
   1301     public final void applyUpdateLocked(NotificationRankingUpdate update) {
   1302         mRankingMap = new RankingMap(update);
   1303     }
   1304 
   1305     /** @hide */
   1306     protected Context getContext() {
   1307         if (mSystemContext != null) {
   1308             return mSystemContext;
   1309         }
   1310         return this;
   1311     }
   1312 
   1313     /**
   1314      * Stores ranking related information on a currently active notification.
   1315      *
   1316      * <p>
   1317      * Ranking objects aren't automatically updated as notification events
   1318      * occur. Instead, ranking information has to be retrieved again via the
   1319      * current {@link RankingMap}.
   1320      */
   1321     public static class Ranking {
   1322 
   1323         /** Value signifying that the user has not expressed a per-app visibility override value.
   1324          * @hide */
   1325         public static final int VISIBILITY_NO_OVERRIDE = NotificationManager.VISIBILITY_NO_OVERRIDE;
   1326 
   1327         private String mKey;
   1328         private int mRank = -1;
   1329         private boolean mIsAmbient;
   1330         private boolean mMatchesInterruptionFilter;
   1331         private int mVisibilityOverride;
   1332         private int mSuppressedVisualEffects;
   1333         private @NotificationManager.Importance int mImportance;
   1334         private CharSequence mImportanceExplanation;
   1335         // System specified group key.
   1336         private String mOverrideGroupKey;
   1337         // Notification assistant channel override.
   1338         private NotificationChannel mChannel;
   1339         // Notification assistant people override.
   1340         private ArrayList<String> mOverridePeople;
   1341         // Notification assistant snooze criteria.
   1342         private ArrayList<SnoozeCriterion> mSnoozeCriteria;
   1343         private boolean mShowBadge;
   1344 
   1345         public Ranking() {}
   1346 
   1347         /**
   1348          * Returns the key of the notification this Ranking applies to.
   1349          */
   1350         public String getKey() {
   1351             return mKey;
   1352         }
   1353 
   1354         /**
   1355          * Returns the rank of the notification.
   1356          *
   1357          * @return the rank of the notification, that is the 0-based index in
   1358          *     the list of active notifications.
   1359          */
   1360         public int getRank() {
   1361             return mRank;
   1362         }
   1363 
   1364         /**
   1365          * Returns whether the notification is an ambient notification, that is
   1366          * a notification that doesn't require the user's immediate attention.
   1367          */
   1368         public boolean isAmbient() {
   1369             return mIsAmbient;
   1370         }
   1371 
   1372         /**
   1373          * Returns the user specified visibility for the package that posted
   1374          * this notification, or
   1375          * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
   1376          * no such preference has been expressed.
   1377          * @hide
   1378          */
   1379         public int getVisibilityOverride() {
   1380             return mVisibilityOverride;
   1381         }
   1382 
   1383         /**
   1384          * Returns the type(s) of visual effects that should be suppressed for this notification.
   1385          * See {@link #SUPPRESSED_EFFECT_SCREEN_OFF}, {@link #SUPPRESSED_EFFECT_SCREEN_ON}.
   1386          */
   1387         public int getSuppressedVisualEffects() {
   1388             return mSuppressedVisualEffects;
   1389         }
   1390 
   1391         /**
   1392          * Returns whether the notification matches the user's interruption
   1393          * filter.
   1394          *
   1395          * @return {@code true} if the notification is allowed by the filter, or
   1396          * {@code false} if it is blocked.
   1397          */
   1398         public boolean matchesInterruptionFilter() {
   1399             return mMatchesInterruptionFilter;
   1400         }
   1401 
   1402         /**
   1403          * Returns the importance of the notification, which dictates its
   1404          * modes of presentation, see: {@link NotificationManager#IMPORTANCE_DEFAULT}, etc.
   1405          *
   1406          * @return the importance of the notification
   1407          */
   1408         public @NotificationManager.Importance int getImportance() {
   1409             return mImportance;
   1410         }
   1411 
   1412         /**
   1413          * If the importance has been overridden by user preference, then this will be non-null,
   1414          * and should be displayed to the user.
   1415          *
   1416          * @return the explanation for the importance, or null if it is the natural importance
   1417          */
   1418         public CharSequence getImportanceExplanation() {
   1419             return mImportanceExplanation;
   1420         }
   1421 
   1422         /**
   1423          * If the system has overridden the group key, then this will be non-null, and this
   1424          * key should be used to bundle notifications.
   1425          */
   1426         public String getOverrideGroupKey() {
   1427             return mOverrideGroupKey;
   1428         }
   1429 
   1430         /**
   1431          * Returns the notification channel this notification was posted to, which dictates
   1432          * notification behavior and presentation.
   1433          */
   1434         public NotificationChannel getChannel() {
   1435             return mChannel;
   1436         }
   1437 
   1438         /**
   1439          * If the {@link NotificationAssistantService} has added people to this notification, then
   1440          * this will be non-null.
   1441          * @hide
   1442          * @removed
   1443          */
   1444         @SystemApi
   1445         public List<String> getAdditionalPeople() {
   1446             return mOverridePeople;
   1447         }
   1448 
   1449         /**
   1450          * Returns snooze criteria provided by the {@link NotificationAssistantService}. If your
   1451          * user interface displays options for snoozing notifications these criteria should be
   1452          * displayed as well.
   1453          * @hide
   1454          * @removed
   1455          */
   1456         @SystemApi
   1457         public List<SnoozeCriterion> getSnoozeCriteria() {
   1458             return mSnoozeCriteria;
   1459         }
   1460 
   1461         /**
   1462          * Returns whether this notification can be displayed as a badge.
   1463          *
   1464          * @return true if the notification can be displayed as a badge, false otherwise.
   1465          */
   1466         public boolean canShowBadge() {
   1467             return mShowBadge;
   1468         }
   1469 
   1470         private void populate(String key, int rank, boolean matchesInterruptionFilter,
   1471                 int visibilityOverride, int suppressedVisualEffects, int importance,
   1472                 CharSequence explanation, String overrideGroupKey,
   1473                 NotificationChannel channel, ArrayList<String> overridePeople,
   1474                 ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge) {
   1475             mKey = key;
   1476             mRank = rank;
   1477             mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
   1478             mMatchesInterruptionFilter = matchesInterruptionFilter;
   1479             mVisibilityOverride = visibilityOverride;
   1480             mSuppressedVisualEffects = suppressedVisualEffects;
   1481             mImportance = importance;
   1482             mImportanceExplanation = explanation;
   1483             mOverrideGroupKey = overrideGroupKey;
   1484             mChannel = channel;
   1485             mOverridePeople = overridePeople;
   1486             mSnoozeCriteria = snoozeCriteria;
   1487             mShowBadge = showBadge;
   1488         }
   1489 
   1490         /**
   1491          * {@hide}
   1492          */
   1493         public static String importanceToString(int importance) {
   1494             switch (importance) {
   1495                 case NotificationManager.IMPORTANCE_UNSPECIFIED:
   1496                     return "UNSPECIFIED";
   1497                 case NotificationManager.IMPORTANCE_NONE:
   1498                     return "NONE";
   1499                 case NotificationManager.IMPORTANCE_MIN:
   1500                     return "MIN";
   1501                 case NotificationManager.IMPORTANCE_LOW:
   1502                     return "LOW";
   1503                 case NotificationManager.IMPORTANCE_DEFAULT:
   1504                     return "DEFAULT";
   1505                 case NotificationManager.IMPORTANCE_HIGH:
   1506                 case NotificationManager.IMPORTANCE_MAX:
   1507                     return "HIGH";
   1508                 default:
   1509                     return "UNKNOWN(" + String.valueOf(importance) + ")";
   1510             }
   1511         }
   1512     }
   1513 
   1514     /**
   1515      * Provides access to ranking information on currently active
   1516      * notifications.
   1517      *
   1518      * <p>
   1519      * Note that this object represents a ranking snapshot that only applies to
   1520      * notifications active at the time of retrieval.
   1521      */
   1522     public static class RankingMap implements Parcelable {
   1523         private final NotificationRankingUpdate mRankingUpdate;
   1524         private ArrayMap<String,Integer> mRanks;
   1525         private ArraySet<Object> mIntercepted;
   1526         private ArrayMap<String, Integer> mVisibilityOverrides;
   1527         private ArrayMap<String, Integer> mSuppressedVisualEffects;
   1528         private ArrayMap<String, Integer> mImportance;
   1529         private ArrayMap<String, String> mImportanceExplanation;
   1530         private ArrayMap<String, String> mOverrideGroupKeys;
   1531         private ArrayMap<String, NotificationChannel> mChannels;
   1532         private ArrayMap<String, ArrayList<String>> mOverridePeople;
   1533         private ArrayMap<String, ArrayList<SnoozeCriterion>> mSnoozeCriteria;
   1534         private ArrayMap<String, Boolean> mShowBadge;
   1535 
   1536         private RankingMap(NotificationRankingUpdate rankingUpdate) {
   1537             mRankingUpdate = rankingUpdate;
   1538         }
   1539 
   1540         /**
   1541          * Request the list of notification keys in their current ranking
   1542          * order.
   1543          *
   1544          * @return An array of active notification keys, in their ranking order.
   1545          */
   1546         public String[] getOrderedKeys() {
   1547             return mRankingUpdate.getOrderedKeys();
   1548         }
   1549 
   1550         /**
   1551          * Populates outRanking with ranking information for the notification
   1552          * with the given key.
   1553          *
   1554          * @return true if a valid key has been passed and outRanking has
   1555          *     been populated; false otherwise
   1556          */
   1557         public boolean getRanking(String key, Ranking outRanking) {
   1558             int rank = getRank(key);
   1559             outRanking.populate(key, rank, !isIntercepted(key),
   1560                     getVisibilityOverride(key), getSuppressedVisualEffects(key),
   1561                     getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
   1562                     getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
   1563                     getShowBadge(key));
   1564             return rank >= 0;
   1565         }
   1566 
   1567         private int getRank(String key) {
   1568             synchronized (this) {
   1569                 if (mRanks == null) {
   1570                     buildRanksLocked();
   1571                 }
   1572             }
   1573             Integer rank = mRanks.get(key);
   1574             return rank != null ? rank : -1;
   1575         }
   1576 
   1577         private boolean isIntercepted(String key) {
   1578             synchronized (this) {
   1579                 if (mIntercepted == null) {
   1580                     buildInterceptedSetLocked();
   1581                 }
   1582             }
   1583             return mIntercepted.contains(key);
   1584         }
   1585 
   1586         private int getVisibilityOverride(String key) {
   1587             synchronized (this) {
   1588                 if (mVisibilityOverrides == null) {
   1589                     buildVisibilityOverridesLocked();
   1590                 }
   1591             }
   1592             Integer override = mVisibilityOverrides.get(key);
   1593             if (override == null) {
   1594                 return Ranking.VISIBILITY_NO_OVERRIDE;
   1595             }
   1596             return override.intValue();
   1597         }
   1598 
   1599         private int getSuppressedVisualEffects(String key) {
   1600             synchronized (this) {
   1601                 if (mSuppressedVisualEffects == null) {
   1602                     buildSuppressedVisualEffectsLocked();
   1603                 }
   1604             }
   1605             Integer suppressed = mSuppressedVisualEffects.get(key);
   1606             if (suppressed == null) {
   1607                 return 0;
   1608             }
   1609             return suppressed.intValue();
   1610         }
   1611 
   1612         private int getImportance(String key) {
   1613             synchronized (this) {
   1614                 if (mImportance == null) {
   1615                     buildImportanceLocked();
   1616                 }
   1617             }
   1618             Integer importance = mImportance.get(key);
   1619             if (importance == null) {
   1620                 return NotificationManager.IMPORTANCE_DEFAULT;
   1621             }
   1622             return importance.intValue();
   1623         }
   1624 
   1625         private String getImportanceExplanation(String key) {
   1626             synchronized (this) {
   1627                 if (mImportanceExplanation == null) {
   1628                     buildImportanceExplanationLocked();
   1629                 }
   1630             }
   1631             return mImportanceExplanation.get(key);
   1632         }
   1633 
   1634         private String getOverrideGroupKey(String key) {
   1635             synchronized (this) {
   1636                 if (mOverrideGroupKeys == null) {
   1637                     buildOverrideGroupKeys();
   1638                 }
   1639             }
   1640             return mOverrideGroupKeys.get(key);
   1641         }
   1642 
   1643         private NotificationChannel getChannel(String key) {
   1644             synchronized (this) {
   1645                 if (mChannels == null) {
   1646                     buildChannelsLocked();
   1647                 }
   1648             }
   1649             return mChannels.get(key);
   1650         }
   1651 
   1652         private ArrayList<String> getOverridePeople(String key) {
   1653             synchronized (this) {
   1654                 if (mOverridePeople == null) {
   1655                     buildOverridePeopleLocked();
   1656                 }
   1657             }
   1658             return mOverridePeople.get(key);
   1659         }
   1660 
   1661         private ArrayList<SnoozeCriterion> getSnoozeCriteria(String key) {
   1662             synchronized (this) {
   1663                 if (mSnoozeCriteria == null) {
   1664                     buildSnoozeCriteriaLocked();
   1665                 }
   1666             }
   1667             return mSnoozeCriteria.get(key);
   1668         }
   1669 
   1670         private boolean getShowBadge(String key) {
   1671             synchronized (this) {
   1672                 if (mShowBadge == null) {
   1673                     buildShowBadgeLocked();
   1674                 }
   1675             }
   1676             Boolean showBadge = mShowBadge.get(key);
   1677             return showBadge == null ? false : showBadge.booleanValue();
   1678         }
   1679 
   1680         // Locked by 'this'
   1681         private void buildRanksLocked() {
   1682             String[] orderedKeys = mRankingUpdate.getOrderedKeys();
   1683             mRanks = new ArrayMap<>(orderedKeys.length);
   1684             for (int i = 0; i < orderedKeys.length; i++) {
   1685                 String key = orderedKeys[i];
   1686                 mRanks.put(key, i);
   1687             }
   1688         }
   1689 
   1690         // Locked by 'this'
   1691         private void buildInterceptedSetLocked() {
   1692             String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys();
   1693             mIntercepted = new ArraySet<>(dndInterceptedKeys.length);
   1694             Collections.addAll(mIntercepted, dndInterceptedKeys);
   1695         }
   1696 
   1697         // Locked by 'this'
   1698         private void buildVisibilityOverridesLocked() {
   1699             Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides();
   1700             mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size());
   1701             for (String key: visibilityBundle.keySet()) {
   1702                mVisibilityOverrides.put(key, visibilityBundle.getInt(key));
   1703             }
   1704         }
   1705 
   1706         // Locked by 'this'
   1707         private void buildSuppressedVisualEffectsLocked() {
   1708             Bundle suppressedBundle = mRankingUpdate.getSuppressedVisualEffects();
   1709             mSuppressedVisualEffects = new ArrayMap<>(suppressedBundle.size());
   1710             for (String key: suppressedBundle.keySet()) {
   1711                 mSuppressedVisualEffects.put(key, suppressedBundle.getInt(key));
   1712             }
   1713         }
   1714         // Locked by 'this'
   1715         private void buildImportanceLocked() {
   1716             String[] orderedKeys = mRankingUpdate.getOrderedKeys();
   1717             int[] importance = mRankingUpdate.getImportance();
   1718             mImportance = new ArrayMap<>(orderedKeys.length);
   1719             for (int i = 0; i < orderedKeys.length; i++) {
   1720                 String key = orderedKeys[i];
   1721                 mImportance.put(key, importance[i]);
   1722             }
   1723         }
   1724 
   1725         // Locked by 'this'
   1726         private void buildImportanceExplanationLocked() {
   1727             Bundle explanationBundle = mRankingUpdate.getImportanceExplanation();
   1728             mImportanceExplanation = new ArrayMap<>(explanationBundle.size());
   1729             for (String key: explanationBundle.keySet()) {
   1730                 mImportanceExplanation.put(key, explanationBundle.getString(key));
   1731             }
   1732         }
   1733 
   1734         // Locked by 'this'
   1735         private void buildOverrideGroupKeys() {
   1736             Bundle overrideGroupKeys = mRankingUpdate.getOverrideGroupKeys();
   1737             mOverrideGroupKeys = new ArrayMap<>(overrideGroupKeys.size());
   1738             for (String key: overrideGroupKeys.keySet()) {
   1739                 mOverrideGroupKeys.put(key, overrideGroupKeys.getString(key));
   1740             }
   1741         }
   1742 
   1743         // Locked by 'this'
   1744         private void buildChannelsLocked() {
   1745             Bundle channels = mRankingUpdate.getChannels();
   1746             mChannels = new ArrayMap<>(channels.size());
   1747             for (String key : channels.keySet()) {
   1748                 mChannels.put(key, channels.getParcelable(key));
   1749             }
   1750         }
   1751 
   1752         // Locked by 'this'
   1753         private void buildOverridePeopleLocked() {
   1754             Bundle overridePeople = mRankingUpdate.getOverridePeople();
   1755             mOverridePeople = new ArrayMap<>(overridePeople.size());
   1756             for (String key : overridePeople.keySet()) {
   1757                 mOverridePeople.put(key, overridePeople.getStringArrayList(key));
   1758             }
   1759         }
   1760 
   1761         // Locked by 'this'
   1762         private void buildSnoozeCriteriaLocked() {
   1763             Bundle snoozeCriteria = mRankingUpdate.getSnoozeCriteria();
   1764             mSnoozeCriteria = new ArrayMap<>(snoozeCriteria.size());
   1765             for (String key : snoozeCriteria.keySet()) {
   1766                 mSnoozeCriteria.put(key, snoozeCriteria.getParcelableArrayList(key));
   1767             }
   1768         }
   1769 
   1770         // Locked by 'this'
   1771         private void buildShowBadgeLocked() {
   1772             Bundle showBadge = mRankingUpdate.getShowBadge();
   1773             mShowBadge = new ArrayMap<>(showBadge.size());
   1774             for (String key : showBadge.keySet()) {
   1775                 mShowBadge.put(key, showBadge.getBoolean(key));
   1776             }
   1777         }
   1778 
   1779         // ----------- Parcelable
   1780 
   1781         @Override
   1782         public int describeContents() {
   1783             return 0;
   1784         }
   1785 
   1786         @Override
   1787         public void writeToParcel(Parcel dest, int flags) {
   1788             dest.writeParcelable(mRankingUpdate, flags);
   1789         }
   1790 
   1791         public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() {
   1792             @Override
   1793             public RankingMap createFromParcel(Parcel source) {
   1794                 NotificationRankingUpdate rankingUpdate = source.readParcelable(null);
   1795                 return new RankingMap(rankingUpdate);
   1796             }
   1797 
   1798             @Override
   1799             public RankingMap[] newArray(int size) {
   1800                 return new RankingMap[size];
   1801             }
   1802         };
   1803     }
   1804 
   1805     private final class MyHandler extends Handler {
   1806         public static final int MSG_ON_NOTIFICATION_POSTED = 1;
   1807         public static final int MSG_ON_NOTIFICATION_REMOVED = 2;
   1808         public static final int MSG_ON_LISTENER_CONNECTED = 3;
   1809         public static final int MSG_ON_NOTIFICATION_RANKING_UPDATE = 4;
   1810         public static final int MSG_ON_LISTENER_HINTS_CHANGED = 5;
   1811         public static final int MSG_ON_INTERRUPTION_FILTER_CHANGED = 6;
   1812         public static final int MSG_ON_NOTIFICATION_CHANNEL_MODIFIED = 7;
   1813         public static final int MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED = 8;
   1814 
   1815         public MyHandler(Looper looper) {
   1816             super(looper, null, false);
   1817         }
   1818 
   1819         @Override
   1820         public void handleMessage(Message msg) {
   1821             if (!isConnected) {
   1822                 return;
   1823             }
   1824             switch (msg.what) {
   1825                 case MSG_ON_NOTIFICATION_POSTED: {
   1826                     SomeArgs args = (SomeArgs) msg.obj;
   1827                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
   1828                     RankingMap rankingMap = (RankingMap) args.arg2;
   1829                     args.recycle();
   1830                     onNotificationPosted(sbn, rankingMap);
   1831                 } break;
   1832 
   1833                 case MSG_ON_NOTIFICATION_REMOVED: {
   1834                     SomeArgs args = (SomeArgs) msg.obj;
   1835                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
   1836                     RankingMap rankingMap = (RankingMap) args.arg2;
   1837                     int reason = (int) args.arg3;
   1838                     args.recycle();
   1839                     onNotificationRemoved(sbn, rankingMap, reason);
   1840                 } break;
   1841 
   1842                 case MSG_ON_LISTENER_CONNECTED: {
   1843                     onListenerConnected();
   1844                 } break;
   1845 
   1846                 case MSG_ON_NOTIFICATION_RANKING_UPDATE: {
   1847                     RankingMap rankingMap = (RankingMap) msg.obj;
   1848                     onNotificationRankingUpdate(rankingMap);
   1849                 } break;
   1850 
   1851                 case MSG_ON_LISTENER_HINTS_CHANGED: {
   1852                     final int hints = msg.arg1;
   1853                     onListenerHintsChanged(hints);
   1854                 } break;
   1855 
   1856                 case MSG_ON_INTERRUPTION_FILTER_CHANGED: {
   1857                     final int interruptionFilter = msg.arg1;
   1858                     onInterruptionFilterChanged(interruptionFilter);
   1859                 } break;
   1860 
   1861                 case MSG_ON_NOTIFICATION_CHANNEL_MODIFIED: {
   1862                     SomeArgs args = (SomeArgs) msg.obj;
   1863                     String pkgName = (String) args.arg1;
   1864                     UserHandle user= (UserHandle) args.arg2;
   1865                     NotificationChannel channel = (NotificationChannel) args.arg3;
   1866                     int modificationType = (int) args.arg4;
   1867                     onNotificationChannelModified(pkgName, user, channel, modificationType);
   1868                 } break;
   1869 
   1870                 case MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED: {
   1871                     SomeArgs args = (SomeArgs) msg.obj;
   1872                     String pkgName = (String) args.arg1;
   1873                     UserHandle user = (UserHandle) args.arg2;
   1874                     NotificationChannelGroup group = (NotificationChannelGroup) args.arg3;
   1875                     int modificationType = (int) args.arg4;
   1876                     onNotificationChannelGroupModified(pkgName, user, group, modificationType);
   1877                 } break;
   1878             }
   1879         }
   1880     }
   1881 }
   1882