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.CurrentTimeMillisLong;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SdkConstant;
24 import android.annotation.SystemApi;
25 import android.app.ActivityManager;
26 import android.app.INotificationManager;
27 import android.app.Notification;
28 import android.app.Notification.Builder;
29 import android.app.NotificationChannel;
30 import android.app.NotificationChannelGroup;
31 import android.app.NotificationManager;
32 import android.app.Person;
33 import android.app.Service;
34 import android.companion.CompanionDeviceManager;
35 import android.compat.annotation.UnsupportedAppUsage;
36 import android.content.ComponentName;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.content.pm.ParceledListSlice;
40 import android.content.pm.ShortcutInfo;
41 import android.graphics.Bitmap;
42 import android.graphics.drawable.BitmapDrawable;
43 import android.graphics.drawable.Drawable;
44 import android.graphics.drawable.Icon;
45 import android.os.Build;
46 import android.os.Bundle;
47 import android.os.Handler;
48 import android.os.IBinder;
49 import android.os.Looper;
50 import android.os.Message;
51 import android.os.Parcel;
52 import android.os.Parcelable;
53 import android.os.RemoteException;
54 import android.os.ServiceManager;
55 import android.os.UserHandle;
56 import android.util.ArrayMap;
57 import android.util.Log;
58 import android.widget.RemoteViews;
59 
60 import com.android.internal.annotations.GuardedBy;
61 import com.android.internal.annotations.VisibleForTesting;
62 import com.android.internal.os.SomeArgs;
63 
64 import java.lang.annotation.Retention;
65 import java.lang.annotation.RetentionPolicy;
66 import java.util.ArrayList;
67 import java.util.Collections;
68 import java.util.List;
69 import java.util.Objects;
70 
71 /**
72  * A service that receives calls from the system when new notifications are
73  * posted or removed, or their ranking changed.
74  * <p>To extend this class, you must declare the service in your manifest file with
75  * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission
76  * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
77  * <pre>
78  * &lt;service android:name=".NotificationListener"
79  *          android:label="&#64;string/service_name"
80  *          android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
81  *     &lt;intent-filter>
82  *         &lt;action android:name="android.service.notification.NotificationListenerService" />
83  *     &lt;/intent-filter>
84  *     &lt;meta-data
85  *               android:name="android.service.notification.default_filter_types"
86  *               android:value="conversations|alerting">
87  *           &lt;/meta-data>
88  *     &lt;meta-data
89  *               android:name="android.service.notification.disabled_filter_types"
90  *               android:value="ongoing|silent">
91  *           &lt;/meta-data>
92  * &lt;/service></pre>
93  *
94  * <p>The service should wait for the {@link #onListenerConnected()} event
95  * before performing any operations. The {@link #requestRebind(ComponentName)}
96  * method is the <i>only</i> one that is safe to call before {@link #onListenerConnected()}
97  * or after {@link #onListenerDisconnected()}.
98  * </p>
99  * <p> Notification listeners cannot get notification access or be bound by the system on
100  * {@linkplain ActivityManager#isLowRamDevice() low-RAM} devices running Android Q (and below).
101  * The system also ignores notification listeners running in a work profile. A
102  * {@link android.app.admin.DevicePolicyManager} might block notifications originating from a work
103  * profile.</p>
104  * <p>
105  *     From {@link Build.VERSION_CODES#N} onward all callbacks are called on the main thread. Prior
106  *     to N, there is no guarantee on what thread the callback will happen.
107  * </p>
108  */
109 public abstract class NotificationListenerService extends Service {
110 
111     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
112     private final String TAG = getClass().getSimpleName();
113 
114     /**
115      * The name of the {@code meta-data} tag containing a pipe separated list of default
116      * integer notification types or "ongoing", "conversations", "alerting", or "silent"
117      * that should be provided to this listener. See
118      * {@link #FLAG_FILTER_TYPE_ONGOING},
119      * {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING),
120      * and {@link #FLAG_FILTER_TYPE_SILENT}.
121      * <p>This value will only be read if the app has not previously specified a default type list,
122      * and if the user has not overridden the allowed types.</p>
123      * <p>An absent value means 'allow all types'.
124      * A present but empty value means 'allow no types'.</p>
125      *
126      */
127     public static final String META_DATA_DEFAULT_FILTER_TYPES
128             = "android.service.notification.default_filter_types";
129 
130     /**
131      * The name of the {@code meta-data} tag containing a comma separated list of default
132      * integer notification types that this listener never wants to receive. See
133      * {@link #FLAG_FILTER_TYPE_ONGOING},
134      * {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING),
135      * and {@link #FLAG_FILTER_TYPE_SILENT}.
136      * <p>Types provided in this list will appear as 'off' and 'disabled' in the user interface,
137      * so users don't enable a type that the listener will never bridge to their paired devices.</p>
138      *
139      */
140     public static final String META_DATA_DISABLED_FILTER_TYPES
141             = "android.service.notification.disabled_filter_types";
142 
143     /**
144      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
145      *     Normal interruption filter.
146      */
147     public static final int INTERRUPTION_FILTER_ALL
148             = NotificationManager.INTERRUPTION_FILTER_ALL;
149 
150     /**
151      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
152      *     Priority interruption filter.
153      */
154     public static final int INTERRUPTION_FILTER_PRIORITY
155             = NotificationManager.INTERRUPTION_FILTER_PRIORITY;
156 
157     /**
158      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
159      *     No interruptions filter.
160      */
161     public static final int INTERRUPTION_FILTER_NONE
162             = NotificationManager.INTERRUPTION_FILTER_NONE;
163 
164     /**
165      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
166      *     Alarms only interruption filter.
167      */
168     public static final int INTERRUPTION_FILTER_ALARMS
169             = NotificationManager.INTERRUPTION_FILTER_ALARMS;
170 
171     /** {@link #getCurrentInterruptionFilter() Interruption filter} constant - returned when
172      * the value is unavailable for any reason.  For example, before the notification listener
173      * is connected.
174      *
175      * {@see #onListenerConnected()}
176      */
177     public static final int INTERRUPTION_FILTER_UNKNOWN
178             = NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
179 
180     /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
181      * should disable notification sound, vibrating and other visual or aural effects.
182      * This does not change the interruption filter, only the effects. **/
183     public static final int HINT_HOST_DISABLE_EFFECTS = 1;
184 
185     /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
186      * should disable notification sound, but not phone calls.
187      * This does not change the interruption filter, only the effects. **/
188     public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 1 << 1;
189 
190     /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
191      * should disable phone call sounds, but not notification sound.
192      * This does not change the interruption filter, only the effects. **/
193     public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 1 << 2;
194 
195     /**
196      * Whether notification suppressed by DND should not interruption visually when the screen is
197      * off.
198      *
199      * @deprecated Use the more specific visual effects in {@link NotificationManager.Policy}.
200      */
201     @Deprecated
202     public static final int SUPPRESSED_EFFECT_SCREEN_OFF =
203             NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
204     /**
205      * Whether notification suppressed by DND should not interruption visually when the screen is
206      * on.
207      *
208      * @deprecated Use the more specific visual effects in {@link NotificationManager.Policy}.
209      */
210     @Deprecated
211     public static final int SUPPRESSED_EFFECT_SCREEN_ON =
212             NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
213 
214 
215     // Notification cancellation reasons
216 
217     /** Notification was canceled by the status bar reporting a notification click. */
218     public static final int REASON_CLICK = 1;
219     /** Notification was canceled by the status bar reporting a user dismissal. */
220     public static final int REASON_CANCEL = 2;
221     /** Notification was canceled by the status bar reporting a user dismiss all. */
222     public static final int REASON_CANCEL_ALL = 3;
223     /** Notification was canceled by the status bar reporting an inflation error. */
224     public static final int REASON_ERROR = 4;
225     /** Notification was canceled by the package manager modifying the package. */
226     public static final int REASON_PACKAGE_CHANGED = 5;
227     /** Notification was canceled by the owning user context being stopped. */
228     public static final int REASON_USER_STOPPED = 6;
229     /** Notification was canceled by the user banning the package. */
230     public static final int REASON_PACKAGE_BANNED = 7;
231     /** Notification was canceled by the app canceling this specific notification. */
232     public static final int REASON_APP_CANCEL = 8;
233     /** Notification was canceled by the app cancelling all its notifications. */
234     public static final int REASON_APP_CANCEL_ALL = 9;
235     /** Notification was canceled by a listener reporting a user dismissal. */
236     public static final int REASON_LISTENER_CANCEL = 10;
237     /** Notification was canceled by a listener reporting a user dismiss all. */
238     public static final int REASON_LISTENER_CANCEL_ALL = 11;
239     /** Notification was canceled because it was a member of a canceled group. */
240     public static final int REASON_GROUP_SUMMARY_CANCELED = 12;
241     /** Notification was canceled because it was an invisible member of a group. */
242     public static final int REASON_GROUP_OPTIMIZATION = 13;
243     /** Notification was canceled by the device administrator suspending the package. */
244     public static final int REASON_PACKAGE_SUSPENDED = 14;
245     /** Notification was canceled by the owning managed profile being turned off. */
246     public static final int REASON_PROFILE_TURNED_OFF = 15;
247     /** Autobundled summary notification was canceled because its group was unbundled */
248     public static final int REASON_UNAUTOBUNDLED = 16;
249     /** Notification was canceled by the user banning the channel. */
250     public static final int REASON_CHANNEL_BANNED = 17;
251     /** Notification was snoozed. */
252     public static final int REASON_SNOOZED = 18;
253     /** Notification was canceled due to timeout */
254     public static final int REASON_TIMEOUT = 19;
255     /** Notification was canceled due to the backing channel being deleted */
256     public static final int REASON_CHANNEL_REMOVED = 20;
257     /** Notification was canceled due to the app's storage being cleared */
258     public static final int REASON_CLEAR_DATA = 21;
259 
260     /**
261      * @hide
262      */
263     @IntDef(prefix = "REASON_", value = {
264             REASON_CLICK,
265             REASON_CANCEL,
266             REASON_CANCEL_ALL,
267             REASON_ERROR,
268             REASON_PACKAGE_CHANGED,
269             REASON_USER_STOPPED,
270             REASON_PACKAGE_BANNED,
271             REASON_APP_CANCEL,
272             REASON_APP_CANCEL_ALL,
273             REASON_LISTENER_CANCEL,
274             REASON_LISTENER_CANCEL_ALL,
275             REASON_GROUP_SUMMARY_CANCELED,
276             REASON_GROUP_OPTIMIZATION,
277             REASON_PACKAGE_SUSPENDED,
278             REASON_PROFILE_TURNED_OFF,
279             REASON_UNAUTOBUNDLED,
280             REASON_CHANNEL_BANNED,
281             REASON_SNOOZED,
282             REASON_TIMEOUT
283     })
284     public @interface NotificationCancelReason{};
285 
286     /**
287      * @hide
288      */
289     @IntDef(flag = true, prefix = { "FLAG_FILTER_TYPE_" }, value = {
290             FLAG_FILTER_TYPE_CONVERSATIONS,
291             FLAG_FILTER_TYPE_ALERTING,
292             FLAG_FILTER_TYPE_SILENT,
293             FLAG_FILTER_TYPE_ONGOING
294     })
295     public @interface NotificationFilterTypes {}
296     /**
297      * A flag value indicating that this notification listener can see conversation type
298      * notifications.
299      */
300     public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1;
301     /**
302      * A flag value indicating that this notification listener can see altering type notifications.
303      */
304     public static final int FLAG_FILTER_TYPE_ALERTING = 2;
305     /**
306      * A flag value indicating that this notification listener can see silent type notifications.
307      */
308     public static final int FLAG_FILTER_TYPE_SILENT = 4;
309     /**
310      * A flag value indicating that this notification listener can see important
311      * ( > {@link NotificationManager#IMPORTANCE_MIN}) ongoing type notifications.
312      */
313     public static final int FLAG_FILTER_TYPE_ONGOING = 8;
314 
315     /**
316      * The full trim of the StatusBarNotification including all its features.
317      *
318      * @hide
319      * @removed
320      */
321     @SystemApi
322     public static final int TRIM_FULL = 0;
323 
324     /**
325      * A light trim of the StatusBarNotification excluding the following features:
326      *
327      * <ol>
328      *     <li>{@link Notification#tickerView tickerView}</li>
329      *     <li>{@link Notification#contentView contentView}</li>
330      *     <li>{@link Notification#largeIcon largeIcon}</li>
331      *     <li>{@link Notification#bigContentView bigContentView}</li>
332      *     <li>{@link Notification#headsUpContentView headsUpContentView}</li>
333      *     <li>{@link Notification#EXTRA_LARGE_ICON extras[EXTRA_LARGE_ICON]}</li>
334      *     <li>{@link Notification#EXTRA_LARGE_ICON_BIG extras[EXTRA_LARGE_ICON_BIG]}</li>
335      *     <li>{@link Notification#EXTRA_PICTURE extras[EXTRA_PICTURE]}</li>
336      *     <li>{@link Notification#EXTRA_BIG_TEXT extras[EXTRA_BIG_TEXT]}</li>
337      * </ol>
338      *
339      * @hide
340      * @removed
341      */
342     @SystemApi
343     public static final int TRIM_LIGHT = 1;
344 
345 
346     /** @hide */
347     @IntDef(prefix = { "NOTIFICATION_CHANNEL_OR_GROUP_" }, value = {
348             NOTIFICATION_CHANNEL_OR_GROUP_ADDED,
349             NOTIFICATION_CHANNEL_OR_GROUP_UPDATED,
350             NOTIFICATION_CHANNEL_OR_GROUP_DELETED
351     })
352     @Retention(RetentionPolicy.SOURCE)
353     public @interface ChannelOrGroupModificationTypes {}
354 
355     /**
356      * Channel or group modification reason provided to
357      * {@link #onNotificationChannelModified(String, UserHandle,NotificationChannel, int)} or
358      * {@link #onNotificationChannelGroupModified(String, UserHandle, NotificationChannelGroup,
359      * int)}- the provided object was created.
360      */
361     public static final int NOTIFICATION_CHANNEL_OR_GROUP_ADDED = 1;
362 
363     /**
364      * Channel or group modification reason provided to
365      * {@link #onNotificationChannelModified(String, UserHandle, NotificationChannel, int)} or
366      * {@link #onNotificationChannelGroupModified(String, UserHandle,NotificationChannelGroup, int)}
367      * - the provided object was updated.
368      */
369     public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2;
370 
371     /**
372      * Channel or group modification reason provided to
373      * {@link #onNotificationChannelModified(String, UserHandle, NotificationChannel, int)} or
374      * {@link #onNotificationChannelGroupModified(String, UserHandle, NotificationChannelGroup,
375      * int)}- the provided object was deleted.
376      */
377     public static final int NOTIFICATION_CHANNEL_OR_GROUP_DELETED = 3;
378 
379     private final Object mLock = new Object();
380 
381     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
382     private Handler mHandler;
383 
384     /** @hide */
385     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
386     protected NotificationListenerWrapper mWrapper = null;
387     private boolean isConnected = false;
388 
389     @GuardedBy("mLock")
390     private RankingMap mRankingMap;
391 
392     /**
393      * @hide
394      */
395     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
396     protected INotificationManager mNoMan;
397 
398     /**
399      * Only valid after a successful call to (@link registerAsService}.
400      * @hide
401      */
402     protected int mCurrentUser;
403 
404     /**
405      * This context is required for system services since NotificationListenerService isn't
406      * started as a real Service and hence no context is available..
407      * @hide
408      */
409     protected Context mSystemContext;
410 
411     /**
412      * The {@link Intent} that must be declared as handled by the service.
413      */
414     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
415     public static final String SERVICE_INTERFACE
416             = "android.service.notification.NotificationListenerService";
417 
418     @Override
attachBaseContext(Context base)419     protected void attachBaseContext(Context base) {
420         super.attachBaseContext(base);
421         mHandler = new MyHandler(getMainLooper());
422     }
423 
424     /**
425      * Implement this method to learn about new notifications as they are posted by apps.
426      *
427      * @param sbn A data structure encapsulating the original {@link android.app.Notification}
428      *            object as well as its identifying information (tag and id) and source
429      *            (package name).
430      */
onNotificationPosted(StatusBarNotification sbn)431     public void onNotificationPosted(StatusBarNotification sbn) {
432         // optional
433     }
434 
435     /**
436      * Implement this method to learn about new notifications as they are posted by apps.
437      *
438      * @param sbn A data structure encapsulating the original {@link android.app.Notification}
439      *            object as well as its identifying information (tag and id) and source
440      *            (package name).
441      * @param rankingMap The current ranking map that can be used to retrieve ranking information
442      *                   for active notifications, including the newly posted one.
443      */
onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap)444     public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
445         onNotificationPosted(sbn);
446     }
447 
448     /**
449      * Implement this method to learn when notifications are removed.
450      * <p>
451      * This might occur because the user has dismissed the notification using system UI (or another
452      * notification listener) or because the app has withdrawn the notification.
453      * <p>
454      * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
455      * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
456      * fields such as {@link android.app.Notification#contentView} and
457      * {@link android.app.Notification#largeIcon}. However, all other fields on
458      * {@link StatusBarNotification}, sufficient to match this call with a prior call to
459      * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
460      *
461      * @param sbn A data structure encapsulating at least the original information (tag and id)
462      *            and source (package name) used to post the {@link android.app.Notification} that
463      *            was just removed.
464      */
onNotificationRemoved(StatusBarNotification sbn)465     public void onNotificationRemoved(StatusBarNotification sbn) {
466         // optional
467     }
468 
469     /**
470      * Implement this method to learn when notifications are removed.
471      * <p>
472      * This might occur because the user has dismissed the notification using system UI (or another
473      * notification listener) or because the app has withdrawn the notification.
474      * <p>
475      * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
476      * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
477      * fields such as {@link android.app.Notification#contentView} and
478      * {@link android.app.Notification#largeIcon}. However, all other fields on
479      * {@link StatusBarNotification}, sufficient to match this call with a prior call to
480      * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
481      *
482      * @param sbn A data structure encapsulating at least the original information (tag and id)
483      *            and source (package name) used to post the {@link android.app.Notification} that
484      *            was just removed.
485      * @param rankingMap The current ranking map that can be used to retrieve ranking information
486      *                   for active notifications.
487      *
488      */
onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap)489     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
490         onNotificationRemoved(sbn);
491     }
492 
493 
494     /**
495      * Implement this method to learn when notifications are removed and why.
496      * <p>
497      * This might occur because the user has dismissed the notification using system UI (or another
498      * notification listener) or because the app has withdrawn the notification.
499      * <p>
500      * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
501      * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
502      * fields such as {@link android.app.Notification#contentView} and
503      * {@link android.app.Notification#largeIcon}. However, all other fields on
504      * {@link StatusBarNotification}, sufficient to match this call with a prior call to
505      * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
506      *
507      ** @param sbn A data structure encapsulating at least the original information (tag and id)
508      *            and source (package name) used to post the {@link android.app.Notification} that
509      *            was just removed.
510      * @param rankingMap The current ranking map that can be used to retrieve ranking information
511      *                   for active notifications.
512      * @param reason see {@link #REASON_LISTENER_CANCEL}, etc.
513      */
onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason)514     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
515             int reason) {
516         onNotificationRemoved(sbn, rankingMap);
517     }
518 
519     /**
520      * NotificationStats are not populated for notification listeners, so fall back to
521      * {@link #onNotificationRemoved(StatusBarNotification, RankingMap, int)}.
522      *
523      * @hide
524      */
525     @SystemApi
onNotificationRemoved(@onNull StatusBarNotification sbn, @NonNull RankingMap rankingMap, @NonNull NotificationStats stats, int reason)526     public void onNotificationRemoved(@NonNull StatusBarNotification sbn,
527             @NonNull RankingMap rankingMap, @NonNull NotificationStats stats, int reason) {
528         onNotificationRemoved(sbn, rankingMap, reason);
529     }
530 
531     /**
532      * Implement this method to learn about when the listener is enabled and connected to
533      * the notification manager.  You are safe to call {@link #getActiveNotifications()}
534      * at this time.
535      */
onListenerConnected()536     public void onListenerConnected() {
537         // optional
538     }
539 
540     /**
541      * Implement this method to learn about when the listener is disconnected from the
542      * notification manager.You will not receive any events after this call, and may only
543      * call {@link #requestRebind(ComponentName)} at this time.
544      */
onListenerDisconnected()545     public void onListenerDisconnected() {
546         // optional
547     }
548 
549     /**
550      * Implement this method to be notified when the notification ranking changes.
551      *
552      * @param rankingMap The current ranking map that can be used to retrieve ranking information
553      *                   for active notifications.
554      */
onNotificationRankingUpdate(RankingMap rankingMap)555     public void onNotificationRankingUpdate(RankingMap rankingMap) {
556         // optional
557     }
558 
559     /**
560      * Implement this method to be notified when the
561      * {@link #getCurrentListenerHints() Listener hints} change.
562      *
563      * @param hints The current {@link #getCurrentListenerHints() listener hints}.
564      */
onListenerHintsChanged(int hints)565     public void onListenerHintsChanged(int hints) {
566         // optional
567     }
568 
569     /**
570      * Implement this method to be notified when the behavior of silent notifications in the status
571      * bar changes. See {@link NotificationManager#shouldHideSilentStatusBarIcons()}.
572      *
573      * @param hideSilentStatusIcons whether or not status bar icons should be hidden for silent
574      *                              notifications
575      */
onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons)576     public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) {
577         // optional
578     }
579 
580     /**
581      * Implement this method to learn about notification channel modifications.
582      *
583      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
584      * device} in order to receive this callback.
585      *
586      * @param pkg The package the channel belongs to.
587      * @param user The user on which the change was made.
588      * @param channel The channel that has changed.
589      * @param modificationType One of {@link #NOTIFICATION_CHANNEL_OR_GROUP_ADDED},
590      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
591      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
592      */
onNotificationChannelModified(String pkg, UserHandle user, NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType)593     public void onNotificationChannelModified(String pkg, UserHandle user,
594             NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType) {
595         // optional
596     }
597 
598     /**
599      * Implement this method to learn about notification channel group modifications.
600      *
601      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
602      * device} in order to receive this callback.
603      *
604      * @param pkg The package the group belongs to.
605      * @param user The user on which the change was made.
606      * @param group The group that has changed.
607      * @param modificationType One of {@link #NOTIFICATION_CHANNEL_OR_GROUP_ADDED},
608      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
609      *                   {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
610      */
onNotificationChannelGroupModified(String pkg, UserHandle user, NotificationChannelGroup group, @ChannelOrGroupModificationTypes int modificationType)611     public void onNotificationChannelGroupModified(String pkg, UserHandle user,
612             NotificationChannelGroup group, @ChannelOrGroupModificationTypes int modificationType) {
613         // optional
614     }
615 
616     /**
617      * Implement this method to be notified when the
618      * {@link #getCurrentInterruptionFilter() interruption filter} changed.
619      *
620      * @param interruptionFilter The current
621      *     {@link #getCurrentInterruptionFilter() interruption filter}.
622      */
onInterruptionFilterChanged(int interruptionFilter)623     public void onInterruptionFilterChanged(int interruptionFilter) {
624         // optional
625     }
626 
627     /** @hide */
628     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
getNotificationInterface()629     protected final INotificationManager getNotificationInterface() {
630         if (mNoMan == null) {
631             mNoMan = INotificationManager.Stub.asInterface(
632                     ServiceManager.getService(Context.NOTIFICATION_SERVICE));
633         }
634         return mNoMan;
635     }
636 
637     /**
638      * Inform the notification manager about dismissal of a single notification.
639      * <p>
640      * Use this if your listener has a user interface that allows the user to dismiss individual
641      * notifications, similar to the behavior of Android's status bar and notification panel.
642      * It should be called after the user dismisses a single notification using your UI;
643      * upon being informed, the notification manager will actually remove the notification
644      * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
645      * <p>
646      * <b>Note:</b> If your listener allows the user to fire a notification's
647      * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
648      * this method at that time <i>if</i> the Notification in question has the
649      * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
650      *
651      * <p>The service should wait for the {@link #onListenerConnected()} event
652      * before performing this operation.
653      *
654      * @param pkg Package of the notifying app.
655      * @param tag Tag of the notification as specified by the notifying app in
656      *     {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
657      * @param id  ID of the notification as specified by the notifying app in
658      *     {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
659      * <p>
660      * @deprecated Use {@link #cancelNotification(String key)}
661      * instead. Beginning with {@link android.os.Build.VERSION_CODES#LOLLIPOP} this method will no longer
662      * cancel the notification. It will continue to cancel the notification for applications
663      * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
664      */
665     @Deprecated
cancelNotification(String pkg, String tag, int id)666     public final void cancelNotification(String pkg, String tag, int id) {
667         if (!isBound()) return;
668         try {
669             getNotificationInterface().cancelNotificationFromListener(
670                     mWrapper, pkg, tag, id);
671         } catch (android.os.RemoteException ex) {
672             Log.v(TAG, "Unable to contact notification manager", ex);
673         }
674     }
675 
676     /**
677      * Inform the notification manager about dismissal of a single notification.
678      * <p>
679      * Use this if your listener has a user interface that allows the user to dismiss individual
680      * notifications, similar to the behavior of Android's status bar and notification panel.
681      * It should be called after the user dismisses a single notification using your UI;
682      * upon being informed, the notification manager will actually remove the notification
683      * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
684      * <p>
685      * <b>Note:</b> If your listener allows the user to fire a notification's
686      * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
687      * this method at that time <i>if</i> the Notification in question has the
688      * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
689      * <p>
690      *
691      * <p>The service should wait for the {@link #onListenerConnected()} event
692      * before performing this operation.
693      *
694      * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}.
695      */
cancelNotification(String key)696     public final void cancelNotification(String key) {
697         if (!isBound()) return;
698         try {
699             getNotificationInterface().cancelNotificationsFromListener(mWrapper,
700                     new String[] { key });
701         } catch (android.os.RemoteException ex) {
702             Log.v(TAG, "Unable to contact notification manager", ex);
703         }
704     }
705 
706     /**
707      * Inform the notification manager about dismissal of all notifications.
708      * <p>
709      * Use this if your listener has a user interface that allows the user to dismiss all
710      * notifications, similar to the behavior of Android's status bar and notification panel.
711      * It should be called after the user invokes the "dismiss all" function of your UI;
712      * upon being informed, the notification manager will actually remove all active notifications
713      * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks.
714      *
715      * <p>The service should wait for the {@link #onListenerConnected()} event
716      * before performing this operation.
717      *
718      * {@see #cancelNotification(String, String, int)}
719      */
cancelAllNotifications()720     public final void cancelAllNotifications() {
721         cancelNotifications(null /*all*/);
722     }
723 
724     /**
725      * Inform the notification manager about dismissal of specific notifications.
726      * <p>
727      * Use this if your listener has a user interface that allows the user to dismiss
728      * multiple notifications at once.
729      *
730      * <p>The service should wait for the {@link #onListenerConnected()} event
731      * before performing this operation.
732      *
733      * @param keys Notifications to dismiss, or {@code null} to dismiss all.
734      *
735      * {@see #cancelNotification(String, String, int)}
736      */
cancelNotifications(String[] keys)737     public final void cancelNotifications(String[] keys) {
738         if (!isBound()) return;
739         try {
740             getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys);
741         } catch (android.os.RemoteException ex) {
742             Log.v(TAG, "Unable to contact notification manager", ex);
743         }
744     }
745 
746     /**
747      * Inform the notification manager about snoozing a specific notification.
748      * <p>
749      * Use this if your listener has a user interface that allows the user to snooze a notification
750      * until a given {@link SnoozeCriterion}. It should be called after the user snoozes a single
751      * notification using your UI; upon being informed, the notification manager will actually
752      * remove the notification and you will get an
753      * {@link #onNotificationRemoved(StatusBarNotification)} callback. When the snoozing period
754      * expires, you will get a {@link #onNotificationPosted(StatusBarNotification, RankingMap)}
755      * callback for the notification.
756      * @param key The key of the notification to snooze
757      * @param snoozeCriterionId The{@link SnoozeCriterion#getId()} of a context to snooze the
758      *                          notification until.
759      * @hide
760      * @removed
761      */
762     @SystemApi
snoozeNotification(String key, String snoozeCriterionId)763     public final void snoozeNotification(String key, String snoozeCriterionId) {
764         if (!isBound()) return;
765         try {
766             getNotificationInterface().snoozeNotificationUntilContextFromListener(
767                     mWrapper, key, snoozeCriterionId);
768         } catch (android.os.RemoteException ex) {
769             Log.v(TAG, "Unable to contact notification manager", ex);
770         }
771     }
772 
773     /**
774      * Inform the notification manager about snoozing a specific notification.
775      * <p>
776      * Use this if your listener has a user interface that allows the user to snooze a notification
777      * for a time. It should be called after the user snoozes a single notification using
778      * your UI; upon being informed, the notification manager will actually remove the notification
779      * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. When the
780      * snoozing period expires, you will get a
781      * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
782      * notification.
783      * @param key The key of the notification to snooze
784      * @param durationMs A duration to snooze the notification for, in milliseconds.
785      */
snoozeNotification(String key, long durationMs)786     public final void snoozeNotification(String key, long durationMs) {
787         if (!isBound()) return;
788         try {
789             getNotificationInterface().snoozeNotificationUntilFromListener(
790                     mWrapper, key, durationMs);
791         } catch (android.os.RemoteException ex) {
792             Log.v(TAG, "Unable to contact notification manager", ex);
793         }
794     }
795 
796     /**
797      * Lets an app migrate notification filters from its app into the OS.
798      *
799      * <p>This call will be ignored if the app has already migrated these settings or the user
800      * has set filters in the UI. This method is intended for user specific settings; if an app has
801      * already specified defaults types in its manifest with
802      * {@link #META_DATA_DEFAULT_FILTER_TYPES}, the defaultTypes option will be ignored.</p>
803      * @param defaultTypes A value representing the types of notifications that this listener should
804      * receive by default
805      * @param disallowedPkgs A list of package names whose notifications should not be seen by this
806      * listener, by default, because the listener does not process or display them, or because a
807      * user had previously disallowed these packages in the listener app's UI
808      */
migrateNotificationFilter(@otificationFilterTypes int defaultTypes, @Nullable List<String> disallowedPkgs)809     public final void migrateNotificationFilter(@NotificationFilterTypes int defaultTypes,
810             @Nullable List<String> disallowedPkgs) {
811         if (!isBound()) return;
812         try {
813             getNotificationInterface().migrateNotificationFilter(
814                     mWrapper, defaultTypes, disallowedPkgs);
815         } catch (android.os.RemoteException ex) {
816             Log.v(TAG, "Unable to contact notification manager", ex);
817         }
818     }
819 
820     /**
821      * Inform the notification manager that these notifications have been viewed by the
822      * user. This should only be called when there is sufficient confidence that the user is
823      * looking at the notifications, such as when the notifications appear on the screen due to
824      * an explicit user interaction.
825      *
826      * <p>The service should wait for the {@link #onListenerConnected()} event
827      * before performing this operation.
828      *
829      * @param keys Notifications to mark as seen.
830      */
setNotificationsShown(String[] keys)831     public final void setNotificationsShown(String[] keys) {
832         if (!isBound()) return;
833         try {
834             getNotificationInterface().setNotificationsShownFromListener(mWrapper, keys);
835         } catch (android.os.RemoteException ex) {
836             Log.v(TAG, "Unable to contact notification manager", ex);
837         }
838     }
839 
840 
841     /**
842      * Updates a notification channel for a given package for a given user. This should only be used
843      * to reflect changes a user has made to the channel via the listener's user interface.
844      *
845      * <p>This method will throw a security exception if you don't have access to notifications
846      * for the given user.</p>
847      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
848      * device} in order to use this method.
849      *
850      * @param pkg The package the channel belongs to.
851      * @param user The user the channel belongs to.
852      * @param channel the channel to update.
853      */
updateNotificationChannel(@onNull String pkg, @NonNull UserHandle user, @NonNull NotificationChannel channel)854     public final void updateNotificationChannel(@NonNull String pkg, @NonNull UserHandle user,
855             @NonNull NotificationChannel channel) {
856         if (!isBound()) return;
857         try {
858             getNotificationInterface().updateNotificationChannelFromPrivilegedListener(
859                     mWrapper, pkg, user, channel);
860         } catch (RemoteException e) {
861             Log.v(TAG, "Unable to contact notification manager", e);
862             throw e.rethrowFromSystemServer();
863         }
864     }
865 
866     /**
867      * Returns all notification channels belonging to the given package for a given user.
868      *
869      * <p>This method will throw a security exception if you don't have access to notifications
870      * for the given user.</p>
871      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
872      * device} or be the {@link NotificationAssistantService notification assistant} in order to
873      * use this method.
874      *
875      * @param pkg The package to retrieve channels for.
876      */
getNotificationChannels(@onNull String pkg, @NonNull UserHandle user)877     public final List<NotificationChannel> getNotificationChannels(@NonNull String pkg,
878             @NonNull UserHandle user) {
879         if (!isBound()) return null;
880         try {
881 
882             return getNotificationInterface().getNotificationChannelsFromPrivilegedListener(
883                     mWrapper, pkg, user).getList();
884         } catch (RemoteException e) {
885             Log.v(TAG, "Unable to contact notification manager", e);
886             throw e.rethrowFromSystemServer();
887         }
888     }
889 
890     /**
891      * Returns all notification channel groups belonging to the given package for a given user.
892      *
893      * <p>This method will throw a security exception if you don't have access to notifications
894      * for the given user.</p>
895      * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
896      * device} or be the {@link NotificationAssistantService notification assistant} in order to
897      * use this method.
898      *
899      * @param pkg The package to retrieve channel groups for.
900      */
getNotificationChannelGroups(@onNull String pkg, @NonNull UserHandle user)901     public final List<NotificationChannelGroup> getNotificationChannelGroups(@NonNull String pkg,
902             @NonNull UserHandle user) {
903         if (!isBound()) return null;
904         try {
905 
906             return getNotificationInterface().getNotificationChannelGroupsFromPrivilegedListener(
907                     mWrapper, pkg, user).getList();
908         } catch (RemoteException e) {
909             Log.v(TAG, "Unable to contact notification manager", e);
910             throw e.rethrowFromSystemServer();
911         }
912     }
913 
914     /**
915      * Sets the notification trim that will be received via {@link #onNotificationPosted}.
916      *
917      * <p>
918      * Setting a trim other than {@link #TRIM_FULL} enables listeners that don't need access to the
919      * full notification features right away to reduce their memory footprint. Full notifications
920      * can be requested on-demand via {@link #getActiveNotifications(int)}.
921      *
922      * <p>
923      * Set to {@link #TRIM_FULL} initially.
924      *
925      * <p>The service should wait for the {@link #onListenerConnected()} event
926      * before performing this operation.
927      *
928      * @hide
929      * @removed
930      *
931      * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}.
932      *             See <code>TRIM_*</code> constants.
933      */
934     @SystemApi
setOnNotificationPostedTrim(int trim)935     public final void setOnNotificationPostedTrim(int trim) {
936         if (!isBound()) return;
937         try {
938             getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim);
939         } catch (RemoteException ex) {
940             Log.v(TAG, "Unable to contact notification manager", ex);
941         }
942     }
943 
944     /**
945      * Request the list of outstanding notifications (that is, those that are visible to the
946      * current user). Useful when you don't know what's already been posted.
947      *
948      * <p>The service should wait for the {@link #onListenerConnected()} event
949      * before performing this operation.
950      *
951      * @return An array of active notifications, sorted in natural order.
952      */
getActiveNotifications()953     public StatusBarNotification[] getActiveNotifications() {
954         StatusBarNotification[] activeNotifications = getActiveNotifications(null, TRIM_FULL);
955         return activeNotifications != null ? activeNotifications : new StatusBarNotification[0];
956     }
957 
958     /**
959      * Like {@link #getActiveNotifications()}, but returns the list of currently snoozed
960      * notifications, for all users this listener has access to.
961      *
962      * <p>The service should wait for the {@link #onListenerConnected()} event
963      * before performing this operation.
964      *
965      * @return An array of snoozed notifications, sorted in natural order.
966      */
getSnoozedNotifications()967     public final StatusBarNotification[] getSnoozedNotifications() {
968         try {
969             ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
970                     .getSnoozedNotificationsFromListener(mWrapper, TRIM_FULL);
971             return cleanUpNotificationList(parceledList);
972         } catch (android.os.RemoteException ex) {
973             Log.v(TAG, "Unable to contact notification manager", ex);
974         }
975         return null;
976     }
977 
978     /**
979      * Request the list of outstanding notifications (that is, those that are visible to the
980      * current user). Useful when you don't know what's already been posted.
981      *
982      * @hide
983      * @removed
984      *
985      * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
986      * @return An array of active notifications, sorted in natural order.
987      */
988     @SystemApi
getActiveNotifications(int trim)989     public StatusBarNotification[] getActiveNotifications(int trim) {
990         StatusBarNotification[] activeNotifications = getActiveNotifications(null, trim);
991         return activeNotifications != null ? activeNotifications : new StatusBarNotification[0];
992     }
993 
994     /**
995      * Request one or more notifications by key. Useful if you have been keeping track of
996      * notifications but didn't want to retain the bits, and now need to go back and extract
997      * more data out of those notifications.
998      *
999      * <p>The service should wait for the {@link #onListenerConnected()} event
1000      * before performing this operation.
1001      *
1002      * @param keys the keys of the notifications to request
1003      * @return An array of notifications corresponding to the requested keys, in the
1004      * same order as the key list.
1005      */
getActiveNotifications(String[] keys)1006     public StatusBarNotification[] getActiveNotifications(String[] keys) {
1007         StatusBarNotification[] activeNotifications = getActiveNotifications(keys, TRIM_FULL);
1008         return activeNotifications != null ? activeNotifications : new StatusBarNotification[0];
1009     }
1010 
1011     /**
1012      * Request one or more notifications by key. Useful if you have been keeping track of
1013      * notifications but didn't want to retain the bits, and now need to go back and extract
1014      * more data out of those notifications.
1015      *
1016      * @hide
1017      * @removed
1018      *
1019      * @param keys the keys of the notifications to request
1020      * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
1021      * @return An array of notifications corresponding to the requested keys, in the
1022      * same order as the key list.
1023      */
1024     @SystemApi
getActiveNotifications(String[] keys, int trim)1025     public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) {
1026         if (!isBound())
1027             return null;
1028         try {
1029             ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
1030                     .getActiveNotificationsFromListener(mWrapper, keys, trim);
1031             return cleanUpNotificationList(parceledList);
1032         } catch (android.os.RemoteException ex) {
1033             Log.v(TAG, "Unable to contact notification manager", ex);
1034         }
1035         return null;
1036     }
1037 
cleanUpNotificationList( ParceledListSlice<StatusBarNotification> parceledList)1038     private StatusBarNotification[] cleanUpNotificationList(
1039             ParceledListSlice<StatusBarNotification> parceledList) {
1040         if (parceledList == null || parceledList.getList() == null) {
1041             return new StatusBarNotification[0];
1042         }
1043         List<StatusBarNotification> list = parceledList.getList();
1044         ArrayList<StatusBarNotification> corruptNotifications = null;
1045         int N = list.size();
1046         for (int i = 0; i < N; i++) {
1047             StatusBarNotification sbn = list.get(i);
1048             Notification notification = sbn.getNotification();
1049             try {
1050                 // convert icon metadata to legacy format for older clients
1051                 createLegacyIconExtras(notification);
1052                 // populate remote views for older clients.
1053                 maybePopulateRemoteViews(notification);
1054                 // populate people for older clients.
1055                 maybePopulatePeople(notification);
1056             } catch (IllegalArgumentException e) {
1057                 if (corruptNotifications == null) {
1058                     corruptNotifications = new ArrayList<>(N);
1059                 }
1060                 corruptNotifications.add(sbn);
1061                 Log.w(TAG, "get(Active/Snoozed)Notifications: can't rebuild notification from " +
1062                         sbn.getPackageName());
1063             }
1064         }
1065         if (corruptNotifications != null) {
1066             list.removeAll(corruptNotifications);
1067         }
1068         return list.toArray(new StatusBarNotification[list.size()]);
1069     }
1070 
1071     /**
1072      * Gets the set of hints representing current state.
1073      *
1074      * <p>
1075      * The current state may differ from the requested state if the hint represents state
1076      * shared across all listeners or a feature the notification host does not support or refuses
1077      * to grant.
1078      *
1079      * <p>The service should wait for the {@link #onListenerConnected()} event
1080      * before performing this operation.
1081      *
1082      * @return Zero or more of the HINT_ constants.
1083      */
getCurrentListenerHints()1084     public final int getCurrentListenerHints() {
1085         if (!isBound()) return 0;
1086         try {
1087             return getNotificationInterface().getHintsFromListener(mWrapper);
1088         } catch (android.os.RemoteException ex) {
1089             Log.v(TAG, "Unable to contact notification manager", ex);
1090             return 0;
1091         }
1092     }
1093 
1094     /**
1095      * Gets the current notification interruption filter active on the host.
1096      *
1097      * <p>
1098      * The interruption filter defines which notifications are allowed to interrupt the user
1099      * (e.g. via sound &amp; vibration) and is applied globally. Listeners can find out whether
1100      * a specific notification matched the interruption filter via
1101      * {@link Ranking#matchesInterruptionFilter()}.
1102      * <p>
1103      * The current filter may differ from the previously requested filter if the notification host
1104      * does not support or refuses to apply the requested filter, or if another component changed
1105      * the filter in the meantime.
1106      * <p>
1107      * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
1108      *
1109      * <p>The service should wait for the {@link #onListenerConnected()} event
1110      * before performing this operation.
1111      *
1112      * @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when
1113      * unavailable.
1114      */
getCurrentInterruptionFilter()1115     public final int getCurrentInterruptionFilter() {
1116         if (!isBound()) return INTERRUPTION_FILTER_UNKNOWN;
1117         try {
1118             return getNotificationInterface().getInterruptionFilterFromListener(mWrapper);
1119         } catch (android.os.RemoteException ex) {
1120             Log.v(TAG, "Unable to contact notification manager", ex);
1121             return INTERRUPTION_FILTER_UNKNOWN;
1122         }
1123     }
1124 
1125     /**
1126      * Clears listener hints set via {@link #getCurrentListenerHints()}.
1127      *
1128      * <p>The service should wait for the {@link #onListenerConnected()} event
1129      * before performing this operation.
1130      */
clearRequestedListenerHints()1131     public final void clearRequestedListenerHints() {
1132         if (!isBound()) return;
1133         try {
1134             getNotificationInterface().clearRequestedListenerHints(mWrapper);
1135         } catch (android.os.RemoteException ex) {
1136             Log.v(TAG, "Unable to contact notification manager", ex);
1137         }
1138     }
1139 
1140     /**
1141      * Sets the desired {@link #getCurrentListenerHints() listener hints}.
1142      *
1143      * <p>
1144      * This is merely a request, the host may or may not choose to take action depending
1145      * on other listener requests or other global state.
1146      * <p>
1147      * Listen for updates using {@link #onListenerHintsChanged(int)}.
1148      *
1149      * <p>The service should wait for the {@link #onListenerConnected()} event
1150      * before performing this operation.
1151      *
1152      * @param hints One or more of the HINT_ constants.
1153      */
requestListenerHints(int hints)1154     public final void requestListenerHints(int hints) {
1155         if (!isBound()) return;
1156         try {
1157             getNotificationInterface().requestHintsFromListener(mWrapper, hints);
1158         } catch (android.os.RemoteException ex) {
1159             Log.v(TAG, "Unable to contact notification manager", ex);
1160         }
1161     }
1162 
1163     /**
1164      * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}.
1165      *
1166      * <p>
1167      * This is merely a request, the host may or may not choose to apply the requested
1168      * interruption filter depending on other listener requests or other global state.
1169      * <p>
1170      * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
1171      *
1172      * <p>The service should wait for the {@link #onListenerConnected()} event
1173      * before performing this operation.
1174      *
1175      * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants.
1176      */
requestInterruptionFilter(int interruptionFilter)1177     public final void requestInterruptionFilter(int interruptionFilter) {
1178         if (!isBound()) return;
1179         try {
1180             getNotificationInterface()
1181                     .requestInterruptionFilterFromListener(mWrapper, interruptionFilter);
1182         } catch (android.os.RemoteException ex) {
1183             Log.v(TAG, "Unable to contact notification manager", ex);
1184         }
1185     }
1186 
1187     /**
1188      * Returns current ranking information.
1189      *
1190      * <p>
1191      * The returned object represents the current ranking snapshot and only
1192      * applies for currently active notifications.
1193      * <p>
1194      * Generally you should use the RankingMap that is passed with events such
1195      * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)},
1196      * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and
1197      * so on. This method should only be used when needing access outside of
1198      * such events, for example to retrieve the RankingMap right after
1199      * initialization.
1200      *
1201      * <p>The service should wait for the {@link #onListenerConnected()} event
1202      * before performing this operation.
1203      *
1204      * @return A {@link RankingMap} object providing access to ranking information
1205      */
getCurrentRanking()1206     public RankingMap getCurrentRanking() {
1207         synchronized (mLock) {
1208             return mRankingMap;
1209         }
1210     }
1211 
1212     /**
1213      * This is not the lifecycle event you are looking for.
1214      *
1215      * <p>The service should wait for the {@link #onListenerConnected()} event
1216      * before performing any operations.
1217      */
1218     @Override
onBind(Intent intent)1219     public IBinder onBind(Intent intent) {
1220         if (mWrapper == null) {
1221             mWrapper = new NotificationListenerWrapper();
1222         }
1223         return mWrapper;
1224     }
1225 
1226     /** @hide */
1227     @UnsupportedAppUsage
isBound()1228     protected boolean isBound() {
1229         if (mWrapper == null) {
1230             Log.w(TAG, "Notification listener service not yet bound.");
1231             return false;
1232         }
1233         return true;
1234     }
1235 
1236     @Override
onDestroy()1237     public void onDestroy() {
1238         onListenerDisconnected();
1239         super.onDestroy();
1240     }
1241 
1242     /**
1243      * Directly register this service with the Notification Manager.
1244      *
1245      * <p>Only system services may use this call. It will fail for non-system callers.
1246      * Apps should ask the user to add their listener in Settings.
1247      *
1248      * @param context Context required for accessing resources. Since this service isn't
1249      *    launched as a real Service when using this method, a context has to be passed in.
1250      * @param componentName the component that will consume the notification information
1251      * @param currentUser the user to use as the stream filter
1252      * @hide
1253      * @removed
1254      */
1255     @SystemApi
registerAsSystemService(Context context, ComponentName componentName, int currentUser)1256     public void registerAsSystemService(Context context, ComponentName componentName,
1257             int currentUser) throws RemoteException {
1258         if (mWrapper == null) {
1259             mWrapper = new NotificationListenerWrapper();
1260         }
1261         mSystemContext = context;
1262         INotificationManager noMan = getNotificationInterface();
1263         mHandler = new MyHandler(context.getMainLooper());
1264         mCurrentUser = currentUser;
1265         noMan.registerListener(mWrapper, componentName, currentUser);
1266     }
1267 
1268     /**
1269      * Directly unregister this service from the Notification Manager.
1270      *
1271      * <p>This method will fail for listeners that were not registered
1272      * with (@link registerAsService).
1273      * @hide
1274      * @removed
1275      */
1276     @SystemApi
unregisterAsSystemService()1277     public void unregisterAsSystemService() throws RemoteException {
1278         if (mWrapper != null) {
1279             INotificationManager noMan = getNotificationInterface();
1280             noMan.unregisterListener(mWrapper, mCurrentUser);
1281         }
1282     }
1283 
1284     /**
1285      * Request that the listener be rebound, after a previous call to {@link #requestUnbind}.
1286      *
1287      * <p>This method will fail for listeners that have
1288      * not been granted the permission by the user.
1289      */
requestRebind(ComponentName componentName)1290     public static void requestRebind(ComponentName componentName) {
1291         INotificationManager noMan = INotificationManager.Stub.asInterface(
1292                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
1293         try {
1294             noMan.requestBindListener(componentName);
1295         } catch (RemoteException ex) {
1296             throw ex.rethrowFromSystemServer();
1297         }
1298     }
1299 
1300     /**
1301      * Request that the service be unbound.
1302      *
1303      * <p>Once this is called, you will no longer receive updates and no method calls are
1304      * guaranteed to be successful, until you next receive the {@link #onListenerConnected()} event.
1305      * The service will likely be killed by the system after this call.
1306      *
1307      * <p>The service should wait for the {@link #onListenerConnected()} event
1308      * before performing this operation. I know it's tempting, but you must wait.
1309      */
requestUnbind()1310     public final void requestUnbind() {
1311         if (mWrapper != null) {
1312             INotificationManager noMan = getNotificationInterface();
1313             try {
1314                 noMan.requestUnbindListener(mWrapper);
1315                 // Disable future messages.
1316                 isConnected = false;
1317             } catch (RemoteException ex) {
1318                 throw ex.rethrowFromSystemServer();
1319             }
1320         }
1321     }
1322 
1323     /**
1324      * Convert new-style Icons to legacy representations for pre-M clients.
1325      * @hide
1326      */
createLegacyIconExtras(Notification n)1327     public final void createLegacyIconExtras(Notification n) {
1328         if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.M) {
1329             Icon smallIcon = n.getSmallIcon();
1330             Icon largeIcon = n.getLargeIcon();
1331             if (smallIcon != null && smallIcon.getType() == Icon.TYPE_RESOURCE) {
1332                 n.extras.putInt(Notification.EXTRA_SMALL_ICON, smallIcon.getResId());
1333                 n.icon = smallIcon.getResId();
1334             }
1335             if (largeIcon != null) {
1336                 Drawable d = largeIcon.loadDrawable(getContext());
1337                 if (d != null && d instanceof BitmapDrawable) {
1338                     final Bitmap largeIconBits = ((BitmapDrawable) d).getBitmap();
1339                     n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, largeIconBits);
1340                     n.largeIcon = largeIconBits;
1341                 }
1342             }
1343         }
1344     }
1345 
1346     /**
1347      * Populates remote views for pre-N targeting apps.
1348      */
maybePopulateRemoteViews(Notification notification)1349     private void maybePopulateRemoteViews(Notification notification) {
1350         if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
1351             Builder builder = Builder.recoverBuilder(getContext(), notification);
1352 
1353             // Some styles wrap Notification's contentView, bigContentView and headsUpContentView.
1354             // First inflate them all, only then set them to avoid recursive wrapping.
1355             RemoteViews content = builder.createContentView();
1356             RemoteViews big = builder.createBigContentView();
1357             RemoteViews headsUp = builder.createHeadsUpContentView();
1358 
1359             notification.contentView = content;
1360             notification.bigContentView = big;
1361             notification.headsUpContentView = headsUp;
1362         }
1363     }
1364 
1365     /**
1366      * Populates remote views for pre-P targeting apps.
1367      */
maybePopulatePeople(Notification notification)1368     private void maybePopulatePeople(Notification notification) {
1369         if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P) {
1370             ArrayList<Person> people = notification.extras.getParcelableArrayList(
1371                     Notification.EXTRA_PEOPLE_LIST);
1372             if (people != null && people.isEmpty()) {
1373                 int size = people.size();
1374                 String[] peopleArray = new String[size];
1375                 for (int i = 0; i < size; i++) {
1376                     Person person = people.get(i);
1377                     peopleArray[i] = person.resolveToLegacyUri();
1378                 }
1379                 notification.extras.putStringArray(Notification.EXTRA_PEOPLE, peopleArray);
1380             }
1381         }
1382     }
1383 
1384     /** @hide */
1385     protected class NotificationListenerWrapper extends INotificationListener.Stub {
1386         @Override
onNotificationPosted(IStatusBarNotificationHolder sbnHolder, NotificationRankingUpdate update)1387         public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
1388                 NotificationRankingUpdate update) {
1389             StatusBarNotification sbn;
1390             try {
1391                 sbn = sbnHolder.get();
1392             } catch (RemoteException e) {
1393                 Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
1394                 return;
1395             }
1396             if (sbn == null) {
1397                 Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification");
1398                 return;
1399             }
1400 
1401             try {
1402                 // convert icon metadata to legacy format for older clients
1403                 createLegacyIconExtras(sbn.getNotification());
1404                 maybePopulateRemoteViews(sbn.getNotification());
1405                 maybePopulatePeople(sbn.getNotification());
1406             } catch (IllegalArgumentException e) {
1407                 // warn and drop corrupt notification
1408                 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
1409                         sbn.getPackageName());
1410                 sbn = null;
1411             }
1412 
1413             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
1414             synchronized (mLock) {
1415                 applyUpdateLocked(update);
1416                 if (sbn != null) {
1417                     SomeArgs args = SomeArgs.obtain();
1418                     args.arg1 = sbn;
1419                     args.arg2 = mRankingMap;
1420                     mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
1421                             args).sendToTarget();
1422                 } else {
1423                     // still pass along the ranking map, it may contain other information
1424                     mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
1425                             mRankingMap).sendToTarget();
1426                 }
1427             }
1428 
1429         }
1430 
1431         @Override
onNotificationRemoved(IStatusBarNotificationHolder sbnHolder, NotificationRankingUpdate update, NotificationStats stats, int reason)1432         public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
1433                 NotificationRankingUpdate update, NotificationStats stats, int reason) {
1434             StatusBarNotification sbn;
1435             try {
1436                 sbn = sbnHolder.get();
1437             } catch (RemoteException e) {
1438                 Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e);
1439                 return;
1440             }
1441             if (sbn == null) {
1442                 Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification");
1443                 return;
1444             }
1445             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
1446             synchronized (mLock) {
1447                 applyUpdateLocked(update);
1448                 SomeArgs args = SomeArgs.obtain();
1449                 args.arg1 = sbn;
1450                 args.arg2 = mRankingMap;
1451                 args.arg3 = reason;
1452                 args.arg4 = stats;
1453                 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED,
1454                         args).sendToTarget();
1455             }
1456 
1457         }
1458 
1459         @Override
onListenerConnected(NotificationRankingUpdate update)1460         public void onListenerConnected(NotificationRankingUpdate update) {
1461             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
1462             synchronized (mLock) {
1463                 applyUpdateLocked(update);
1464             }
1465             isConnected = true;
1466             mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_CONNECTED).sendToTarget();
1467         }
1468 
1469         @Override
onNotificationRankingUpdate(NotificationRankingUpdate update)1470         public void onNotificationRankingUpdate(NotificationRankingUpdate update)
1471                 throws RemoteException {
1472             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
1473             synchronized (mLock) {
1474                 applyUpdateLocked(update);
1475                 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
1476                         mRankingMap).sendToTarget();
1477             }
1478 
1479         }
1480 
1481         @Override
onListenerHintsChanged(int hints)1482         public void onListenerHintsChanged(int hints) throws RemoteException {
1483             mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_HINTS_CHANGED,
1484                     hints, 0).sendToTarget();
1485         }
1486 
1487         @Override
onInterruptionFilterChanged(int interruptionFilter)1488         public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException {
1489             mHandler.obtainMessage(MyHandler.MSG_ON_INTERRUPTION_FILTER_CHANGED,
1490                     interruptionFilter, 0).sendToTarget();
1491         }
1492 
1493         @Override
onNotificationEnqueuedWithChannel( IStatusBarNotificationHolder notificationHolder, NotificationChannel channel, NotificationRankingUpdate update)1494         public void onNotificationEnqueuedWithChannel(
1495                 IStatusBarNotificationHolder notificationHolder, NotificationChannel channel,
1496                 NotificationRankingUpdate update)
1497                 throws RemoteException {
1498             // no-op in the listener
1499         }
1500 
1501         @Override
onNotificationsSeen(List<String> keys)1502         public void onNotificationsSeen(List<String> keys)
1503                 throws RemoteException {
1504             // no-op in the listener
1505         }
1506 
1507         @Override
onPanelRevealed(int items)1508         public void onPanelRevealed(int items) throws RemoteException {
1509             // no-op in the listener
1510         }
1511 
1512         @Override
onPanelHidden()1513         public void onPanelHidden() throws RemoteException {
1514             // no-op in the listener
1515         }
1516 
1517         @Override
onNotificationVisibilityChanged( String key, boolean isVisible)1518         public void onNotificationVisibilityChanged(
1519                 String key, boolean isVisible) {
1520             // no-op in the listener
1521         }
1522 
1523         @Override
onNotificationSnoozedUntilContext( IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId)1524         public void onNotificationSnoozedUntilContext(
1525                 IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId)
1526                 throws RemoteException {
1527             // no-op in the listener
1528         }
1529 
1530         @Override
onNotificationExpansionChanged( String key, boolean isUserAction, boolean isExpanded)1531         public void onNotificationExpansionChanged(
1532                 String key, boolean isUserAction, boolean isExpanded) {
1533             // no-op in the listener
1534         }
1535 
1536         @Override
onNotificationDirectReply(String key)1537         public void onNotificationDirectReply(String key) {
1538             // no-op in the listener
1539         }
1540 
1541         @Override
onSuggestedReplySent(String key, CharSequence reply, int source)1542         public void onSuggestedReplySent(String key, CharSequence reply, int source) {
1543             // no-op in the listener
1544         }
1545 
1546         @Override
onActionClicked(String key, Notification.Action action, int source)1547         public void onActionClicked(String key, Notification.Action action, int source) {
1548             // no-op in the listener
1549         }
1550 
1551         @Override
onNotificationClicked(String key)1552         public void onNotificationClicked(String key) {
1553             // no-op in the listener
1554         }
1555 
1556         @Override
onAllowedAdjustmentsChanged()1557         public void onAllowedAdjustmentsChanged() {
1558             // no-op in the listener
1559         }
1560 
1561         @Override
onNotificationChannelModification(String pkgName, UserHandle user, NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType)1562         public void onNotificationChannelModification(String pkgName, UserHandle user,
1563                 NotificationChannel channel,
1564                 @ChannelOrGroupModificationTypes int modificationType) {
1565             SomeArgs args = SomeArgs.obtain();
1566             args.arg1 = pkgName;
1567             args.arg2 = user;
1568             args.arg3 = channel;
1569             args.arg4 = modificationType;
1570             mHandler.obtainMessage(
1571                     MyHandler.MSG_ON_NOTIFICATION_CHANNEL_MODIFIED, args).sendToTarget();
1572         }
1573 
1574         @Override
onNotificationChannelGroupModification(String pkgName, UserHandle user, NotificationChannelGroup group, @ChannelOrGroupModificationTypes int modificationType)1575         public void onNotificationChannelGroupModification(String pkgName, UserHandle user,
1576                 NotificationChannelGroup group,
1577                 @ChannelOrGroupModificationTypes int modificationType) {
1578             SomeArgs args = SomeArgs.obtain();
1579             args.arg1 = pkgName;
1580             args.arg2 = user;
1581             args.arg3 = group;
1582             args.arg4 = modificationType;
1583             mHandler.obtainMessage(
1584                     MyHandler.MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED, args).sendToTarget();
1585         }
1586 
1587         @Override
onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons)1588         public void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) {
1589             mHandler.obtainMessage(MyHandler.MSG_ON_STATUS_BAR_ICON_BEHAVIOR_CHANGED,
1590                     hideSilentStatusIcons).sendToTarget();
1591         }
1592 
1593         @Override
onNotificationFeedbackReceived(String key, NotificationRankingUpdate update, Bundle feedback)1594         public void onNotificationFeedbackReceived(String key, NotificationRankingUpdate update,
1595                 Bundle feedback) {
1596             // no-op in the listener
1597         }
1598 
1599 
1600     }
1601 
1602     /**
1603      * @hide
1604      */
1605     @GuardedBy("mLock")
applyUpdateLocked(NotificationRankingUpdate update)1606     public final void applyUpdateLocked(NotificationRankingUpdate update) {
1607         mRankingMap = update.getRankingMap();
1608     }
1609 
1610     /** @hide */
getContext()1611     protected Context getContext() {
1612         if (mSystemContext != null) {
1613             return mSystemContext;
1614         }
1615         return this;
1616     }
1617 
1618     /**
1619      * Stores ranking related information on a currently active notification.
1620      *
1621      * <p>
1622      * Ranking objects aren't automatically updated as notification events
1623      * occur. Instead, ranking information has to be retrieved again via the
1624      * current {@link RankingMap}.
1625      */
1626     public static class Ranking {
1627 
1628         /**
1629          * Value signifying that the user and device policy manager have not expressed a lockscreen
1630          * visibility override for a notification.
1631          */
1632         public static final int VISIBILITY_NO_OVERRIDE = NotificationManager.VISIBILITY_NO_OVERRIDE;
1633 
1634         /**
1635          * The user is likely to have a negative reaction to this notification.
1636          */
1637         public static final int USER_SENTIMENT_NEGATIVE = -1;
1638         /**
1639          * It is not known how the user will react to this notification.
1640          */
1641         public static final int USER_SENTIMENT_NEUTRAL = 0;
1642         /**
1643          * The user is likely to have a positive reaction to this notification.
1644          */
1645         public static final int USER_SENTIMENT_POSITIVE = 1;
1646 
1647        /** @hide */
1648         @IntDef(prefix = { "USER_SENTIMENT_" }, value = {
1649                 USER_SENTIMENT_NEGATIVE, USER_SENTIMENT_NEUTRAL, USER_SENTIMENT_POSITIVE
1650         })
1651         @Retention(RetentionPolicy.SOURCE)
1652         public @interface UserSentiment {}
1653 
1654         /**
1655          * Notification was demoted in shade
1656          * @hide
1657          */
1658         public static final int RANKING_DEMOTED = -1;
1659         /**
1660          * Notification was unchanged
1661          * @hide
1662          */
1663         public static final int RANKING_UNCHANGED = 0;
1664         /**
1665          * Notification was promoted in shade
1666          * @hide
1667          */
1668         public static final int RANKING_PROMOTED = 1;
1669 
1670         /** @hide */
1671         @IntDef(prefix = { "RANKING_" }, value = {
1672                 RANKING_PROMOTED, RANKING_DEMOTED, RANKING_UNCHANGED
1673         })
1674         @Retention(RetentionPolicy.SOURCE)
1675         public @interface RankingAdjustment {}
1676 
1677         private @NonNull String mKey;
1678         private int mRank = -1;
1679         private boolean mIsAmbient;
1680         private boolean mMatchesInterruptionFilter;
1681         private int mVisibilityOverride;
1682         private int mSuppressedVisualEffects;
1683         private @NotificationManager.Importance int mImportance;
1684         private CharSequence mImportanceExplanation;
1685         private float mRankingScore;
1686         // System specified group key.
1687         private String mOverrideGroupKey;
1688         // Notification assistant channel override.
1689         private NotificationChannel mChannel;
1690         // Notification assistant people override.
1691         private ArrayList<String> mOverridePeople;
1692         // Notification assistant snooze criteria.
1693         private ArrayList<SnoozeCriterion> mSnoozeCriteria;
1694         private boolean mShowBadge;
1695         private @UserSentiment int mUserSentiment = USER_SENTIMENT_NEUTRAL;
1696         private boolean mHidden;
1697         private long mLastAudiblyAlertedMs;
1698         private boolean mNoisy;
1699         private ArrayList<Notification.Action> mSmartActions;
1700         private ArrayList<CharSequence> mSmartReplies;
1701         private boolean mCanBubble;
1702         private boolean mIsTextChanged;
1703         private boolean mIsConversation;
1704         private ShortcutInfo mShortcutInfo;
1705         private @RankingAdjustment int mRankingAdjustment;
1706         private boolean mIsBubble;
1707 
1708         private static final int PARCEL_VERSION = 2;
1709 
Ranking()1710         public Ranking() { }
1711 
1712         // You can parcel it, but it's not Parcelable
1713         /** @hide */
1714         @VisibleForTesting
writeToParcel(Parcel out, int flags)1715         public void writeToParcel(Parcel out, int flags) {
1716             final long start = out.dataPosition();
1717             out.writeInt(PARCEL_VERSION);
1718             out.writeString(mKey);
1719             out.writeInt(mRank);
1720             out.writeBoolean(mIsAmbient);
1721             out.writeBoolean(mMatchesInterruptionFilter);
1722             out.writeInt(mVisibilityOverride);
1723             out.writeInt(mSuppressedVisualEffects);
1724             out.writeInt(mImportance);
1725             out.writeCharSequence(mImportanceExplanation);
1726             out.writeFloat(mRankingScore);
1727             out.writeString(mOverrideGroupKey);
1728             out.writeParcelable(mChannel, flags);
1729             out.writeStringList(mOverridePeople);
1730             out.writeTypedList(mSnoozeCriteria, flags);
1731             out.writeBoolean(mShowBadge);
1732             out.writeInt(mUserSentiment);
1733             out.writeBoolean(mHidden);
1734             out.writeLong(mLastAudiblyAlertedMs);
1735             out.writeBoolean(mNoisy);
1736             out.writeTypedList(mSmartActions, flags);
1737             out.writeCharSequenceList(mSmartReplies);
1738             out.writeBoolean(mCanBubble);
1739             out.writeBoolean(mIsTextChanged);
1740             out.writeBoolean(mIsConversation);
1741             out.writeParcelable(mShortcutInfo, flags);
1742             out.writeInt(mRankingAdjustment);
1743             out.writeBoolean(mIsBubble);
1744         }
1745 
1746         /** @hide */
1747         @VisibleForTesting
Ranking(Parcel in)1748         public Ranking(Parcel in) {
1749             final ClassLoader cl = getClass().getClassLoader();
1750 
1751             final int version = in.readInt();
1752             if (version != PARCEL_VERSION) {
1753                 throw new IllegalArgumentException("malformed Ranking parcel: " + in + " version "
1754                         + version + ", expected " + PARCEL_VERSION);
1755             }
1756             mKey = in.readString();
1757             mRank = in.readInt();
1758             mIsAmbient = in.readBoolean();
1759             mMatchesInterruptionFilter = in.readBoolean();
1760             mVisibilityOverride = in.readInt();
1761             mSuppressedVisualEffects = in.readInt();
1762             mImportance = in.readInt();
1763             mImportanceExplanation = in.readCharSequence(); // may be null
1764             mRankingScore = in.readFloat();
1765             mOverrideGroupKey = in.readString(); // may be null
1766             mChannel = in.readParcelable(cl); // may be null
1767             mOverridePeople = in.createStringArrayList();
1768             mSnoozeCriteria = in.createTypedArrayList(SnoozeCriterion.CREATOR);
1769             mShowBadge = in.readBoolean();
1770             mUserSentiment = in.readInt();
1771             mHidden = in.readBoolean();
1772             mLastAudiblyAlertedMs = in.readLong();
1773             mNoisy = in.readBoolean();
1774             mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR);
1775             mSmartReplies = in.readCharSequenceList();
1776             mCanBubble = in.readBoolean();
1777             mIsTextChanged = in.readBoolean();
1778             mIsConversation = in.readBoolean();
1779             mShortcutInfo = in.readParcelable(cl);
1780             mRankingAdjustment = in.readInt();
1781             mIsBubble = in.readBoolean();
1782         }
1783 
1784 
1785         /**
1786          * Returns the key of the notification this Ranking applies to.
1787          */
getKey()1788         public String getKey() {
1789             return mKey;
1790         }
1791 
1792         /**
1793          * Returns the rank of the notification.
1794          *
1795          * @return the rank of the notification, that is the 0-based index in
1796          *     the list of active notifications.
1797          */
getRank()1798         public int getRank() {
1799             return mRank;
1800         }
1801 
1802         /**
1803          * Returns whether the notification is an ambient notification, that is
1804          * a notification that doesn't require the user's immediate attention.
1805          */
isAmbient()1806         public boolean isAmbient() {
1807             return mIsAmbient;
1808         }
1809 
1810         /**
1811          * Returns the user or device policy manager specified visibility (see
1812          * {@link Notification#VISIBILITY_PRIVATE}, {@link Notification#VISIBILITY_PUBLIC},
1813          * {@link Notification#VISIBILITY_SECRET}) for this notification, or
1814          * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
1815          * no such preference has been expressed.
1816          */
1817         public @Notification.NotificationVisibilityOverride
getLockscreenVisibilityOverride()1818         int getLockscreenVisibilityOverride() {
1819             return mVisibilityOverride;
1820         }
1821 
1822         /**
1823          * Returns the type(s) of visual effects that should be suppressed for this notification.
1824          * See {@link NotificationManager.Policy}, e.g.
1825          * {@link NotificationManager.Policy#SUPPRESSED_EFFECT_LIGHTS}.
1826          */
getSuppressedVisualEffects()1827         public int getSuppressedVisualEffects() {
1828             return mSuppressedVisualEffects;
1829         }
1830 
1831         /**
1832          * Returns whether the notification matches the user's interruption
1833          * filter.
1834          *
1835          * @return {@code true} if the notification is allowed by the filter, or
1836          * {@code false} if it is blocked.
1837          */
matchesInterruptionFilter()1838         public boolean matchesInterruptionFilter() {
1839             return mMatchesInterruptionFilter;
1840         }
1841 
1842         /**
1843          * Returns the importance of the notification, which dictates its
1844          * modes of presentation, see: {@link NotificationManager#IMPORTANCE_DEFAULT}, etc.
1845          *
1846          * @return the importance of the notification
1847          */
getImportance()1848         public @NotificationManager.Importance int getImportance() {
1849             return mImportance;
1850         }
1851 
1852         /**
1853          * If the importance has been overridden by user preference, then this will be non-null,
1854          * and should be displayed to the user.
1855          *
1856          * @return the explanation for the importance, or null if it is the natural importance
1857          */
getImportanceExplanation()1858         public CharSequence getImportanceExplanation() {
1859             return mImportanceExplanation;
1860         }
1861 
1862         /**
1863          * Returns the ranking score provided by the {@link NotificationAssistantService} to
1864          * sort the notifications in the shade
1865          *
1866          * @return the ranking score of the notification, range from -1 to 1
1867          * @hide
1868          */
getRankingScore()1869         public float getRankingScore() {
1870             return mRankingScore;
1871         }
1872 
1873         /**
1874          * If the system has overridden the group key, then this will be non-null, and this
1875          * key should be used to bundle notifications.
1876          */
getOverrideGroupKey()1877         public String getOverrideGroupKey() {
1878             return mOverrideGroupKey;
1879         }
1880 
1881         /**
1882          * Returns the notification channel this notification was posted to, which dictates
1883          * notification behavior and presentation.
1884          */
getChannel()1885         public NotificationChannel getChannel() {
1886             return mChannel;
1887         }
1888 
1889         /**
1890          * Returns how the system thinks the user feels about notifications from the
1891          * channel provided by {@link #getChannel()}. You can use this information to expose
1892          * controls to help the user block this channel's notifications, if the sentiment is
1893          * {@link #USER_SENTIMENT_NEGATIVE}, or emphasize this notification if the sentiment is
1894          * {@link #USER_SENTIMENT_POSITIVE}.
1895          */
getUserSentiment()1896         public int getUserSentiment() {
1897             return mUserSentiment;
1898         }
1899 
1900         /**
1901          * If the {@link NotificationAssistantService} has added people to this notification, then
1902          * this will be non-null.
1903          * @hide
1904          * @removed
1905          */
1906         @SystemApi
getAdditionalPeople()1907         public List<String> getAdditionalPeople() {
1908             return mOverridePeople;
1909         }
1910 
1911         /**
1912          * Returns snooze criteria provided by the {@link NotificationAssistantService}. If your
1913          * user interface displays options for snoozing notifications these criteria should be
1914          * displayed as well.
1915          * @hide
1916          * @removed
1917          */
1918         @SystemApi
getSnoozeCriteria()1919         public List<SnoozeCriterion> getSnoozeCriteria() {
1920             return mSnoozeCriteria;
1921         }
1922 
1923         /**
1924          * Returns a list of smart {@link Notification.Action} that can be added by the
1925          * {@link NotificationAssistantService}
1926          */
getSmartActions()1927         public @NonNull List<Notification.Action> getSmartActions() {
1928             return mSmartActions == null ? Collections.emptyList() : mSmartActions;
1929         }
1930 
1931         /**
1932          * Returns a list of smart replies that can be added by the
1933          * {@link NotificationAssistantService}
1934          */
getSmartReplies()1935         public @NonNull List<CharSequence> getSmartReplies() {
1936             return mSmartReplies == null ? Collections.emptyList() : mSmartReplies;
1937         }
1938 
1939         /**
1940          * Returns whether this notification can be displayed as a badge.
1941          *
1942          * @return true if the notification can be displayed as a badge, false otherwise.
1943          */
canShowBadge()1944         public boolean canShowBadge() {
1945             return mShowBadge;
1946         }
1947 
1948         /**
1949          * Returns whether the app that posted this notification is suspended, so this notification
1950          * should be hidden.
1951          *
1952          * @return true if the notification should be hidden, false otherwise.
1953          */
isSuspended()1954         public boolean isSuspended() {
1955             return mHidden;
1956         }
1957 
1958         /**
1959          * Returns the last time this notification alerted the user via sound or vibration.
1960          *
1961          * @return the time of the last alerting behavior, in milliseconds.
1962          */
1963         @CurrentTimeMillisLong
getLastAudiblyAlertedMillis()1964         public long getLastAudiblyAlertedMillis() {
1965             return mLastAudiblyAlertedMs;
1966         }
1967 
1968         /**
1969          * Returns whether the user has allowed bubbles globally, at the app level, and at the
1970          * channel level for this notification.
1971          *
1972          * <p>This does not take into account the current importance of the notification, the
1973          * current DND state, or whether the posting app is foreground.</p>
1974          */
canBubble()1975         public boolean canBubble() {
1976             return mCanBubble;
1977         }
1978 
1979         /** @hide */
isTextChanged()1980         public boolean isTextChanged() {
1981             return mIsTextChanged;
1982         }
1983 
1984         /** @hide */
isNoisy()1985         public boolean isNoisy() {
1986             return mNoisy;
1987         }
1988 
1989         /**
1990          * Returns whether this notification is a conversation notification, and would appear
1991          * in the conversation section of the notification shade, on devices that separate that
1992          * type of notification.
1993          */
isConversation()1994         public boolean isConversation() {
1995             return mIsConversation;
1996         }
1997 
1998         /**
1999          * Returns whether this notification is actively a bubble.
2000          * @hide
2001          */
isBubble()2002         public boolean isBubble() {
2003             return mIsBubble;
2004         }
2005 
2006         /**
2007          * Returns the shortcut information associated with this notification, if it is a
2008          * {@link #isConversation() conversation notification}.
2009          * <p>This might be null even if the notification is a conversation notification, if
2010          * the posting app hasn't opted into the full conversation feature set yet.</p>
2011          */
getConversationShortcutInfo()2012         public @Nullable ShortcutInfo getConversationShortcutInfo() {
2013             return mShortcutInfo;
2014         }
2015 
2016         /**
2017          * Returns the intended transition to ranking passed by {@link NotificationAssistantService}
2018          * @hide
2019          */
getRankingAdjustment()2020         public @RankingAdjustment int getRankingAdjustment() {
2021             return mRankingAdjustment;
2022         }
2023 
2024         /**
2025          * @hide
2026          */
2027         @VisibleForTesting
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, int userSentiment, boolean hidden, long lastAudiblyAlertedMs, boolean noisy, ArrayList<Notification.Action> smartActions, ArrayList<CharSequence> smartReplies, boolean canBubble, boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo, int rankingAdjustment, boolean isBubble)2028         public void populate(String key, int rank, boolean matchesInterruptionFilter,
2029                 int visibilityOverride, int suppressedVisualEffects, int importance,
2030                 CharSequence explanation, String overrideGroupKey,
2031                 NotificationChannel channel, ArrayList<String> overridePeople,
2032                 ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
2033                 int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
2034                 boolean noisy, ArrayList<Notification.Action> smartActions,
2035                 ArrayList<CharSequence> smartReplies, boolean canBubble,
2036                 boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo,
2037                 int rankingAdjustment, boolean isBubble) {
2038             mKey = key;
2039             mRank = rank;
2040             mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
2041             mMatchesInterruptionFilter = matchesInterruptionFilter;
2042             mVisibilityOverride = visibilityOverride;
2043             mSuppressedVisualEffects = suppressedVisualEffects;
2044             mImportance = importance;
2045             mImportanceExplanation = explanation;
2046             mOverrideGroupKey = overrideGroupKey;
2047             mChannel = channel;
2048             mOverridePeople = overridePeople;
2049             mSnoozeCriteria = snoozeCriteria;
2050             mShowBadge = showBadge;
2051             mUserSentiment = userSentiment;
2052             mHidden = hidden;
2053             mLastAudiblyAlertedMs = lastAudiblyAlertedMs;
2054             mNoisy = noisy;
2055             mSmartActions = smartActions;
2056             mSmartReplies = smartReplies;
2057             mCanBubble = canBubble;
2058             mIsTextChanged = isTextChanged;
2059             mIsConversation = isConversation;
2060             mShortcutInfo = shortcutInfo;
2061             mRankingAdjustment = rankingAdjustment;
2062             mIsBubble = isBubble;
2063         }
2064 
2065         /**
2066          * @hide
2067          */
2068         public @NonNull Ranking withAudiblyAlertedInfo(@Nullable Ranking previous) {
2069             if (previous != null && previous.mLastAudiblyAlertedMs > 0
2070                     && this.mLastAudiblyAlertedMs <= 0) {
2071                 this.mLastAudiblyAlertedMs = previous.mLastAudiblyAlertedMs;
2072             }
2073             return this;
2074         }
2075 
2076         /**
2077          * @hide
2078          */
populate(Ranking other)2079         public void populate(Ranking other) {
2080             populate(other.mKey,
2081                     other.mRank,
2082                     other.mMatchesInterruptionFilter,
2083                     other.mVisibilityOverride,
2084                     other.mSuppressedVisualEffects,
2085                     other.mImportance,
2086                     other.mImportanceExplanation,
2087                     other.mOverrideGroupKey,
2088                     other.mChannel,
2089                     other.mOverridePeople,
2090                     other.mSnoozeCriteria,
2091                     other.mShowBadge,
2092                     other.mUserSentiment,
2093                     other.mHidden,
2094                     other.mLastAudiblyAlertedMs,
2095                     other.mNoisy,
2096                     other.mSmartActions,
2097                     other.mSmartReplies,
2098                     other.mCanBubble,
2099                     other.mIsTextChanged,
2100                     other.mIsConversation,
2101                     other.mShortcutInfo,
2102                     other.mRankingAdjustment,
2103                     other.mIsBubble);
2104         }
2105 
2106         /**
2107          * {@hide}
2108          */
importanceToString(int importance)2109         public static String importanceToString(int importance) {
2110             switch (importance) {
2111                 case NotificationManager.IMPORTANCE_UNSPECIFIED:
2112                     return "UNSPECIFIED";
2113                 case NotificationManager.IMPORTANCE_NONE:
2114                     return "NONE";
2115                 case NotificationManager.IMPORTANCE_MIN:
2116                     return "MIN";
2117                 case NotificationManager.IMPORTANCE_LOW:
2118                     return "LOW";
2119                 case NotificationManager.IMPORTANCE_DEFAULT:
2120                     return "DEFAULT";
2121                 case NotificationManager.IMPORTANCE_HIGH:
2122                 case NotificationManager.IMPORTANCE_MAX:
2123                     return "HIGH";
2124                 default:
2125                     return "UNKNOWN(" + String.valueOf(importance) + ")";
2126             }
2127         }
2128 
2129         @Override
equals(@ullable Object o)2130         public boolean equals(@Nullable Object o) {
2131             if (this == o) return true;
2132             if (o == null || getClass() != o.getClass()) return false;
2133 
2134             Ranking other = (Ranking) o;
2135             return Objects.equals(mKey, other.mKey)
2136                     && Objects.equals(mRank, other.mRank)
2137                     && Objects.equals(mMatchesInterruptionFilter, other.mMatchesInterruptionFilter)
2138                     && Objects.equals(mVisibilityOverride, other.mVisibilityOverride)
2139                     && Objects.equals(mSuppressedVisualEffects, other.mSuppressedVisualEffects)
2140                     && Objects.equals(mImportance, other.mImportance)
2141                     && Objects.equals(mImportanceExplanation, other.mImportanceExplanation)
2142                     && Objects.equals(mOverrideGroupKey, other.mOverrideGroupKey)
2143                     && Objects.equals(mChannel, other.mChannel)
2144                     && Objects.equals(mOverridePeople, other.mOverridePeople)
2145                     && Objects.equals(mSnoozeCriteria, other.mSnoozeCriteria)
2146                     && Objects.equals(mShowBadge, other.mShowBadge)
2147                     && Objects.equals(mUserSentiment, other.mUserSentiment)
2148                     && Objects.equals(mHidden, other.mHidden)
2149                     && Objects.equals(mLastAudiblyAlertedMs, other.mLastAudiblyAlertedMs)
2150                     && Objects.equals(mNoisy, other.mNoisy)
2151                     // Action.equals() doesn't exist so let's just compare list lengths
2152                     && ((mSmartActions == null ? 0 : mSmartActions.size())
2153                         == (other.mSmartActions == null ? 0 : other.mSmartActions.size()))
2154                     && Objects.equals(mSmartReplies, other.mSmartReplies)
2155                     && Objects.equals(mCanBubble, other.mCanBubble)
2156                     && Objects.equals(mIsTextChanged, other.mIsTextChanged)
2157                     && Objects.equals(mIsConversation, other.mIsConversation)
2158                     // Shortcutinfo doesn't have equals either; use id
2159                     &&  Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()),
2160                     (other.mShortcutInfo == null ? 0 : other.mShortcutInfo.getId()))
2161                     && Objects.equals(mRankingAdjustment, other.mRankingAdjustment)
2162                     && Objects.equals(mIsBubble, other.mIsBubble);
2163         }
2164     }
2165 
2166     /**
2167      * Provides access to ranking information on currently active
2168      * notifications.
2169      *
2170      * <p>
2171      * Note that this object represents a ranking snapshot that only applies to
2172      * notifications active at the time of retrieval.
2173      */
2174     public static class RankingMap implements Parcelable {
2175         private ArrayList<String> mOrderedKeys = new ArrayList<>();
2176         // Note: all String keys should be intern'd as pointers into mOrderedKeys
2177         private ArrayMap<String, Ranking> mRankings = new ArrayMap<>();
2178 
2179         /**
2180          * @hide
2181          */
RankingMap(Ranking[] rankings)2182         public RankingMap(Ranking[] rankings) {
2183             for (int i = 0; i < rankings.length; i++) {
2184                 final String key = rankings[i].getKey();
2185                 mOrderedKeys.add(key);
2186                 mRankings.put(key, rankings[i]);
2187             }
2188         }
2189 
2190         // -- parcelable interface --
2191 
RankingMap(Parcel in)2192         private RankingMap(Parcel in) {
2193             final ClassLoader cl = getClass().getClassLoader();
2194             final int count = in.readInt();
2195             mOrderedKeys.ensureCapacity(count);
2196             mRankings.ensureCapacity(count);
2197             for (int i = 0; i < count; i++) {
2198                 final Ranking r = new Ranking(in);
2199                 final String key = r.getKey();
2200                 mOrderedKeys.add(key);
2201                 mRankings.put(key, r);
2202             }
2203         }
2204 
2205         @Override
equals(@ullable Object o)2206         public boolean equals(@Nullable Object o) {
2207             if (this == o) return true;
2208             if (o == null || getClass() != o.getClass()) return false;
2209 
2210             RankingMap other = (RankingMap) o;
2211 
2212             return mOrderedKeys.equals(other.mOrderedKeys)
2213                     && mRankings.equals(other.mRankings);
2214 
2215         }
2216 
2217         @Override
describeContents()2218         public int describeContents() {
2219             return 0;
2220         }
2221 
2222         @Override
writeToParcel(Parcel out, int flags)2223         public void writeToParcel(Parcel out, int flags) {
2224             final int count = mOrderedKeys.size();
2225             out.writeInt(count);
2226             for (int i = 0; i < count; i++) {
2227                 mRankings.get(mOrderedKeys.get(i)).writeToParcel(out, flags);
2228             }
2229         }
2230 
2231         public static final @android.annotation.NonNull Creator<RankingMap> CREATOR = new Creator<RankingMap>() {
2232             @Override
2233             public RankingMap createFromParcel(Parcel source) {
2234                 return new RankingMap(source);
2235             }
2236 
2237             @Override
2238             public RankingMap[] newArray(int size) {
2239                 return new RankingMap[size];
2240             }
2241         };
2242 
2243         /**
2244          * Request the list of notification keys in their current ranking
2245          * order.
2246          *
2247          * @return An array of active notification keys, in their ranking order.
2248          */
getOrderedKeys()2249         public String[] getOrderedKeys() {
2250             return mOrderedKeys.toArray(new String[0]);
2251         }
2252 
2253         /**
2254          * Populates outRanking with ranking information for the notification
2255          * with the given key.
2256          *
2257          * @return true if a valid key has been passed and outRanking has
2258          * been populated; false otherwise
2259          */
getRanking(String key, Ranking outRanking)2260         public boolean getRanking(String key, Ranking outRanking) {
2261             if (mRankings.containsKey(key)) {
2262                 outRanking.populate(mRankings.get(key));
2263                 return true;
2264             }
2265             return false;
2266         }
2267 
2268         /**
2269          * Get a reference to the actual Ranking object corresponding to the key.
2270          * Used only by unit tests.
2271          *
2272          * @hide
2273          */
2274         @VisibleForTesting
getRawRankingObject(String key)2275         public Ranking getRawRankingObject(String key) {
2276             return mRankings.get(key);
2277         }
2278     }
2279 
2280     private final class MyHandler extends Handler {
2281         public static final int MSG_ON_NOTIFICATION_POSTED = 1;
2282         public static final int MSG_ON_NOTIFICATION_REMOVED = 2;
2283         public static final int MSG_ON_LISTENER_CONNECTED = 3;
2284         public static final int MSG_ON_NOTIFICATION_RANKING_UPDATE = 4;
2285         public static final int MSG_ON_LISTENER_HINTS_CHANGED = 5;
2286         public static final int MSG_ON_INTERRUPTION_FILTER_CHANGED = 6;
2287         public static final int MSG_ON_NOTIFICATION_CHANNEL_MODIFIED = 7;
2288         public static final int MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED = 8;
2289         public static final int MSG_ON_STATUS_BAR_ICON_BEHAVIOR_CHANGED = 9;
2290 
MyHandler(Looper looper)2291         public MyHandler(Looper looper) {
2292             super(looper, null, false);
2293         }
2294 
2295         @Override
handleMessage(Message msg)2296         public void handleMessage(Message msg) {
2297             if (!isConnected) {
2298                 return;
2299             }
2300             switch (msg.what) {
2301                 case MSG_ON_NOTIFICATION_POSTED: {
2302                     SomeArgs args = (SomeArgs) msg.obj;
2303                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
2304                     RankingMap rankingMap = (RankingMap) args.arg2;
2305                     args.recycle();
2306                     onNotificationPosted(sbn, rankingMap);
2307                 } break;
2308 
2309                 case MSG_ON_NOTIFICATION_REMOVED: {
2310                     SomeArgs args = (SomeArgs) msg.obj;
2311                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
2312                     RankingMap rankingMap = (RankingMap) args.arg2;
2313                     int reason = (int) args.arg3;
2314                     NotificationStats stats = (NotificationStats) args.arg4;
2315                     args.recycle();
2316                     onNotificationRemoved(sbn, rankingMap, stats, reason);
2317                 } break;
2318 
2319                 case MSG_ON_LISTENER_CONNECTED: {
2320                     onListenerConnected();
2321                 } break;
2322 
2323                 case MSG_ON_NOTIFICATION_RANKING_UPDATE: {
2324                     RankingMap rankingMap = (RankingMap) msg.obj;
2325                     onNotificationRankingUpdate(rankingMap);
2326                 } break;
2327 
2328                 case MSG_ON_LISTENER_HINTS_CHANGED: {
2329                     final int hints = msg.arg1;
2330                     onListenerHintsChanged(hints);
2331                 } break;
2332 
2333                 case MSG_ON_INTERRUPTION_FILTER_CHANGED: {
2334                     final int interruptionFilter = msg.arg1;
2335                     onInterruptionFilterChanged(interruptionFilter);
2336                 } break;
2337 
2338                 case MSG_ON_NOTIFICATION_CHANNEL_MODIFIED: {
2339                     SomeArgs args = (SomeArgs) msg.obj;
2340                     String pkgName = (String) args.arg1;
2341                     UserHandle user= (UserHandle) args.arg2;
2342                     NotificationChannel channel = (NotificationChannel) args.arg3;
2343                     int modificationType = (int) args.arg4;
2344                     onNotificationChannelModified(pkgName, user, channel, modificationType);
2345                 } break;
2346 
2347                 case MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED: {
2348                     SomeArgs args = (SomeArgs) msg.obj;
2349                     String pkgName = (String) args.arg1;
2350                     UserHandle user = (UserHandle) args.arg2;
2351                     NotificationChannelGroup group = (NotificationChannelGroup) args.arg3;
2352                     int modificationType = (int) args.arg4;
2353                     onNotificationChannelGroupModified(pkgName, user, group, modificationType);
2354                 } break;
2355 
2356                 case MSG_ON_STATUS_BAR_ICON_BEHAVIOR_CHANGED: {
2357                     onSilentStatusBarIconsVisibilityChanged((Boolean) msg.obj);
2358                 } break;
2359             }
2360         }
2361     }
2362 }
2363