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