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.Manifest;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.TestApi;
23 import android.app.NotificationChannel;
24 import android.app.NotificationChannelGroup;
25 import android.companion.CompanionDeviceManager;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.os.Message;
29 
30 import android.annotation.SystemApi;
31 import android.annotation.SdkConstant;
32 import android.app.INotificationManager;
33 import android.app.Notification;
34 import android.app.Notification.Builder;
35 import android.app.NotificationManager;
36 import android.app.Service;
37 import android.content.ComponentName;
38 import android.content.Context;
39 import android.content.Intent;
40 import android.content.pm.ParceledListSlice;
41 import android.graphics.drawable.BitmapDrawable;
42 import android.graphics.drawable.Drawable;
43 import android.graphics.drawable.Icon;
44 import android.graphics.Bitmap;
45 import android.os.Build;
46 import android.os.Bundle;
47 import android.os.IBinder;
48 import android.os.Parcel;
49 import android.os.Parcelable;
50 import android.os.RemoteException;
51 import android.os.ServiceManager;
52 import android.os.UserHandle;
53 import android.util.ArrayMap;
54 import android.util.ArraySet;
55 import android.util.Log;
56 import android.widget.RemoteViews;
57 import com.android.internal.annotations.GuardedBy;
58 import com.android.internal.os.SomeArgs;
59 
60 import java.lang.annotation.Retention;
61 import java.lang.annotation.RetentionPolicy;
62 import java.util.ArrayList;
63 import java.util.Collections;
64 import java.util.List;
65 
66 /**
67  * A service that receives calls from the system when new notifications are
68  * posted or removed, or their ranking changed.
69  * <p>To extend this class, you must declare the service in your manifest file with
70  * the {@link Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission
71  * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
72  * <pre>
73  * &lt;service android:name=".NotificationListener"
74  *          android:label="&#64;string/service_name"
75  *          android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
76  *     &lt;intent-filter>
77  *         &lt;action android:name="android.service.notification.NotificationListenerService" />
78  *     &lt;/intent-filter>
79  * &lt;/service></pre>
80  *
81  * <p>The service should wait for the {@link #onListenerConnected()} event
82  * before performing any operations. The {@link #requestRebind(ComponentName)}
83  * method is the <i>only</i> one that is safe to call before {@link #onListenerConnected()}
84  * or after {@link #onListenerDisconnected()}.
85  * </p>
86  */
87 public abstract class NotificationListenerService extends Service {
88 
89     private final String TAG = getClass().getSimpleName();
90 
91     /**
92      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
93      *     Normal interruption filter.
94      */
95     public static final int INTERRUPTION_FILTER_ALL
96             = NotificationManager.INTERRUPTION_FILTER_ALL;
97 
98     /**
99      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
100      *     Priority interruption filter.
101      */
102     public static final int INTERRUPTION_FILTER_PRIORITY
103             = NotificationManager.INTERRUPTION_FILTER_PRIORITY;
104 
105     /**
106      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
107      *     No interruptions filter.
108      */
109     public static final int INTERRUPTION_FILTER_NONE
110             = NotificationManager.INTERRUPTION_FILTER_NONE;
111 
112     /**
113      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
114      *     Alarms only interruption filter.
115      */
116     public static final int INTERRUPTION_FILTER_ALARMS
117             = NotificationManager.INTERRUPTION_FILTER_ALARMS;
118 
119     /** {@link #getCurrentInterruptionFilter() Interruption filter} constant - returned when
120      * the value is unavailable for any reason.  For example, before the notification listener
121      * is connected.
122      *
123      * {@see #onListenerConnected()}
124      */
125     public static final int INTERRUPTION_FILTER_UNKNOWN
126             = NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
127 
128     /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
129      * should disable notification sound, vibrating and other visual or aural effects.
130      * This does not change the interruption filter, only the effects. **/
131     public static final int HINT_HOST_DISABLE_EFFECTS = 1;
132 
133     /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
134      * should disable notification sound, but not phone calls.
135      * This does not change the interruption filter, only the effects. **/
136     public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 1 << 1;
137 
138     /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
139      * should disable phone call sounds, buyt not notification sound.
140      * This does not change the interruption filter, only the effects. **/
141     public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 1 << 2;
142 
143     /**
144      * Whether notification suppressed by DND should not interruption visually when the screen is
145      * off.
146      */
147     public static final int SUPPRESSED_EFFECT_SCREEN_OFF =
148             NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
149     /**
150      * Whether notification suppressed by DND should not interruption visually when the screen is
151      * on.
152      */
153     public static final int SUPPRESSED_EFFECT_SCREEN_ON =
154             NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
155 
156 
157     // Notification cancellation reasons
158 
159     /** Notification was canceled by the status bar reporting a notification click. */
160     public static final int REASON_CLICK = 1;
161     /** Notification was canceled by the status bar reporting a user dismissal. */
162     public static final int REASON_CANCEL = 2;
163     /** Notification was canceled by the status bar reporting a user dismiss all. */
164     public static final int REASON_CANCEL_ALL = 3;
165     /** Notification was canceled by the status bar reporting an inflation error. */
166     public static final int REASON_ERROR = 4;
167     /** Notification was canceled by the package manager modifying the package. */
168     public static final int REASON_PACKAGE_CHANGED = 5;
169     /** Notification was canceled by the owning user context being stopped. */
170     public static final int REASON_USER_STOPPED = 6;
171     /** Notification was canceled by the user banning the package. */
172     public static final int REASON_PACKAGE_BANNED = 7;
173     /** Notification was canceled by the app canceling this specific notification. */
174     public static final int REASON_APP_CANCEL = 8;
175     /** Notification was canceled by the app cancelling all its notifications. */
176     public static final int REASON_APP_CANCEL_ALL = 9;
177     /** Notification was canceled by a listener reporting a user dismissal. */
178     public static final int REASON_LISTENER_CANCEL = 10;
179     /** Notification was canceled by a listener reporting a user dismiss all. */
180     public static final int REASON_LISTENER_CANCEL_ALL = 11;
181     /** Notification was canceled because it was a member of a canceled group. */
182     public static final int REASON_GROUP_SUMMARY_CANCELED = 12;
183     /** Notification was canceled because it was an invisible member of a group. */
184     public static final int REASON_GROUP_OPTIMIZATION = 13;
185     /** Notification was canceled by the device administrator suspending the package. */
186     public static final int REASON_PACKAGE_SUSPENDED = 14;
187     /** Notification was canceled by the owning managed profile being turned off. */
188     public static final int REASON_PROFILE_TURNED_OFF = 15;
189     /** Autobundled summary notification was canceled because its group was unbundled */
190     public static final int REASON_UNAUTOBUNDLED = 16;
191     /** Notification was canceled by the user banning the channel. */
192     public static final int REASON_CHANNEL_BANNED = 17;
193     /** Notification was snoozed. */
194     public static final int REASON_SNOOZED = 18;
195     /** Notification was canceled due to timeout */
196     public static final int REASON_TIMEOUT = 19;
197 
198     /**
199      * The full trim of the StatusBarNotification including all its features.
200      *
201      * @hide
202      */
203     @SystemApi
204     public static final int TRIM_FULL = 0;
205 
206     /**
207      * A light trim of the StatusBarNotification excluding the following features:
208      *
209      * <ol>
210      *     <li>{@link Notification#tickerView tickerView}</li>
211      *     <li>{@link Notification#contentView contentView}</li>
212      *     <li>{@link Notification#largeIcon largeIcon}</li>
213      *     <li>{@link Notification#bigContentView bigContentView}</li>
214      *     <li>{@link Notification#headsUpContentView headsUpContentView}</li>
215      *     <li>{@link Notification#EXTRA_LARGE_ICON extras[EXTRA_LARGE_ICON]}</li>
216      *     <li>{@link Notification#EXTRA_LARGE_ICON_BIG extras[EXTRA_LARGE_ICON_BIG]}</li>
217      *     <li>{@link Notification#EXTRA_PICTURE extras[EXTRA_PICTURE]}</li>
218      *     <li>{@link Notification#EXTRA_BIG_TEXT extras[EXTRA_BIG_TEXT]}</li>
219      * </ol>
220      *
221      * @hide
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
attachBaseContext(Context base)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      */
onNotificationPosted(StatusBarNotification sbn)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      */
onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap)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      */
onNotificationRemoved(StatusBarNotification sbn)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      */
onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap)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      */
onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason)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      */
onListenerConnected()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      */
onListenerDisconnected()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      */
onNotificationRankingUpdate(RankingMap rankingMap)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      */
onListenerHintsChanged(int hints)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      */
onNotificationChannelModified(String pkg, UserHandle user, NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType)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      */
onNotificationChannelGroupModified(String pkg, UserHandle user, NotificationChannelGroup group, @ChannelOrGroupModificationTypes int modificationType)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      */
onInterruptionFilterChanged(int interruptionFilter)472     public void onInterruptionFilterChanged(int interruptionFilter) {
473         // optional
474     }
475 
476     /** @hide */
getNotificationInterface()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
cancelNotification(String pkg, String tag, int id)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      */
cancelNotification(String key)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      */
cancelAllNotifications()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      */
cancelNotifications(String[] keys)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      */
609     @SystemApi
610     @TestApi
snoozeNotification(String key, String snoozeCriterionId)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      */
snoozeNotification(String key, long durationMs)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      */
setNotificationsShown(String[] keys)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      */
updateNotificationChannel(@onNull String pkg, @NonNull UserHandle user, @NonNull NotificationChannel channel)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      */
getNotificationChannels(@onNull String pkg, @NonNull UserHandle user)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      */
getNotificationChannelGroups(@onNull String pkg, @NonNull UserHandle user)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      *
753      * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}.
754      *             See <code>TRIM_*</code> constants.
755      */
756     @SystemApi
setOnNotificationPostedTrim(int trim)757     public final void setOnNotificationPostedTrim(int trim) {
758         if (!isBound()) return;
759         try {
760             getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim);
761         } catch (RemoteException ex) {
762             Log.v(TAG, "Unable to contact notification manager", ex);
763         }
764     }
765 
766     /**
767      * Request the list of outstanding notifications (that is, those that are visible to the
768      * current user). Useful when you don't know what's already been posted.
769      *
770      * <p>The service should wait for the {@link #onListenerConnected()} event
771      * before performing this operation.
772      *
773      * @return An array of active notifications, sorted in natural order.
774      */
getActiveNotifications()775     public StatusBarNotification[] getActiveNotifications() {
776         return getActiveNotifications(null, TRIM_FULL);
777     }
778 
779     /**
780      * Like {@link #getActiveNotifications()}, but returns the list of currently snoozed
781      * notifications, for all users this listener has access to.
782      *
783      * <p>The service should wait for the {@link #onListenerConnected()} event
784      * before performing this operation.
785      *
786      * @return An array of snoozed notifications, sorted in natural order.
787      */
getSnoozedNotifications()788     public final StatusBarNotification[] getSnoozedNotifications() {
789         try {
790             ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
791                     .getSnoozedNotificationsFromListener(mWrapper, TRIM_FULL);
792             return cleanUpNotificationList(parceledList);
793         } catch (android.os.RemoteException ex) {
794             Log.v(TAG, "Unable to contact notification manager", ex);
795         }
796         return null;
797     }
798 
799     /**
800      * Request the list of outstanding notifications (that is, those that are visible to the
801      * current user). Useful when you don't know what's already been posted.
802      *
803      * @hide
804      *
805      * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
806      * @return An array of active notifications, sorted in natural order.
807      */
808     @SystemApi
getActiveNotifications(int trim)809     public StatusBarNotification[] getActiveNotifications(int trim) {
810         return getActiveNotifications(null, trim);
811     }
812 
813     /**
814      * Request one or more notifications by key. Useful if you have been keeping track of
815      * notifications but didn't want to retain the bits, and now need to go back and extract
816      * more data out of those notifications.
817      *
818      * <p>The service should wait for the {@link #onListenerConnected()} event
819      * before performing this operation.
820      *
821      * @param keys the keys of the notifications to request
822      * @return An array of notifications corresponding to the requested keys, in the
823      * same order as the key list.
824      */
getActiveNotifications(String[] keys)825     public StatusBarNotification[] getActiveNotifications(String[] keys) {
826         return getActiveNotifications(keys, TRIM_FULL);
827     }
828 
829     /**
830      * Request one or more notifications by key. Useful if you have been keeping track of
831      * notifications but didn't want to retain the bits, and now need to go back and extract
832      * more data out of those notifications.
833      *
834      * @hide
835      *
836      * @param keys the keys of the notifications to request
837      * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
838      * @return An array of notifications corresponding to the requested keys, in the
839      * same order as the key list.
840      */
841     @SystemApi
getActiveNotifications(String[] keys, int trim)842     public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) {
843         if (!isBound())
844             return null;
845         try {
846             ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
847                     .getActiveNotificationsFromListener(mWrapper, keys, trim);
848             return cleanUpNotificationList(parceledList);
849         } catch (android.os.RemoteException ex) {
850             Log.v(TAG, "Unable to contact notification manager", ex);
851         }
852         return null;
853     }
854 
cleanUpNotificationList( ParceledListSlice<StatusBarNotification> parceledList)855     private StatusBarNotification[] cleanUpNotificationList(
856             ParceledListSlice<StatusBarNotification> parceledList) {
857         List<StatusBarNotification> list = parceledList.getList();
858         ArrayList<StatusBarNotification> corruptNotifications = null;
859         int N = list.size();
860         for (int i = 0; i < N; i++) {
861             StatusBarNotification sbn = list.get(i);
862             Notification notification = sbn.getNotification();
863             try {
864                 // convert icon metadata to legacy format for older clients
865                 createLegacyIconExtras(notification);
866                 // populate remote views for older clients.
867                 maybePopulateRemoteViews(notification);
868             } catch (IllegalArgumentException e) {
869                 if (corruptNotifications == null) {
870                     corruptNotifications = new ArrayList<>(N);
871                 }
872                 corruptNotifications.add(sbn);
873                 Log.w(TAG, "get(Active/Snoozed)Notifications: can't rebuild notification from " +
874                         sbn.getPackageName());
875             }
876         }
877         if (corruptNotifications != null) {
878             list.removeAll(corruptNotifications);
879         }
880         return list.toArray(new StatusBarNotification[list.size()]);
881     }
882 
883     /**
884      * Gets the set of hints representing current state.
885      *
886      * <p>
887      * The current state may differ from the requested state if the hint represents state
888      * shared across all listeners or a feature the notification host does not support or refuses
889      * to grant.
890      *
891      * <p>The service should wait for the {@link #onListenerConnected()} event
892      * before performing this operation.
893      *
894      * @return Zero or more of the HINT_ constants.
895      */
getCurrentListenerHints()896     public final int getCurrentListenerHints() {
897         if (!isBound()) return 0;
898         try {
899             return getNotificationInterface().getHintsFromListener(mWrapper);
900         } catch (android.os.RemoteException ex) {
901             Log.v(TAG, "Unable to contact notification manager", ex);
902             return 0;
903         }
904     }
905 
906     /**
907      * Gets the current notification interruption filter active on the host.
908      *
909      * <p>
910      * The interruption filter defines which notifications are allowed to interrupt the user
911      * (e.g. via sound &amp; vibration) and is applied globally. Listeners can find out whether
912      * a specific notification matched the interruption filter via
913      * {@link Ranking#matchesInterruptionFilter()}.
914      * <p>
915      * The current filter may differ from the previously requested filter if the notification host
916      * does not support or refuses to apply the requested filter, or if another component changed
917      * the filter in the meantime.
918      * <p>
919      * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
920      *
921      * <p>The service should wait for the {@link #onListenerConnected()} event
922      * before performing this operation.
923      *
924      * @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when
925      * unavailable.
926      */
getCurrentInterruptionFilter()927     public final int getCurrentInterruptionFilter() {
928         if (!isBound()) return INTERRUPTION_FILTER_UNKNOWN;
929         try {
930             return getNotificationInterface().getInterruptionFilterFromListener(mWrapper);
931         } catch (android.os.RemoteException ex) {
932             Log.v(TAG, "Unable to contact notification manager", ex);
933             return INTERRUPTION_FILTER_UNKNOWN;
934         }
935     }
936 
937     /**
938      * Sets the desired {@link #getCurrentListenerHints() listener hints}.
939      *
940      * <p>
941      * This is merely a request, the host may or may not choose to take action depending
942      * on other listener requests or other global state.
943      * <p>
944      * Listen for updates using {@link #onListenerHintsChanged(int)}.
945      *
946      * <p>The service should wait for the {@link #onListenerConnected()} event
947      * before performing this operation.
948      *
949      * @param hints One or more of the HINT_ constants.
950      */
requestListenerHints(int hints)951     public final void requestListenerHints(int hints) {
952         if (!isBound()) return;
953         try {
954             getNotificationInterface().requestHintsFromListener(mWrapper, hints);
955         } catch (android.os.RemoteException ex) {
956             Log.v(TAG, "Unable to contact notification manager", ex);
957         }
958     }
959 
960     /**
961      * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}.
962      *
963      * <p>
964      * This is merely a request, the host may or may not choose to apply the requested
965      * interruption filter depending on other listener requests or other global state.
966      * <p>
967      * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
968      *
969      * <p>The service should wait for the {@link #onListenerConnected()} event
970      * before performing this operation.
971      *
972      * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants.
973      */
requestInterruptionFilter(int interruptionFilter)974     public final void requestInterruptionFilter(int interruptionFilter) {
975         if (!isBound()) return;
976         try {
977             getNotificationInterface()
978                     .requestInterruptionFilterFromListener(mWrapper, interruptionFilter);
979         } catch (android.os.RemoteException ex) {
980             Log.v(TAG, "Unable to contact notification manager", ex);
981         }
982     }
983 
984     /**
985      * Returns current ranking information.
986      *
987      * <p>
988      * The returned object represents the current ranking snapshot and only
989      * applies for currently active notifications.
990      * <p>
991      * Generally you should use the RankingMap that is passed with events such
992      * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)},
993      * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and
994      * so on. This method should only be used when needing access outside of
995      * such events, for example to retrieve the RankingMap right after
996      * initialization.
997      *
998      * <p>The service should wait for the {@link #onListenerConnected()} event
999      * before performing this operation.
1000      *
1001      * @return A {@link RankingMap} object providing access to ranking information
1002      */
getCurrentRanking()1003     public RankingMap getCurrentRanking() {
1004         synchronized (mLock) {
1005             return mRankingMap;
1006         }
1007     }
1008 
1009     /**
1010      * This is not the lifecycle event you are looking for.
1011      *
1012      * <p>The service should wait for the {@link #onListenerConnected()} event
1013      * before performing any operations.
1014      */
1015     @Override
onBind(Intent intent)1016     public IBinder onBind(Intent intent) {
1017         if (mWrapper == null) {
1018             mWrapper = new NotificationListenerWrapper();
1019         }
1020         return mWrapper;
1021     }
1022 
1023     /** @hide */
isBound()1024     protected boolean isBound() {
1025         if (mWrapper == null) {
1026             Log.w(TAG, "Notification listener service not yet bound.");
1027             return false;
1028         }
1029         return true;
1030     }
1031 
1032     @Override
onDestroy()1033     public void onDestroy() {
1034         onListenerDisconnected();
1035         super.onDestroy();
1036     }
1037 
1038     /**
1039      * Directly register this service with the Notification Manager.
1040      *
1041      * <p>Only system services may use this call. It will fail for non-system callers.
1042      * Apps should ask the user to add their listener in Settings.
1043      *
1044      * @param context Context required for accessing resources. Since this service isn't
1045      *    launched as a real Service when using this method, a context has to be passed in.
1046      * @param componentName the component that will consume the notification information
1047      * @param currentUser the user to use as the stream filter
1048      * @hide
1049      */
1050     @SystemApi
registerAsSystemService(Context context, ComponentName componentName, int currentUser)1051     public void registerAsSystemService(Context context, ComponentName componentName,
1052             int currentUser) throws RemoteException {
1053         if (mWrapper == null) {
1054             mWrapper = new NotificationListenerWrapper();
1055         }
1056         mSystemContext = context;
1057         INotificationManager noMan = getNotificationInterface();
1058         mHandler = new MyHandler(context.getMainLooper());
1059         mCurrentUser = currentUser;
1060         noMan.registerListener(mWrapper, componentName, currentUser);
1061     }
1062 
1063     /**
1064      * Directly unregister this service from the Notification Manager.
1065      *
1066      * <p>This method will fail for listeners that were not registered
1067      * with (@link registerAsService).
1068      * @hide
1069      */
1070     @SystemApi
unregisterAsSystemService()1071     public void unregisterAsSystemService() throws RemoteException {
1072         if (mWrapper != null) {
1073             INotificationManager noMan = getNotificationInterface();
1074             noMan.unregisterListener(mWrapper, mCurrentUser);
1075         }
1076     }
1077 
1078     /**
1079      * Request that the listener be rebound, after a previous call to {@link #requestUnbind}.
1080      *
1081      * <p>This method will fail for listeners that have
1082      * not been granted the permission by the user.
1083      */
requestRebind(ComponentName componentName)1084     public static void requestRebind(ComponentName componentName) {
1085         INotificationManager noMan = INotificationManager.Stub.asInterface(
1086                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
1087         try {
1088             noMan.requestBindListener(componentName);
1089         } catch (RemoteException ex) {
1090             throw ex.rethrowFromSystemServer();
1091         }
1092     }
1093 
1094     /**
1095      * Request that the service be unbound.
1096      *
1097      * <p>Once this is called, you will no longer receive updates and no method calls are
1098      * guaranteed to be successful, until you next receive the {@link #onListenerConnected()} event.
1099      * The service will likely be killed by the system after this call.
1100      *
1101      * <p>The service should wait for the {@link #onListenerConnected()} event
1102      * before performing this operation. I know it's tempting, but you must wait.
1103      */
requestUnbind()1104     public final void requestUnbind() {
1105         if (mWrapper != null) {
1106             INotificationManager noMan = getNotificationInterface();
1107             try {
1108                 noMan.requestUnbindListener(mWrapper);
1109                 // Disable future messages.
1110                 isConnected = false;
1111             } catch (RemoteException ex) {
1112                 throw ex.rethrowFromSystemServer();
1113             }
1114         }
1115     }
1116 
1117     /** Convert new-style Icons to legacy representations for pre-M clients. */
createLegacyIconExtras(Notification n)1118     private void createLegacyIconExtras(Notification n) {
1119         Icon smallIcon = n.getSmallIcon();
1120         Icon largeIcon = n.getLargeIcon();
1121         if (smallIcon != null && smallIcon.getType() == Icon.TYPE_RESOURCE) {
1122             n.extras.putInt(Notification.EXTRA_SMALL_ICON, smallIcon.getResId());
1123             n.icon = smallIcon.getResId();
1124         }
1125         if (largeIcon != null) {
1126             Drawable d = largeIcon.loadDrawable(getContext());
1127             if (d != null && d instanceof BitmapDrawable) {
1128                 final Bitmap largeIconBits = ((BitmapDrawable) d).getBitmap();
1129                 n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, largeIconBits);
1130                 n.largeIcon = largeIconBits;
1131             }
1132         }
1133     }
1134 
1135     /**
1136      * Populates remote views for pre-N targeting apps.
1137      */
maybePopulateRemoteViews(Notification notification)1138     private void maybePopulateRemoteViews(Notification notification) {
1139         if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
1140             Builder builder = Builder.recoverBuilder(getContext(), notification);
1141 
1142             // Some styles wrap Notification's contentView, bigContentView and headsUpContentView.
1143             // First inflate them all, only then set them to avoid recursive wrapping.
1144             RemoteViews content = builder.createContentView();
1145             RemoteViews big = builder.createBigContentView();
1146             RemoteViews headsUp = builder.createHeadsUpContentView();
1147 
1148             notification.contentView = content;
1149             notification.bigContentView = big;
1150             notification.headsUpContentView = headsUp;
1151         }
1152     }
1153 
1154     /** @hide */
1155     protected class NotificationListenerWrapper extends INotificationListener.Stub {
1156         @Override
onNotificationPosted(IStatusBarNotificationHolder sbnHolder, NotificationRankingUpdate update)1157         public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
1158                 NotificationRankingUpdate update) {
1159             StatusBarNotification sbn;
1160             try {
1161                 sbn = sbnHolder.get();
1162             } catch (RemoteException e) {
1163                 Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
1164                 return;
1165             }
1166 
1167             try {
1168                 // convert icon metadata to legacy format for older clients
1169                 createLegacyIconExtras(sbn.getNotification());
1170                 maybePopulateRemoteViews(sbn.getNotification());
1171             } catch (IllegalArgumentException e) {
1172                 // warn and drop corrupt notification
1173                 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
1174                         sbn.getPackageName());
1175                 sbn = null;
1176             }
1177 
1178             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
1179             synchronized (mLock) {
1180                 applyUpdateLocked(update);
1181                 if (sbn != null) {
1182                     SomeArgs args = SomeArgs.obtain();
1183                     args.arg1 = sbn;
1184                     args.arg2 = mRankingMap;
1185                     mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
1186                             args).sendToTarget();
1187                 } else {
1188                     // still pass along the ranking map, it may contain other information
1189                     mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
1190                             mRankingMap).sendToTarget();
1191                 }
1192             }
1193 
1194         }
1195 
1196         @Override
onNotificationRemoved(IStatusBarNotificationHolder sbnHolder, NotificationRankingUpdate update, int reason)1197         public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
1198                 NotificationRankingUpdate update, int reason) {
1199             StatusBarNotification sbn;
1200             try {
1201                 sbn = sbnHolder.get();
1202             } catch (RemoteException e) {
1203                 Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e);
1204                 return;
1205             }
1206             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
1207             synchronized (mLock) {
1208                 applyUpdateLocked(update);
1209                 SomeArgs args = SomeArgs.obtain();
1210                 args.arg1 = sbn;
1211                 args.arg2 = mRankingMap;
1212                 args.arg3 = reason;
1213                 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED,
1214                         args).sendToTarget();
1215             }
1216 
1217         }
1218 
1219         @Override
onListenerConnected(NotificationRankingUpdate update)1220         public void onListenerConnected(NotificationRankingUpdate update) {
1221             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
1222             synchronized (mLock) {
1223                 applyUpdateLocked(update);
1224             }
1225             isConnected = true;
1226             mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_CONNECTED).sendToTarget();
1227         }
1228 
1229         @Override
onNotificationRankingUpdate(NotificationRankingUpdate update)1230         public void onNotificationRankingUpdate(NotificationRankingUpdate update)
1231                 throws RemoteException {
1232             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
1233             synchronized (mLock) {
1234                 applyUpdateLocked(update);
1235                 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
1236                         mRankingMap).sendToTarget();
1237             }
1238 
1239         }
1240 
1241         @Override
onListenerHintsChanged(int hints)1242         public void onListenerHintsChanged(int hints) throws RemoteException {
1243             mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_HINTS_CHANGED,
1244                     hints, 0).sendToTarget();
1245         }
1246 
1247         @Override
onInterruptionFilterChanged(int interruptionFilter)1248         public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException {
1249             mHandler.obtainMessage(MyHandler.MSG_ON_INTERRUPTION_FILTER_CHANGED,
1250                     interruptionFilter, 0).sendToTarget();
1251         }
1252 
1253         @Override
onNotificationEnqueued(IStatusBarNotificationHolder notificationHolder)1254         public void onNotificationEnqueued(IStatusBarNotificationHolder notificationHolder)
1255                 throws RemoteException {
1256             // no-op in the listener
1257         }
1258 
1259         @Override
onNotificationSnoozedUntilContext( IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId)1260         public void onNotificationSnoozedUntilContext(
1261                 IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId)
1262                 throws RemoteException {
1263             // no-op in the listener
1264         }
1265 
1266         @Override
onNotificationChannelModification(String pkgName, UserHandle user, NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType)1267         public void onNotificationChannelModification(String pkgName, UserHandle user,
1268                 NotificationChannel channel,
1269                 @ChannelOrGroupModificationTypes int modificationType) {
1270             SomeArgs args = SomeArgs.obtain();
1271             args.arg1 = pkgName;
1272             args.arg2 = user;
1273             args.arg3 = channel;
1274             args.arg4 = modificationType;
1275             mHandler.obtainMessage(
1276                     MyHandler.MSG_ON_NOTIFICATION_CHANNEL_MODIFIED, args).sendToTarget();
1277         }
1278 
1279         @Override
onNotificationChannelGroupModification(String pkgName, UserHandle user, NotificationChannelGroup group, @ChannelOrGroupModificationTypes int modificationType)1280         public void onNotificationChannelGroupModification(String pkgName, UserHandle user,
1281                 NotificationChannelGroup group,
1282                 @ChannelOrGroupModificationTypes int modificationType) {
1283             SomeArgs args = SomeArgs.obtain();
1284             args.arg1 = pkgName;
1285             args.arg2 = user;
1286             args.arg3 = group;
1287             args.arg4 = modificationType;
1288             mHandler.obtainMessage(
1289                     MyHandler.MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED, args).sendToTarget();
1290         }
1291     }
1292 
1293     /**
1294      * @hide
1295      */
applyUpdateLocked(NotificationRankingUpdate update)1296     public final void applyUpdateLocked(NotificationRankingUpdate update) {
1297         mRankingMap = new RankingMap(update);
1298     }
1299 
1300     /** @hide */
getContext()1301     protected Context getContext() {
1302         if (mSystemContext != null) {
1303             return mSystemContext;
1304         }
1305         return this;
1306     }
1307 
1308     /**
1309      * Stores ranking related information on a currently active notification.
1310      *
1311      * <p>
1312      * Ranking objects aren't automatically updated as notification events
1313      * occur. Instead, ranking information has to be retrieved again via the
1314      * current {@link RankingMap}.
1315      */
1316     public static class Ranking {
1317 
1318         /** Value signifying that the user has not expressed a per-app visibility override value.
1319          * @hide */
1320         public static final int VISIBILITY_NO_OVERRIDE = NotificationManager.VISIBILITY_NO_OVERRIDE;
1321 
1322         private String mKey;
1323         private int mRank = -1;
1324         private boolean mIsAmbient;
1325         private boolean mMatchesInterruptionFilter;
1326         private int mVisibilityOverride;
1327         private int mSuppressedVisualEffects;
1328         private @NotificationManager.Importance int mImportance;
1329         private CharSequence mImportanceExplanation;
1330         // System specified group key.
1331         private String mOverrideGroupKey;
1332         // Notification assistant channel override.
1333         private NotificationChannel mChannel;
1334         // Notification assistant people override.
1335         private ArrayList<String> mOverridePeople;
1336         // Notification assistant snooze criteria.
1337         private ArrayList<SnoozeCriterion> mSnoozeCriteria;
1338         private boolean mShowBadge;
1339 
Ranking()1340         public Ranking() {}
1341 
1342         /**
1343          * Returns the key of the notification this Ranking applies to.
1344          */
getKey()1345         public String getKey() {
1346             return mKey;
1347         }
1348 
1349         /**
1350          * Returns the rank of the notification.
1351          *
1352          * @return the rank of the notification, that is the 0-based index in
1353          *     the list of active notifications.
1354          */
getRank()1355         public int getRank() {
1356             return mRank;
1357         }
1358 
1359         /**
1360          * Returns whether the notification is an ambient notification, that is
1361          * a notification that doesn't require the user's immediate attention.
1362          */
isAmbient()1363         public boolean isAmbient() {
1364             return mIsAmbient;
1365         }
1366 
1367         /**
1368          * Returns the user specified visibility for the package that posted
1369          * this notification, or
1370          * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
1371          * no such preference has been expressed.
1372          * @hide
1373          */
getVisibilityOverride()1374         public int getVisibilityOverride() {
1375             return mVisibilityOverride;
1376         }
1377 
1378         /**
1379          * Returns the type(s) of visual effects that should be suppressed for this notification.
1380          * See {@link #SUPPRESSED_EFFECT_SCREEN_OFF}, {@link #SUPPRESSED_EFFECT_SCREEN_ON}.
1381          */
getSuppressedVisualEffects()1382         public int getSuppressedVisualEffects() {
1383             return mSuppressedVisualEffects;
1384         }
1385 
1386         /**
1387          * Returns whether the notification matches the user's interruption
1388          * filter.
1389          *
1390          * @return {@code true} if the notification is allowed by the filter, or
1391          * {@code false} if it is blocked.
1392          */
matchesInterruptionFilter()1393         public boolean matchesInterruptionFilter() {
1394             return mMatchesInterruptionFilter;
1395         }
1396 
1397         /**
1398          * Returns the importance of the notification, which dictates its
1399          * modes of presentation, see: {@link NotificationManager#IMPORTANCE_DEFAULT}, etc.
1400          *
1401          * @return the importance of the notification
1402          */
getImportance()1403         public @NotificationManager.Importance int getImportance() {
1404             return mImportance;
1405         }
1406 
1407         /**
1408          * If the importance has been overridden by user preference, then this will be non-null,
1409          * and should be displayed to the user.
1410          *
1411          * @return the explanation for the importance, or null if it is the natural importance
1412          */
getImportanceExplanation()1413         public CharSequence getImportanceExplanation() {
1414             return mImportanceExplanation;
1415         }
1416 
1417         /**
1418          * If the system has overridden the group key, then this will be non-null, and this
1419          * key should be used to bundle notifications.
1420          */
getOverrideGroupKey()1421         public String getOverrideGroupKey() {
1422             return mOverrideGroupKey;
1423         }
1424 
1425         /**
1426          * Returns the notification channel this notification was posted to, which dictates
1427          * notification behavior and presentation.
1428          */
getChannel()1429         public NotificationChannel getChannel() {
1430             return mChannel;
1431         }
1432 
1433         /**
1434          * If the {@link NotificationAssistantService} has added people to this notification, then
1435          * this will be non-null.
1436          * @hide
1437          */
1438         @SystemApi
1439         @TestApi
getAdditionalPeople()1440         public List<String> getAdditionalPeople() {
1441             return mOverridePeople;
1442         }
1443 
1444         /**
1445          * Returns snooze criteria provided by the {@link NotificationAssistantService}. If your
1446          * user interface displays options for snoozing notifications these criteria should be
1447          * displayed as well.
1448          * @hide
1449          */
1450         @SystemApi
1451         @TestApi
getSnoozeCriteria()1452         public List<SnoozeCriterion> getSnoozeCriteria() {
1453             return mSnoozeCriteria;
1454         }
1455 
1456         /**
1457          * Returns whether this notification can be displayed as a badge.
1458          *
1459          * @return true if the notification can be displayed as a badge, false otherwise.
1460          */
canShowBadge()1461         public boolean canShowBadge() {
1462             return mShowBadge;
1463         }
1464 
populate(String key, int rank, boolean matchesInterruptionFilter, int visibilityOverride, int suppressedVisualEffects, int importance, CharSequence explanation, String overrideGroupKey, NotificationChannel channel, ArrayList<String> overridePeople, ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge)1465         private void populate(String key, int rank, boolean matchesInterruptionFilter,
1466                 int visibilityOverride, int suppressedVisualEffects, int importance,
1467                 CharSequence explanation, String overrideGroupKey,
1468                 NotificationChannel channel, ArrayList<String> overridePeople,
1469                 ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge) {
1470             mKey = key;
1471             mRank = rank;
1472             mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
1473             mMatchesInterruptionFilter = matchesInterruptionFilter;
1474             mVisibilityOverride = visibilityOverride;
1475             mSuppressedVisualEffects = suppressedVisualEffects;
1476             mImportance = importance;
1477             mImportanceExplanation = explanation;
1478             mOverrideGroupKey = overrideGroupKey;
1479             mChannel = channel;
1480             mOverridePeople = overridePeople;
1481             mSnoozeCriteria = snoozeCriteria;
1482             mShowBadge = showBadge;
1483         }
1484 
1485         /**
1486          * {@hide}
1487          */
1488         public static String importanceToString(int importance) {
1489             switch (importance) {
1490                 case NotificationManager.IMPORTANCE_UNSPECIFIED:
1491                     return "UNSPECIFIED";
1492                 case NotificationManager.IMPORTANCE_NONE:
1493                     return "NONE";
1494                 case NotificationManager.IMPORTANCE_MIN:
1495                     return "MIN";
1496                 case NotificationManager.IMPORTANCE_LOW:
1497                     return "LOW";
1498                 case NotificationManager.IMPORTANCE_DEFAULT:
1499                     return "DEFAULT";
1500                 case NotificationManager.IMPORTANCE_HIGH:
1501                 case NotificationManager.IMPORTANCE_MAX:
1502                     return "HIGH";
1503                 default:
1504                     return "UNKNOWN(" + String.valueOf(importance) + ")";
1505             }
1506         }
1507     }
1508 
1509     /**
1510      * Provides access to ranking information on currently active
1511      * notifications.
1512      *
1513      * <p>
1514      * Note that this object represents a ranking snapshot that only applies to
1515      * notifications active at the time of retrieval.
1516      */
1517     public static class RankingMap implements Parcelable {
1518         private final NotificationRankingUpdate mRankingUpdate;
1519         private ArrayMap<String,Integer> mRanks;
1520         private ArraySet<Object> mIntercepted;
1521         private ArrayMap<String, Integer> mVisibilityOverrides;
1522         private ArrayMap<String, Integer> mSuppressedVisualEffects;
1523         private ArrayMap<String, Integer> mImportance;
1524         private ArrayMap<String, String> mImportanceExplanation;
1525         private ArrayMap<String, String> mOverrideGroupKeys;
1526         private ArrayMap<String, NotificationChannel> mChannels;
1527         private ArrayMap<String, ArrayList<String>> mOverridePeople;
1528         private ArrayMap<String, ArrayList<SnoozeCriterion>> mSnoozeCriteria;
1529         private ArrayMap<String, Boolean> mShowBadge;
1530 
1531         private RankingMap(NotificationRankingUpdate rankingUpdate) {
1532             mRankingUpdate = rankingUpdate;
1533         }
1534 
1535         /**
1536          * Request the list of notification keys in their current ranking
1537          * order.
1538          *
1539          * @return An array of active notification keys, in their ranking order.
1540          */
1541         public String[] getOrderedKeys() {
1542             return mRankingUpdate.getOrderedKeys();
1543         }
1544 
1545         /**
1546          * Populates outRanking with ranking information for the notification
1547          * with the given key.
1548          *
1549          * @return true if a valid key has been passed and outRanking has
1550          *     been populated; false otherwise
1551          */
1552         public boolean getRanking(String key, Ranking outRanking) {
1553             int rank = getRank(key);
1554             outRanking.populate(key, rank, !isIntercepted(key),
1555                     getVisibilityOverride(key), getSuppressedVisualEffects(key),
1556                     getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
1557                     getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
1558                     getShowBadge(key));
1559             return rank >= 0;
1560         }
1561 
getRank(String key)1562         private int getRank(String key) {
1563             synchronized (this) {
1564                 if (mRanks == null) {
1565                     buildRanksLocked();
1566                 }
1567             }
1568             Integer rank = mRanks.get(key);
1569             return rank != null ? rank : -1;
1570         }
1571 
isIntercepted(String key)1572         private boolean isIntercepted(String key) {
1573             synchronized (this) {
1574                 if (mIntercepted == null) {
1575                     buildInterceptedSetLocked();
1576                 }
1577             }
1578             return mIntercepted.contains(key);
1579         }
1580 
getVisibilityOverride(String key)1581         private int getVisibilityOverride(String key) {
1582             synchronized (this) {
1583                 if (mVisibilityOverrides == null) {
1584                     buildVisibilityOverridesLocked();
1585                 }
1586             }
1587             Integer override = mVisibilityOverrides.get(key);
1588             if (override == null) {
1589                 return Ranking.VISIBILITY_NO_OVERRIDE;
1590             }
1591             return override.intValue();
1592         }
1593 
getSuppressedVisualEffects(String key)1594         private int getSuppressedVisualEffects(String key) {
1595             synchronized (this) {
1596                 if (mSuppressedVisualEffects == null) {
1597                     buildSuppressedVisualEffectsLocked();
1598                 }
1599             }
1600             Integer suppressed = mSuppressedVisualEffects.get(key);
1601             if (suppressed == null) {
1602                 return 0;
1603             }
1604             return suppressed.intValue();
1605         }
1606 
getImportance(String key)1607         private int getImportance(String key) {
1608             synchronized (this) {
1609                 if (mImportance == null) {
1610                     buildImportanceLocked();
1611                 }
1612             }
1613             Integer importance = mImportance.get(key);
1614             if (importance == null) {
1615                 return NotificationManager.IMPORTANCE_DEFAULT;
1616             }
1617             return importance.intValue();
1618         }
1619 
getImportanceExplanation(String key)1620         private String getImportanceExplanation(String key) {
1621             synchronized (this) {
1622                 if (mImportanceExplanation == null) {
1623                     buildImportanceExplanationLocked();
1624                 }
1625             }
1626             return mImportanceExplanation.get(key);
1627         }
1628 
getOverrideGroupKey(String key)1629         private String getOverrideGroupKey(String key) {
1630             synchronized (this) {
1631                 if (mOverrideGroupKeys == null) {
1632                     buildOverrideGroupKeys();
1633                 }
1634             }
1635             return mOverrideGroupKeys.get(key);
1636         }
1637 
getChannel(String key)1638         private NotificationChannel getChannel(String key) {
1639             synchronized (this) {
1640                 if (mChannels == null) {
1641                     buildChannelsLocked();
1642                 }
1643             }
1644             return mChannels.get(key);
1645         }
1646 
getOverridePeople(String key)1647         private ArrayList<String> getOverridePeople(String key) {
1648             synchronized (this) {
1649                 if (mOverridePeople == null) {
1650                     buildOverridePeopleLocked();
1651                 }
1652             }
1653             return mOverridePeople.get(key);
1654         }
1655 
getSnoozeCriteria(String key)1656         private ArrayList<SnoozeCriterion> getSnoozeCriteria(String key) {
1657             synchronized (this) {
1658                 if (mSnoozeCriteria == null) {
1659                     buildSnoozeCriteriaLocked();
1660                 }
1661             }
1662             return mSnoozeCriteria.get(key);
1663         }
1664 
getShowBadge(String key)1665         private boolean getShowBadge(String key) {
1666             synchronized (this) {
1667                 if (mShowBadge == null) {
1668                     buildShowBadgeLocked();
1669                 }
1670             }
1671             Boolean showBadge = mShowBadge.get(key);
1672             return showBadge == null ? false : showBadge.booleanValue();
1673         }
1674 
1675         // Locked by 'this'
buildRanksLocked()1676         private void buildRanksLocked() {
1677             String[] orderedKeys = mRankingUpdate.getOrderedKeys();
1678             mRanks = new ArrayMap<>(orderedKeys.length);
1679             for (int i = 0; i < orderedKeys.length; i++) {
1680                 String key = orderedKeys[i];
1681                 mRanks.put(key, i);
1682             }
1683         }
1684 
1685         // Locked by 'this'
buildInterceptedSetLocked()1686         private void buildInterceptedSetLocked() {
1687             String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys();
1688             mIntercepted = new ArraySet<>(dndInterceptedKeys.length);
1689             Collections.addAll(mIntercepted, dndInterceptedKeys);
1690         }
1691 
1692         // Locked by 'this'
buildVisibilityOverridesLocked()1693         private void buildVisibilityOverridesLocked() {
1694             Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides();
1695             mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size());
1696             for (String key: visibilityBundle.keySet()) {
1697                mVisibilityOverrides.put(key, visibilityBundle.getInt(key));
1698             }
1699         }
1700 
1701         // Locked by 'this'
buildSuppressedVisualEffectsLocked()1702         private void buildSuppressedVisualEffectsLocked() {
1703             Bundle suppressedBundle = mRankingUpdate.getSuppressedVisualEffects();
1704             mSuppressedVisualEffects = new ArrayMap<>(suppressedBundle.size());
1705             for (String key: suppressedBundle.keySet()) {
1706                 mSuppressedVisualEffects.put(key, suppressedBundle.getInt(key));
1707             }
1708         }
1709         // Locked by 'this'
buildImportanceLocked()1710         private void buildImportanceLocked() {
1711             String[] orderedKeys = mRankingUpdate.getOrderedKeys();
1712             int[] importance = mRankingUpdate.getImportance();
1713             mImportance = new ArrayMap<>(orderedKeys.length);
1714             for (int i = 0; i < orderedKeys.length; i++) {
1715                 String key = orderedKeys[i];
1716                 mImportance.put(key, importance[i]);
1717             }
1718         }
1719 
1720         // Locked by 'this'
buildImportanceExplanationLocked()1721         private void buildImportanceExplanationLocked() {
1722             Bundle explanationBundle = mRankingUpdate.getImportanceExplanation();
1723             mImportanceExplanation = new ArrayMap<>(explanationBundle.size());
1724             for (String key: explanationBundle.keySet()) {
1725                 mImportanceExplanation.put(key, explanationBundle.getString(key));
1726             }
1727         }
1728 
1729         // Locked by 'this'
buildOverrideGroupKeys()1730         private void buildOverrideGroupKeys() {
1731             Bundle overrideGroupKeys = mRankingUpdate.getOverrideGroupKeys();
1732             mOverrideGroupKeys = new ArrayMap<>(overrideGroupKeys.size());
1733             for (String key: overrideGroupKeys.keySet()) {
1734                 mOverrideGroupKeys.put(key, overrideGroupKeys.getString(key));
1735             }
1736         }
1737 
1738         // Locked by 'this'
buildChannelsLocked()1739         private void buildChannelsLocked() {
1740             Bundle channels = mRankingUpdate.getChannels();
1741             mChannels = new ArrayMap<>(channels.size());
1742             for (String key : channels.keySet()) {
1743                 mChannels.put(key, channels.getParcelable(key));
1744             }
1745         }
1746 
1747         // Locked by 'this'
buildOverridePeopleLocked()1748         private void buildOverridePeopleLocked() {
1749             Bundle overridePeople = mRankingUpdate.getOverridePeople();
1750             mOverridePeople = new ArrayMap<>(overridePeople.size());
1751             for (String key : overridePeople.keySet()) {
1752                 mOverridePeople.put(key, overridePeople.getStringArrayList(key));
1753             }
1754         }
1755 
1756         // Locked by 'this'
buildSnoozeCriteriaLocked()1757         private void buildSnoozeCriteriaLocked() {
1758             Bundle snoozeCriteria = mRankingUpdate.getSnoozeCriteria();
1759             mSnoozeCriteria = new ArrayMap<>(snoozeCriteria.size());
1760             for (String key : snoozeCriteria.keySet()) {
1761                 mSnoozeCriteria.put(key, snoozeCriteria.getParcelableArrayList(key));
1762             }
1763         }
1764 
1765         // Locked by 'this'
buildShowBadgeLocked()1766         private void buildShowBadgeLocked() {
1767             Bundle showBadge = mRankingUpdate.getShowBadge();
1768             mShowBadge = new ArrayMap<>(showBadge.size());
1769             for (String key : showBadge.keySet()) {
1770                 mShowBadge.put(key, showBadge.getBoolean(key));
1771             }
1772         }
1773 
1774         // ----------- Parcelable
1775 
1776         @Override
describeContents()1777         public int describeContents() {
1778             return 0;
1779         }
1780 
1781         @Override
writeToParcel(Parcel dest, int flags)1782         public void writeToParcel(Parcel dest, int flags) {
1783             dest.writeParcelable(mRankingUpdate, flags);
1784         }
1785 
1786         public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() {
1787             @Override
1788             public RankingMap createFromParcel(Parcel source) {
1789                 NotificationRankingUpdate rankingUpdate = source.readParcelable(null);
1790                 return new RankingMap(rankingUpdate);
1791             }
1792 
1793             @Override
1794             public RankingMap[] newArray(int size) {
1795                 return new RankingMap[size];
1796             }
1797         };
1798     }
1799 
1800     private final class MyHandler extends Handler {
1801         public static final int MSG_ON_NOTIFICATION_POSTED = 1;
1802         public static final int MSG_ON_NOTIFICATION_REMOVED = 2;
1803         public static final int MSG_ON_LISTENER_CONNECTED = 3;
1804         public static final int MSG_ON_NOTIFICATION_RANKING_UPDATE = 4;
1805         public static final int MSG_ON_LISTENER_HINTS_CHANGED = 5;
1806         public static final int MSG_ON_INTERRUPTION_FILTER_CHANGED = 6;
1807         public static final int MSG_ON_NOTIFICATION_CHANNEL_MODIFIED = 7;
1808         public static final int MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED = 8;
1809 
MyHandler(Looper looper)1810         public MyHandler(Looper looper) {
1811             super(looper, null, false);
1812         }
1813 
1814         @Override
handleMessage(Message msg)1815         public void handleMessage(Message msg) {
1816             if (!isConnected) {
1817                 return;
1818             }
1819             switch (msg.what) {
1820                 case MSG_ON_NOTIFICATION_POSTED: {
1821                     SomeArgs args = (SomeArgs) msg.obj;
1822                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
1823                     RankingMap rankingMap = (RankingMap) args.arg2;
1824                     args.recycle();
1825                     onNotificationPosted(sbn, rankingMap);
1826                 } break;
1827 
1828                 case MSG_ON_NOTIFICATION_REMOVED: {
1829                     SomeArgs args = (SomeArgs) msg.obj;
1830                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
1831                     RankingMap rankingMap = (RankingMap) args.arg2;
1832                     int reason = (int) args.arg3;
1833                     args.recycle();
1834                     onNotificationRemoved(sbn, rankingMap, reason);
1835                 } break;
1836 
1837                 case MSG_ON_LISTENER_CONNECTED: {
1838                     onListenerConnected();
1839                 } break;
1840 
1841                 case MSG_ON_NOTIFICATION_RANKING_UPDATE: {
1842                     RankingMap rankingMap = (RankingMap) msg.obj;
1843                     onNotificationRankingUpdate(rankingMap);
1844                 } break;
1845 
1846                 case MSG_ON_LISTENER_HINTS_CHANGED: {
1847                     final int hints = msg.arg1;
1848                     onListenerHintsChanged(hints);
1849                 } break;
1850 
1851                 case MSG_ON_INTERRUPTION_FILTER_CHANGED: {
1852                     final int interruptionFilter = msg.arg1;
1853                     onInterruptionFilterChanged(interruptionFilter);
1854                 } break;
1855 
1856                 case MSG_ON_NOTIFICATION_CHANNEL_MODIFIED: {
1857                     SomeArgs args = (SomeArgs) msg.obj;
1858                     String pkgName = (String) args.arg1;
1859                     UserHandle user= (UserHandle) args.arg2;
1860                     NotificationChannel channel = (NotificationChannel) args.arg3;
1861                     int modificationType = (int) args.arg4;
1862                     onNotificationChannelModified(pkgName, user, channel, modificationType);
1863                 } break;
1864 
1865                 case MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED: {
1866                     SomeArgs args = (SomeArgs) msg.obj;
1867                     String pkgName = (String) args.arg1;
1868                     UserHandle user = (UserHandle) args.arg2;
1869                     NotificationChannelGroup group = (NotificationChannelGroup) args.arg3;
1870                     int modificationType = (int) args.arg4;
1871                     onNotificationChannelGroupModified(pkgName, user, group, modificationType);
1872                 } break;
1873             }
1874         }
1875     }
1876 }
1877