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