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