• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
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      * @removed
609      */
610     @SystemApi
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      * @removed
753      *
754      * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}.
755      *             See <code>TRIM_*</code> constants.
756      */
757     @SystemApi
setOnNotificationPostedTrim(int trim)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      */
getActiveNotifications()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      */
getSnoozedNotifications()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
getActiveNotifications(int trim)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      */
getActiveNotifications(String[] keys)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
getActiveNotifications(String[] keys, int trim)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 
cleanUpNotificationList( ParceledListSlice<StatusBarNotification> parceledList)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      */
getCurrentListenerHints()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      */
getCurrentInterruptionFilter()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      */
requestListenerHints(int hints)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      */
requestInterruptionFilter(int interruptionFilter)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      */
getCurrentRanking()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
onBind(Intent intent)1019     public IBinder onBind(Intent intent) {
1020         if (mWrapper == null) {
1021             mWrapper = new NotificationListenerWrapper();
1022         }
1023         return mWrapper;
1024     }
1025 
1026     /** @hide */
isBound()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
onDestroy()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
registerAsSystemService(Context context, ComponentName componentName, int currentUser)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
unregisterAsSystemService()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      */
requestRebind(ComponentName componentName)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      */
requestUnbind()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. */
createLegacyIconExtras(Notification n)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      */
maybePopulateRemoteViews(Notification notification)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
onNotificationPosted(IStatusBarNotificationHolder sbnHolder, NotificationRankingUpdate update)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
onNotificationRemoved(IStatusBarNotificationHolder sbnHolder, NotificationRankingUpdate update, int reason)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
onListenerConnected(NotificationRankingUpdate update)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
onNotificationRankingUpdate(NotificationRankingUpdate update)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
onListenerHintsChanged(int hints)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
onInterruptionFilterChanged(int interruptionFilter)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
onNotificationEnqueued(IStatusBarNotificationHolder notificationHolder)1259         public void onNotificationEnqueued(IStatusBarNotificationHolder notificationHolder)
1260                 throws RemoteException {
1261             // no-op in the listener
1262         }
1263 
1264         @Override
onNotificationSnoozedUntilContext( IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId)1265         public void onNotificationSnoozedUntilContext(
1266                 IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId)
1267                 throws RemoteException {
1268             // no-op in the listener
1269         }
1270 
1271         @Override
onNotificationChannelModification(String pkgName, UserHandle user, NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType)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
onNotificationChannelGroupModification(String pkgName, UserHandle user, NotificationChannelGroup group, @ChannelOrGroupModificationTypes int modificationType)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      */
applyUpdateLocked(NotificationRankingUpdate update)1301     public final void applyUpdateLocked(NotificationRankingUpdate update) {
1302         mRankingMap = new RankingMap(update);
1303     }
1304 
1305     /** @hide */
getContext()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 
Ranking()1345         public Ranking() {}
1346 
1347         /**
1348          * Returns the key of the notification this Ranking applies to.
1349          */
getKey()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          */
getRank()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          */
isAmbient()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          */
getVisibilityOverride()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          */
getSuppressedVisualEffects()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          */
matchesInterruptionFilter()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          */
getImportance()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          */
getImportanceExplanation()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          */
getOverrideGroupKey()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          */
getChannel()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
getAdditionalPeople()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
getSnoozeCriteria()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          */
canShowBadge()1466         public boolean canShowBadge() {
1467             return mShowBadge;
1468         }
1469 
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)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 
getRank(String key)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 
isIntercepted(String key)1577         private boolean isIntercepted(String key) {
1578             synchronized (this) {
1579                 if (mIntercepted == null) {
1580                     buildInterceptedSetLocked();
1581                 }
1582             }
1583             return mIntercepted.contains(key);
1584         }
1585 
getVisibilityOverride(String key)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 
getSuppressedVisualEffects(String key)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 
getImportance(String key)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 
getImportanceExplanation(String key)1625         private String getImportanceExplanation(String key) {
1626             synchronized (this) {
1627                 if (mImportanceExplanation == null) {
1628                     buildImportanceExplanationLocked();
1629                 }
1630             }
1631             return mImportanceExplanation.get(key);
1632         }
1633 
getOverrideGroupKey(String key)1634         private String getOverrideGroupKey(String key) {
1635             synchronized (this) {
1636                 if (mOverrideGroupKeys == null) {
1637                     buildOverrideGroupKeys();
1638                 }
1639             }
1640             return mOverrideGroupKeys.get(key);
1641         }
1642 
getChannel(String key)1643         private NotificationChannel getChannel(String key) {
1644             synchronized (this) {
1645                 if (mChannels == null) {
1646                     buildChannelsLocked();
1647                 }
1648             }
1649             return mChannels.get(key);
1650         }
1651 
getOverridePeople(String key)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 
getSnoozeCriteria(String key)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 
getShowBadge(String key)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'
buildRanksLocked()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'
buildInterceptedSetLocked()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'
buildVisibilityOverridesLocked()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'
buildSuppressedVisualEffectsLocked()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'
buildImportanceLocked()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'
buildImportanceExplanationLocked()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'
buildOverrideGroupKeys()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'
buildChannelsLocked()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'
buildOverridePeopleLocked()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'
buildSnoozeCriteriaLocked()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'
buildShowBadgeLocked()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
describeContents()1782         public int describeContents() {
1783             return 0;
1784         }
1785 
1786         @Override
writeToParcel(Parcel dest, int flags)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 
MyHandler(Looper looper)1815         public MyHandler(Looper looper) {
1816             super(looper, null, false);
1817         }
1818 
1819         @Override
handleMessage(Message msg)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