• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.service.notification;
18 
19 import android.os.Handler;
20 import android.os.Looper;
21 import android.os.Message;
22 
23 import android.annotation.IntDef;
24 import android.annotation.SystemApi;
25 import android.annotation.SdkConstant;
26 import android.app.INotificationManager;
27 import android.app.Notification;
28 import android.app.Notification.Builder;
29 import android.app.NotificationManager;
30 import android.app.Service;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.pm.ParceledListSlice;
35 import android.graphics.drawable.BitmapDrawable;
36 import android.graphics.drawable.Drawable;
37 import android.graphics.drawable.Icon;
38 import android.graphics.Bitmap;
39 import android.os.Build;
40 import android.os.Bundle;
41 import android.os.IBinder;
42 import android.os.Parcel;
43 import android.os.Parcelable;
44 import android.os.RemoteException;
45 import android.os.ServiceManager;
46 import android.util.ArrayMap;
47 import android.util.ArraySet;
48 import android.util.Log;
49 import android.widget.RemoteViews;
50 import com.android.internal.annotations.GuardedBy;
51 import com.android.internal.os.SomeArgs;
52 import java.lang.annotation.Retention;
53 import java.lang.annotation.RetentionPolicy;
54 import java.util.ArrayList;
55 import java.util.Collections;
56 import java.util.List;
57 
58 /**
59  * A service that receives calls from the system when new notifications are
60  * posted or removed, or their ranking changed.
61  * <p>To extend this class, you must declare the service in your manifest file with
62  * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission
63  * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
64  * <pre>
65  * &lt;service android:name=".NotificationListener"
66  *          android:label="&#64;string/service_name"
67  *          android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
68  *     &lt;intent-filter>
69  *         &lt;action android:name="android.service.notification.NotificationListenerService" />
70  *     &lt;/intent-filter>
71  * &lt;/service></pre>
72  *
73  * <p>The service should wait for the {@link #onListenerConnected()} event
74  * before performing any operations. The {@link #requestRebind(ComponentName)}
75  * method is the <i>only</i> one that is safe to call before {@link #onListenerConnected()}
76  * or after {@link #onListenerDisconnected()}.
77  * </p>
78  */
79 public abstract class NotificationListenerService extends Service {
80     // TAG = "NotificationListenerService[MySubclass]"
81     private final String TAG = NotificationListenerService.class.getSimpleName()
82             + "[" + getClass().getSimpleName() + "]";
83 
84     /**
85      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
86      *     Normal interruption filter.
87      */
88     public static final int INTERRUPTION_FILTER_ALL
89             = NotificationManager.INTERRUPTION_FILTER_ALL;
90 
91     /**
92      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
93      *     Priority interruption filter.
94      */
95     public static final int INTERRUPTION_FILTER_PRIORITY
96             = NotificationManager.INTERRUPTION_FILTER_PRIORITY;
97 
98     /**
99      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
100      *     No interruptions filter.
101      */
102     public static final int INTERRUPTION_FILTER_NONE
103             = NotificationManager.INTERRUPTION_FILTER_NONE;
104 
105     /**
106      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
107      *     Alarms only interruption filter.
108      */
109     public static final int INTERRUPTION_FILTER_ALARMS
110             = NotificationManager.INTERRUPTION_FILTER_ALARMS;
111 
112     /** {@link #getCurrentInterruptionFilter() Interruption filter} constant - returned when
113      * the value is unavailable for any reason.  For example, before the notification listener
114      * is connected.
115      *
116      * {@see #onListenerConnected()}
117      */
118     public static final int INTERRUPTION_FILTER_UNKNOWN
119             = NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
120 
121     /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
122      * should disable notification sound, vibrating and other visual or aural effects.
123      * This does not change the interruption filter, only the effects. **/
124     public static final int HINT_HOST_DISABLE_EFFECTS = 1;
125 
126     /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
127      * should disable notification sound, but not phone calls.
128      * This does not change the interruption filter, only the effects. **/
129     public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 1 << 1;
130 
131     /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
132      * should disable phone call sounds, buyt not notification sound.
133      * This does not change the interruption filter, only the effects. **/
134     public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 1 << 2;
135 
136     /**
137      * Whether notification suppressed by DND should not interruption visually when the screen is
138      * off.
139      */
140     public static final int SUPPRESSED_EFFECT_SCREEN_OFF =
141             NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
142     /**
143      * Whether notification suppressed by DND should not interruption visually when the screen is
144      * on.
145      */
146     public static final int SUPPRESSED_EFFECT_SCREEN_ON =
147             NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
148 
149     /**
150      * The full trim of the StatusBarNotification including all its features.
151      *
152      * @hide
153      */
154     @SystemApi
155     public static final int TRIM_FULL = 0;
156 
157     /**
158      * A light trim of the StatusBarNotification excluding the following features:
159      *
160      * <ol>
161      *     <li>{@link Notification#tickerView tickerView}</li>
162      *     <li>{@link Notification#contentView contentView}</li>
163      *     <li>{@link Notification#largeIcon largeIcon}</li>
164      *     <li>{@link Notification#bigContentView bigContentView}</li>
165      *     <li>{@link Notification#headsUpContentView headsUpContentView}</li>
166      *     <li>{@link Notification#EXTRA_LARGE_ICON extras[EXTRA_LARGE_ICON]}</li>
167      *     <li>{@link Notification#EXTRA_LARGE_ICON_BIG extras[EXTRA_LARGE_ICON_BIG]}</li>
168      *     <li>{@link Notification#EXTRA_PICTURE extras[EXTRA_PICTURE]}</li>
169      *     <li>{@link Notification#EXTRA_BIG_TEXT extras[EXTRA_BIG_TEXT]}</li>
170      * </ol>
171      *
172      * @hide
173      */
174     @SystemApi
175     public static final int TRIM_LIGHT = 1;
176 
177     private final Object mLock = new Object();
178 
179     private Handler mHandler;
180 
181     /** @hide */
182     protected NotificationListenerWrapper mWrapper = null;
183     private boolean isConnected = false;
184 
185     @GuardedBy("mLock")
186     private RankingMap mRankingMap;
187 
188     private INotificationManager mNoMan;
189 
190     /**
191      * Only valid after a successful call to (@link registerAsService}.
192      * @hide
193      */
194     protected int mCurrentUser;
195 
196     /**
197      * This context is required for system services since NotificationListenerService isn't
198      * started as a real Service and hence no context is available..
199      * @hide
200      */
201     protected Context mSystemContext;
202 
203     /**
204      * The {@link Intent} that must be declared as handled by the service.
205      */
206     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
207     public static final String SERVICE_INTERFACE
208             = "android.service.notification.NotificationListenerService";
209 
210     @Override
attachBaseContext(Context base)211     protected void attachBaseContext(Context base) {
212         super.attachBaseContext(base);
213         mHandler = new MyHandler(getMainLooper());
214     }
215 
216     /**
217      * Implement this method to learn about new notifications as they are posted by apps.
218      *
219      * @param sbn A data structure encapsulating the original {@link android.app.Notification}
220      *            object as well as its identifying information (tag and id) and source
221      *            (package name).
222      */
onNotificationPosted(StatusBarNotification sbn)223     public void onNotificationPosted(StatusBarNotification sbn) {
224         // optional
225     }
226 
227     /**
228      * Implement this method to learn about new notifications as they are posted by apps.
229      *
230      * @param sbn A data structure encapsulating the original {@link android.app.Notification}
231      *            object as well as its identifying information (tag and id) and source
232      *            (package name).
233      * @param rankingMap The current ranking map that can be used to retrieve ranking information
234      *                   for active notifications, including the newly posted one.
235      */
onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap)236     public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
237         onNotificationPosted(sbn);
238     }
239 
240     /**
241      * Implement this method to learn when notifications are removed.
242      * <p>
243      * This might occur because the user has dismissed the notification using system UI (or another
244      * notification listener) or because the app has withdrawn the notification.
245      * <p>
246      * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
247      * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
248      * fields such as {@link android.app.Notification#contentView} and
249      * {@link android.app.Notification#largeIcon}. However, all other fields on
250      * {@link StatusBarNotification}, sufficient to match this call with a prior call to
251      * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
252      *
253      * @param sbn A data structure encapsulating at least the original information (tag and id)
254      *            and source (package name) used to post the {@link android.app.Notification} that
255      *            was just removed.
256      */
onNotificationRemoved(StatusBarNotification sbn)257     public void onNotificationRemoved(StatusBarNotification sbn) {
258         // optional
259     }
260 
261     /**
262      * Implement this method to learn when notifications are removed.
263      * <p>
264      * This might occur because the user has dismissed the notification using system UI (or another
265      * notification listener) or because the app has withdrawn the notification.
266      * <p>
267      * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
268      * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
269      * fields such as {@link android.app.Notification#contentView} and
270      * {@link android.app.Notification#largeIcon}. However, all other fields on
271      * {@link StatusBarNotification}, sufficient to match this call with a prior call to
272      * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
273      *
274      * @param sbn A data structure encapsulating at least the original information (tag and id)
275      *            and source (package name) used to post the {@link android.app.Notification} that
276      *            was just removed.
277      * @param rankingMap The current ranking map that can be used to retrieve ranking information
278      *                   for active notifications.
279      *
280      */
onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap)281     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
282         onNotificationRemoved(sbn);
283     }
284 
285     /**
286      * Implement this method to learn about when the listener is enabled and connected to
287      * the notification manager.  You are safe to call {@link #getActiveNotifications()}
288      * at this time.
289      */
onListenerConnected()290     public void onListenerConnected() {
291         // optional
292     }
293 
294     /**
295      * Implement this method to learn about when the listener is disconnected from the
296      * notification manager.You will not receive any events after this call, and may only
297      * call {@link #requestRebind(ComponentName)} at this time.
298      */
onListenerDisconnected()299     public void onListenerDisconnected() {
300         // optional
301     }
302 
303     /**
304      * Implement this method to be notified when the notification ranking changes.
305      *
306      * @param rankingMap The current ranking map that can be used to retrieve ranking information
307      *                   for active notifications.
308      */
onNotificationRankingUpdate(RankingMap rankingMap)309     public void onNotificationRankingUpdate(RankingMap rankingMap) {
310         // optional
311     }
312 
313     /**
314      * Implement this method to be notified when the
315      * {@link #getCurrentListenerHints() Listener hints} change.
316      *
317      * @param hints The current {@link #getCurrentListenerHints() listener hints}.
318      */
onListenerHintsChanged(int hints)319     public void onListenerHintsChanged(int hints) {
320         // optional
321     }
322 
323     /**
324      * Implement this method to be notified when the
325      * {@link #getCurrentInterruptionFilter() interruption filter} changed.
326      *
327      * @param interruptionFilter The current
328      *     {@link #getCurrentInterruptionFilter() interruption filter}.
329      */
onInterruptionFilterChanged(int interruptionFilter)330     public void onInterruptionFilterChanged(int interruptionFilter) {
331         // optional
332     }
333 
334     /** @hide */
getNotificationInterface()335     protected final INotificationManager getNotificationInterface() {
336         if (mNoMan == null) {
337             mNoMan = INotificationManager.Stub.asInterface(
338                     ServiceManager.getService(Context.NOTIFICATION_SERVICE));
339         }
340         return mNoMan;
341     }
342 
343     /**
344      * Inform the notification manager about dismissal of a single notification.
345      * <p>
346      * Use this if your listener has a user interface that allows the user to dismiss individual
347      * notifications, similar to the behavior of Android's status bar and notification panel.
348      * It should be called after the user dismisses a single notification using your UI;
349      * upon being informed, the notification manager will actually remove the notification
350      * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
351      * <p>
352      * <b>Note:</b> If your listener allows the user to fire a notification's
353      * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
354      * this method at that time <i>if</i> the Notification in question has the
355      * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
356      *
357      * <p>The service should wait for the {@link #onListenerConnected()} event
358      * before performing this operation.
359      *
360      * @param pkg Package of the notifying app.
361      * @param tag Tag of the notification as specified by the notifying app in
362      *     {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
363      * @param id  ID of the notification as specified by the notifying app in
364      *     {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
365      * <p>
366      * @deprecated Use {@link #cancelNotification(String key)}
367      * instead. Beginning with {@link android.os.Build.VERSION_CODES#LOLLIPOP} this method will no longer
368      * cancel the notification. It will continue to cancel the notification for applications
369      * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
370      */
cancelNotification(String pkg, String tag, int id)371     public final void cancelNotification(String pkg, String tag, int id) {
372         if (!isBound()) return;
373         try {
374             getNotificationInterface().cancelNotificationFromListener(
375                     mWrapper, pkg, tag, id);
376         } catch (android.os.RemoteException ex) {
377             Log.v(TAG, "Unable to contact notification manager", ex);
378         }
379     }
380 
381     /**
382      * Inform the notification manager about dismissal of a single notification.
383      * <p>
384      * Use this if your listener has a user interface that allows the user to dismiss individual
385      * notifications, similar to the behavior of Android's status bar and notification panel.
386      * It should be called after the user dismisses a single notification using your UI;
387      * upon being informed, the notification manager will actually remove the notification
388      * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
389      * <p>
390      * <b>Note:</b> If your listener allows the user to fire a notification's
391      * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
392      * this method at that time <i>if</i> the Notification in question has the
393      * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
394      * <p>
395      *
396      * <p>The service should wait for the {@link #onListenerConnected()} event
397      * before performing this operation.
398      *
399      * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}.
400      */
cancelNotification(String key)401     public final void cancelNotification(String key) {
402         if (!isBound()) return;
403         try {
404             getNotificationInterface().cancelNotificationsFromListener(mWrapper,
405                     new String[] { key });
406         } catch (android.os.RemoteException ex) {
407             Log.v(TAG, "Unable to contact notification manager", ex);
408         }
409     }
410 
411     /**
412      * Inform the notification manager about dismissal of all notifications.
413      * <p>
414      * Use this if your listener has a user interface that allows the user to dismiss all
415      * notifications, similar to the behavior of Android's status bar and notification panel.
416      * It should be called after the user invokes the "dismiss all" function of your UI;
417      * upon being informed, the notification manager will actually remove all active notifications
418      * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks.
419      *
420      * <p>The service should wait for the {@link #onListenerConnected()} event
421      * before performing this operation.
422      *
423      * {@see #cancelNotification(String, String, int)}
424      */
cancelAllNotifications()425     public final void cancelAllNotifications() {
426         cancelNotifications(null /*all*/);
427     }
428 
429     /**
430      * Inform the notification manager about dismissal of specific notifications.
431      * <p>
432      * Use this if your listener has a user interface that allows the user to dismiss
433      * multiple notifications at once.
434      *
435      * <p>The service should wait for the {@link #onListenerConnected()} event
436      * before performing this operation.
437      *
438      * @param keys Notifications to dismiss, or {@code null} to dismiss all.
439      *
440      * {@see #cancelNotification(String, String, int)}
441      */
cancelNotifications(String[] keys)442     public final void cancelNotifications(String[] keys) {
443         if (!isBound()) return;
444         try {
445             getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys);
446         } catch (android.os.RemoteException ex) {
447             Log.v(TAG, "Unable to contact notification manager", ex);
448         }
449     }
450 
451     /**
452      * Inform the notification manager that these notifications have been viewed by the
453      * user. This should only be called when there is sufficient confidence that the user is
454      * looking at the notifications, such as when the notifications appear on the screen due to
455      * an explicit user interaction.
456      *
457      * <p>The service should wait for the {@link #onListenerConnected()} event
458      * before performing this operation.
459      *
460      * @param keys Notifications to mark as seen.
461      */
setNotificationsShown(String[] keys)462     public final void setNotificationsShown(String[] keys) {
463         if (!isBound()) return;
464         try {
465             getNotificationInterface().setNotificationsShownFromListener(mWrapper, keys);
466         } catch (android.os.RemoteException ex) {
467             Log.v(TAG, "Unable to contact notification manager", ex);
468         }
469     }
470 
471     /**
472      * Sets the notification trim that will be received via {@link #onNotificationPosted}.
473      *
474      * <p>
475      * Setting a trim other than {@link #TRIM_FULL} enables listeners that don't need access to the
476      * full notification features right away to reduce their memory footprint. Full notifications
477      * can be requested on-demand via {@link #getActiveNotifications(int)}.
478      *
479      * <p>
480      * Set to {@link #TRIM_FULL} initially.
481      *
482      * <p>The service should wait for the {@link #onListenerConnected()} event
483      * before performing this operation.
484      *
485      * @hide
486      *
487      * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}.
488      *             See <code>TRIM_*</code> constants.
489      */
490     @SystemApi
setOnNotificationPostedTrim(int trim)491     public final void setOnNotificationPostedTrim(int trim) {
492         if (!isBound()) return;
493         try {
494             getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim);
495         } catch (RemoteException ex) {
496             Log.v(TAG, "Unable to contact notification manager", ex);
497         }
498     }
499 
500     /**
501      * Request the list of outstanding notifications (that is, those that are visible to the
502      * current user). Useful when you don't know what's already been posted.
503      *
504      * <p>The service should wait for the {@link #onListenerConnected()} event
505      * before performing this operation.
506      *
507      * @return An array of active notifications, sorted in natural order.
508      */
getActiveNotifications()509     public StatusBarNotification[] getActiveNotifications() {
510         return getActiveNotifications(null, TRIM_FULL);
511     }
512 
513     /**
514      * Request the list of outstanding notifications (that is, those that are visible to the
515      * current user). Useful when you don't know what's already been posted.
516      *
517      * @hide
518      *
519      * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
520      * @return An array of active notifications, sorted in natural order.
521      */
522     @SystemApi
getActiveNotifications(int trim)523     public StatusBarNotification[] getActiveNotifications(int trim) {
524         return getActiveNotifications(null, trim);
525     }
526 
527     /**
528      * Request one or more notifications by key. Useful if you have been keeping track of
529      * notifications but didn't want to retain the bits, and now need to go back and extract
530      * more data out of those notifications.
531      *
532      * <p>The service should wait for the {@link #onListenerConnected()} event
533      * before performing this operation.
534      *
535      * @param keys the keys of the notifications to request
536      * @return An array of notifications corresponding to the requested keys, in the
537      * same order as the key list.
538      */
getActiveNotifications(String[] keys)539     public StatusBarNotification[] getActiveNotifications(String[] keys) {
540         return getActiveNotifications(keys, TRIM_FULL);
541     }
542 
543     /**
544      * Request one or more notifications by key. Useful if you have been keeping track of
545      * notifications but didn't want to retain the bits, and now need to go back and extract
546      * more data out of those notifications.
547      *
548      * @hide
549      *
550      * @param keys the keys of the notifications to request
551      * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
552      * @return An array of notifications corresponding to the requested keys, in the
553      * same order as the key list.
554      */
555     @SystemApi
getActiveNotifications(String[] keys, int trim)556     public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) {
557         if (!isBound())
558             return null;
559         try {
560             ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
561                     .getActiveNotificationsFromListener(mWrapper, keys, trim);
562             List<StatusBarNotification> list = parceledList.getList();
563             ArrayList<StatusBarNotification> corruptNotifications = null;
564             int N = list.size();
565             for (int i = 0; i < N; i++) {
566                 StatusBarNotification sbn = list.get(i);
567                 Notification notification = sbn.getNotification();
568                 try {
569                     // convert icon metadata to legacy format for older clients
570                     createLegacyIconExtras(notification);
571                     // populate remote views for older clients.
572                     maybePopulateRemoteViews(notification);
573                 } catch (IllegalArgumentException e) {
574                     if (corruptNotifications == null) {
575                         corruptNotifications = new ArrayList<>(N);
576                     }
577                     corruptNotifications.add(sbn);
578                     Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
579                             sbn.getPackageName());
580                 }
581             }
582             if (corruptNotifications != null) {
583                 list.removeAll(corruptNotifications);
584             }
585             return list.toArray(new StatusBarNotification[list.size()]);
586         } catch (android.os.RemoteException ex) {
587             Log.v(TAG, "Unable to contact notification manager", ex);
588         }
589         return null;
590     }
591 
592     /**
593      * Gets the set of hints representing current state.
594      *
595      * <p>
596      * The current state may differ from the requested state if the hint represents state
597      * shared across all listeners or a feature the notification host does not support or refuses
598      * to grant.
599      *
600      * <p>The service should wait for the {@link #onListenerConnected()} event
601      * before performing this operation.
602      *
603      * @return Zero or more of the HINT_ constants.
604      */
getCurrentListenerHints()605     public final int getCurrentListenerHints() {
606         if (!isBound()) return 0;
607         try {
608             return getNotificationInterface().getHintsFromListener(mWrapper);
609         } catch (android.os.RemoteException ex) {
610             Log.v(TAG, "Unable to contact notification manager", ex);
611             return 0;
612         }
613     }
614 
615     /**
616      * Gets the current notification interruption filter active on the host.
617      *
618      * <p>
619      * The interruption filter defines which notifications are allowed to interrupt the user
620      * (e.g. via sound &amp; vibration) and is applied globally. Listeners can find out whether
621      * a specific notification matched the interruption filter via
622      * {@link Ranking#matchesInterruptionFilter()}.
623      * <p>
624      * The current filter may differ from the previously requested filter if the notification host
625      * does not support or refuses to apply the requested filter, or if another component changed
626      * the filter in the meantime.
627      * <p>
628      * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
629      *
630      * <p>The service should wait for the {@link #onListenerConnected()} event
631      * before performing this operation.
632      *
633      * @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when
634      * unavailable.
635      */
getCurrentInterruptionFilter()636     public final int getCurrentInterruptionFilter() {
637         if (!isBound()) return INTERRUPTION_FILTER_UNKNOWN;
638         try {
639             return getNotificationInterface().getInterruptionFilterFromListener(mWrapper);
640         } catch (android.os.RemoteException ex) {
641             Log.v(TAG, "Unable to contact notification manager", ex);
642             return INTERRUPTION_FILTER_UNKNOWN;
643         }
644     }
645 
646     /**
647      * Sets the desired {@link #getCurrentListenerHints() listener hints}.
648      *
649      * <p>
650      * This is merely a request, the host may or may not choose to take action depending
651      * on other listener requests or other global state.
652      * <p>
653      * Listen for updates using {@link #onListenerHintsChanged(int)}.
654      *
655      * <p>The service should wait for the {@link #onListenerConnected()} event
656      * before performing this operation.
657      *
658      * @param hints One or more of the HINT_ constants.
659      */
requestListenerHints(int hints)660     public final void requestListenerHints(int hints) {
661         if (!isBound()) return;
662         try {
663             getNotificationInterface().requestHintsFromListener(mWrapper, hints);
664         } catch (android.os.RemoteException ex) {
665             Log.v(TAG, "Unable to contact notification manager", ex);
666         }
667     }
668 
669     /**
670      * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}.
671      *
672      * <p>
673      * This is merely a request, the host may or may not choose to apply the requested
674      * interruption filter depending on other listener requests or other global state.
675      * <p>
676      * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
677      *
678      * <p>The service should wait for the {@link #onListenerConnected()} event
679      * before performing this operation.
680      *
681      * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants.
682      */
requestInterruptionFilter(int interruptionFilter)683     public final void requestInterruptionFilter(int interruptionFilter) {
684         if (!isBound()) return;
685         try {
686             getNotificationInterface()
687                     .requestInterruptionFilterFromListener(mWrapper, interruptionFilter);
688         } catch (android.os.RemoteException ex) {
689             Log.v(TAG, "Unable to contact notification manager", ex);
690         }
691     }
692 
693     /**
694      * Returns current ranking information.
695      *
696      * <p>
697      * The returned object represents the current ranking snapshot and only
698      * applies for currently active notifications.
699      * <p>
700      * Generally you should use the RankingMap that is passed with events such
701      * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)},
702      * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and
703      * so on. This method should only be used when needing access outside of
704      * such events, for example to retrieve the RankingMap right after
705      * initialization.
706      *
707      * <p>The service should wait for the {@link #onListenerConnected()} event
708      * before performing this operation.
709      *
710      * @return A {@link RankingMap} object providing access to ranking information
711      */
getCurrentRanking()712     public RankingMap getCurrentRanking() {
713         synchronized (mLock) {
714             return mRankingMap;
715         }
716     }
717 
718     /**
719      * This is not the lifecycle event you are looking for.
720      *
721      * <p>The service should wait for the {@link #onListenerConnected()} event
722      * before performing any operations.
723      */
724     @Override
onBind(Intent intent)725     public IBinder onBind(Intent intent) {
726         if (mWrapper == null) {
727             mWrapper = new NotificationListenerWrapper();
728         }
729         return mWrapper;
730     }
731 
732     /** @hide */
isBound()733     protected boolean isBound() {
734         if (mWrapper == null) {
735             Log.w(TAG, "Notification listener service not yet bound.");
736             return false;
737         }
738         return true;
739     }
740 
741     @Override
onDestroy()742     public void onDestroy() {
743         onListenerDisconnected();
744         super.onDestroy();
745     }
746 
747     /**
748      * Directly register this service with the Notification Manager.
749      *
750      * <p>Only system services may use this call. It will fail for non-system callers.
751      * Apps should ask the user to add their listener in Settings.
752      *
753      * @param context Context required for accessing resources. Since this service isn't
754      *    launched as a real Service when using this method, a context has to be passed in.
755      * @param componentName the component that will consume the notification information
756      * @param currentUser the user to use as the stream filter
757      * @hide
758      */
759     @SystemApi
registerAsSystemService(Context context, ComponentName componentName, int currentUser)760     public void registerAsSystemService(Context context, ComponentName componentName,
761             int currentUser) throws RemoteException {
762         if (mWrapper == null) {
763             mWrapper = new NotificationListenerWrapper();
764         }
765         mSystemContext = context;
766         INotificationManager noMan = getNotificationInterface();
767         mHandler = new MyHandler(context.getMainLooper());
768         mCurrentUser = currentUser;
769         noMan.registerListener(mWrapper, componentName, currentUser);
770     }
771 
772     /**
773      * Directly unregister this service from the Notification Manager.
774      *
775      * <p>This method will fail for listeners that were not registered
776      * with (@link registerAsService).
777      * @hide
778      */
779     @SystemApi
unregisterAsSystemService()780     public void unregisterAsSystemService() throws RemoteException {
781         if (mWrapper != null) {
782             INotificationManager noMan = getNotificationInterface();
783             noMan.unregisterListener(mWrapper, mCurrentUser);
784         }
785     }
786 
787     /**
788      * Request that the listener be rebound, after a previous call to {@link #requestUnbind}.
789      *
790      * <p>This method will fail for listeners that have
791      * not been granted the permission by the user.
792      */
requestRebind(ComponentName componentName)793     public static void requestRebind(ComponentName componentName) {
794         INotificationManager noMan = INotificationManager.Stub.asInterface(
795                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
796         try {
797             noMan.requestBindListener(componentName);
798         } catch (RemoteException ex) {
799             throw ex.rethrowFromSystemServer();
800         }
801     }
802 
803     /**
804      * Request that the service be unbound.
805      *
806      * <p>This will no longer receive updates until
807      * {@link #requestRebind(ComponentName)} is called.
808      * The service will likely be kiled by the system after this call.
809      *
810      * <p>The service should wait for the {@link #onListenerConnected()} event
811      * before performing this operation. I know it's tempting, but you must wait.
812      */
requestUnbind()813     public final void requestUnbind() {
814         if (mWrapper != null) {
815             INotificationManager noMan = getNotificationInterface();
816             try {
817                 noMan.requestUnbindListener(mWrapper);
818                 // Disable future messages.
819                 isConnected = false;
820             } catch (RemoteException ex) {
821                 throw ex.rethrowFromSystemServer();
822             }
823         }
824     }
825 
826     /** Convert new-style Icons to legacy representations for pre-M clients. */
createLegacyIconExtras(Notification n)827     private void createLegacyIconExtras(Notification n) {
828         Icon smallIcon = n.getSmallIcon();
829         Icon largeIcon = n.getLargeIcon();
830         if (smallIcon != null && smallIcon.getType() == Icon.TYPE_RESOURCE) {
831             n.extras.putInt(Notification.EXTRA_SMALL_ICON, smallIcon.getResId());
832             n.icon = smallIcon.getResId();
833         }
834         if (largeIcon != null) {
835             Drawable d = largeIcon.loadDrawable(getContext());
836             if (d != null && d instanceof BitmapDrawable) {
837                 final Bitmap largeIconBits = ((BitmapDrawable) d).getBitmap();
838                 n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, largeIconBits);
839                 n.largeIcon = largeIconBits;
840             }
841         }
842     }
843 
844     /**
845      * Populates remote views for pre-N targeting apps.
846      */
maybePopulateRemoteViews(Notification notification)847     private void maybePopulateRemoteViews(Notification notification) {
848         if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
849             Builder builder = Builder.recoverBuilder(getContext(), notification);
850 
851             // Some styles wrap Notification's contentView, bigContentView and headsUpContentView.
852             // First inflate them all, only then set them to avoid recursive wrapping.
853             RemoteViews content = builder.createContentView();
854             RemoteViews big = builder.createBigContentView();
855             RemoteViews headsUp = builder.createHeadsUpContentView();
856 
857             notification.contentView = content;
858             notification.bigContentView = big;
859             notification.headsUpContentView = headsUp;
860         }
861     }
862 
863     /** @hide */
864     protected class NotificationListenerWrapper extends INotificationListener.Stub {
865         @Override
onNotificationPosted(IStatusBarNotificationHolder sbnHolder, NotificationRankingUpdate update)866         public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
867                 NotificationRankingUpdate update) {
868             StatusBarNotification sbn;
869             try {
870                 sbn = sbnHolder.get();
871             } catch (RemoteException e) {
872                 Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
873                 return;
874             }
875 
876             try {
877                 // convert icon metadata to legacy format for older clients
878                 createLegacyIconExtras(sbn.getNotification());
879                 maybePopulateRemoteViews(sbn.getNotification());
880             } catch (IllegalArgumentException e) {
881                 // warn and drop corrupt notification
882                 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
883                         sbn.getPackageName());
884                 sbn = null;
885             }
886 
887             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
888             synchronized (mLock) {
889                 applyUpdateLocked(update);
890                 if (sbn != null) {
891                     SomeArgs args = SomeArgs.obtain();
892                     args.arg1 = sbn;
893                     args.arg2 = mRankingMap;
894                     mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
895                             args).sendToTarget();
896                 } else {
897                     // still pass along the ranking map, it may contain other information
898                     mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
899                             mRankingMap).sendToTarget();
900                 }
901             }
902 
903         }
904 
905         @Override
onNotificationRemoved(IStatusBarNotificationHolder sbnHolder, NotificationRankingUpdate update)906         public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
907                 NotificationRankingUpdate update) {
908             StatusBarNotification sbn;
909             try {
910                 sbn = sbnHolder.get();
911             } catch (RemoteException e) {
912                 Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e);
913                 return;
914             }
915             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
916             synchronized (mLock) {
917                 applyUpdateLocked(update);
918                 SomeArgs args = SomeArgs.obtain();
919                 args.arg1 = sbn;
920                 args.arg2 = mRankingMap;
921                 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED,
922                         args).sendToTarget();
923             }
924 
925         }
926 
927         @Override
onListenerConnected(NotificationRankingUpdate update)928         public void onListenerConnected(NotificationRankingUpdate update) {
929             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
930             synchronized (mLock) {
931                 applyUpdateLocked(update);
932             }
933             isConnected = true;
934             mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_CONNECTED).sendToTarget();
935         }
936 
937         @Override
onNotificationRankingUpdate(NotificationRankingUpdate update)938         public void onNotificationRankingUpdate(NotificationRankingUpdate update)
939                 throws RemoteException {
940             // protect subclass from concurrent modifications of (@link mNotificationKeys}.
941             synchronized (mLock) {
942                 applyUpdateLocked(update);
943                 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
944                         mRankingMap).sendToTarget();
945             }
946 
947         }
948 
949         @Override
onListenerHintsChanged(int hints)950         public void onListenerHintsChanged(int hints) throws RemoteException {
951             mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_HINTS_CHANGED,
952                     hints, 0).sendToTarget();
953         }
954 
955         @Override
onInterruptionFilterChanged(int interruptionFilter)956         public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException {
957             mHandler.obtainMessage(MyHandler.MSG_ON_INTERRUPTION_FILTER_CHANGED,
958                     interruptionFilter, 0).sendToTarget();
959         }
960 
961         @Override
onNotificationEnqueued(IStatusBarNotificationHolder notificationHolder, int importance, boolean user)962         public void onNotificationEnqueued(IStatusBarNotificationHolder notificationHolder,
963                                            int importance, boolean user) throws RemoteException {
964             // no-op in the listener
965         }
966 
967         @Override
onNotificationVisibilityChanged(String key, long time, boolean visible)968         public void onNotificationVisibilityChanged(String key, long time, boolean visible)
969                 throws RemoteException {
970             // no-op in the listener
971         }
972 
973         @Override
onNotificationClick(String key, long time)974         public void onNotificationClick(String key, long time) throws RemoteException {
975             // no-op in the listener
976         }
977 
978         @Override
onNotificationActionClick(String key, long time, int actionIndex)979         public void onNotificationActionClick(String key, long time, int actionIndex)
980                 throws RemoteException {
981             // no-op in the listener
982         }
983 
984         @Override
onNotificationRemovedReason(String key, long time, int reason)985         public void onNotificationRemovedReason(String key, long time, int reason)
986                 throws RemoteException {
987             // no-op in the listener
988         }
989     }
990 
applyUpdateLocked(NotificationRankingUpdate update)991     private void applyUpdateLocked(NotificationRankingUpdate update) {
992         mRankingMap = new RankingMap(update);
993     }
994 
995     /** @hide */
getContext()996     protected Context getContext() {
997         if (mSystemContext != null) {
998             return mSystemContext;
999         }
1000         return this;
1001     }
1002 
1003     /**
1004      * Stores ranking related information on a currently active notification.
1005      *
1006      * <p>
1007      * Ranking objects aren't automatically updated as notification events
1008      * occur. Instead, ranking information has to be retrieved again via the
1009      * current {@link RankingMap}.
1010      */
1011     public static class Ranking {
1012 
1013         /** Value signifying that the user has not expressed a per-app visibility override value.
1014          * @hide */
1015         public static final int VISIBILITY_NO_OVERRIDE = NotificationManager.VISIBILITY_NO_OVERRIDE;
1016 
1017         /**
1018          * Value signifying that the user has not expressed an importance.
1019          *
1020          * This value is for persisting preferences, and should never be associated with
1021          * an actual notification.
1022          *
1023          * @hide
1024          */
1025         public static final int IMPORTANCE_UNSPECIFIED = NotificationManager.IMPORTANCE_UNSPECIFIED;
1026 
1027         /**
1028          * A notification with no importance: shows nowhere, is blocked.
1029          *
1030          * @hide
1031          */
1032         public static final int IMPORTANCE_NONE = NotificationManager.IMPORTANCE_NONE;
1033 
1034         /**
1035          * Min notification importance: only shows in the shade, below the fold.
1036          *
1037          * @hide
1038          */
1039         public static final int IMPORTANCE_MIN = NotificationManager.IMPORTANCE_MIN;
1040 
1041         /**
1042          * Low notification importance: shows everywhere, but is not intrusive.
1043          *
1044          * @hide
1045          */
1046         public static final int IMPORTANCE_LOW = NotificationManager.IMPORTANCE_LOW;
1047 
1048         /**
1049          * Default notification importance: shows everywhere, allowed to makes noise,
1050          * but does not visually intrude.
1051          *
1052          * @hide
1053          */
1054         public static final int IMPORTANCE_DEFAULT = NotificationManager.IMPORTANCE_DEFAULT;
1055 
1056         /**
1057          * Higher notification importance: shows everywhere, allowed to makes noise and peek.
1058          *
1059          * @hide
1060          */
1061         public static final int IMPORTANCE_HIGH = NotificationManager.IMPORTANCE_HIGH;
1062 
1063         /**
1064          * Highest notification importance: shows everywhere, allowed to makes noise, peek, and
1065          * use full screen intents.
1066          *
1067          * @hide
1068          */
1069         public static final int IMPORTANCE_MAX = NotificationManager.IMPORTANCE_MAX;
1070 
1071         private String mKey;
1072         private int mRank = -1;
1073         private boolean mIsAmbient;
1074         private boolean mMatchesInterruptionFilter;
1075         private int mVisibilityOverride;
1076         private int mSuppressedVisualEffects;
1077         private @NotificationManager.Importance int mImportance;
1078         private CharSequence mImportanceExplanation;
1079         // System specified group key.
1080         private String mOverrideGroupKey;
1081 
Ranking()1082         public Ranking() {}
1083 
1084         /**
1085          * Returns the key of the notification this Ranking applies to.
1086          */
getKey()1087         public String getKey() {
1088             return mKey;
1089         }
1090 
1091         /**
1092          * Returns the rank of the notification.
1093          *
1094          * @return the rank of the notification, that is the 0-based index in
1095          *     the list of active notifications.
1096          */
getRank()1097         public int getRank() {
1098             return mRank;
1099         }
1100 
1101         /**
1102          * Returns whether the notification is an ambient notification, that is
1103          * a notification that doesn't require the user's immediate attention.
1104          */
isAmbient()1105         public boolean isAmbient() {
1106             return mIsAmbient;
1107         }
1108 
1109         /**
1110          * Returns the user specificed visibility for the package that posted
1111          * this notification, or
1112          * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
1113          * no such preference has been expressed.
1114          * @hide
1115          */
getVisibilityOverride()1116         public int getVisibilityOverride() {
1117             return mVisibilityOverride;
1118         }
1119 
1120         /**
1121          * Returns the type(s) of visual effects that should be suppressed for this notification.
1122          * See {@link #SUPPRESSED_EFFECT_SCREEN_OFF}, {@link #SUPPRESSED_EFFECT_SCREEN_ON}.
1123          */
getSuppressedVisualEffects()1124         public int getSuppressedVisualEffects() {
1125             return mSuppressedVisualEffects;
1126         }
1127 
1128         /**
1129          * Returns whether the notification matches the user's interruption
1130          * filter.
1131          *
1132          * @return {@code true} if the notification is allowed by the filter, or
1133          * {@code false} if it is blocked.
1134          */
matchesInterruptionFilter()1135         public boolean matchesInterruptionFilter() {
1136             return mMatchesInterruptionFilter;
1137         }
1138 
1139         /**
1140          * Returns the importance of the notification, which dictates its
1141          * modes of presentation, see: {@link NotificationManager#IMPORTANCE_DEFAULT}, etc.
1142          *
1143          * @return the rank of the notification
1144          */
getImportance()1145         public @NotificationManager.Importance int getImportance() {
1146             return mImportance;
1147         }
1148 
1149         /**
1150          * If the importance has been overriden by user preference, then this will be non-null,
1151          * and should be displayed to the user.
1152          *
1153          * @return the explanation for the importance, or null if it is the natural importance
1154          */
getImportanceExplanation()1155         public CharSequence getImportanceExplanation() {
1156             return mImportanceExplanation;
1157         }
1158 
1159         /**
1160          * If the system has overriden the group key, then this will be non-null, and this
1161          * key should be used to bundle notifications.
1162          */
getOverrideGroupKey()1163         public String getOverrideGroupKey() {
1164             return mOverrideGroupKey;
1165         }
1166 
populate(String key, int rank, boolean matchesInterruptionFilter, int visibilityOverride, int suppressedVisualEffects, int importance, CharSequence explanation, String overrideGroupKey)1167         private void populate(String key, int rank, boolean matchesInterruptionFilter,
1168                 int visibilityOverride, int suppressedVisualEffects, int importance,
1169                 CharSequence explanation, String overrideGroupKey) {
1170             mKey = key;
1171             mRank = rank;
1172             mIsAmbient = importance < IMPORTANCE_LOW;
1173             mMatchesInterruptionFilter = matchesInterruptionFilter;
1174             mVisibilityOverride = visibilityOverride;
1175             mSuppressedVisualEffects = suppressedVisualEffects;
1176             mImportance = importance;
1177             mImportanceExplanation = explanation;
1178             mOverrideGroupKey = overrideGroupKey;
1179         }
1180 
1181         /**
1182          * {@hide}
1183          */
1184         public static String importanceToString(int importance) {
1185             switch (importance) {
1186                 case IMPORTANCE_UNSPECIFIED:
1187                     return "UNSPECIFIED";
1188                 case IMPORTANCE_NONE:
1189                     return "NONE";
1190                 case IMPORTANCE_MIN:
1191                     return "MIN";
1192                 case IMPORTANCE_LOW:
1193                     return "LOW";
1194                 case IMPORTANCE_DEFAULT:
1195                     return "DEFAULT";
1196                 case IMPORTANCE_HIGH:
1197                     return "HIGH";
1198                 case IMPORTANCE_MAX:
1199                     return "MAX";
1200                 default:
1201                     return "UNKNOWN(" + String.valueOf(importance) + ")";
1202             }
1203         }
1204     }
1205 
1206     /**
1207      * Provides access to ranking information on currently active
1208      * notifications.
1209      *
1210      * <p>
1211      * Note that this object represents a ranking snapshot that only applies to
1212      * notifications active at the time of retrieval.
1213      */
1214     public static class RankingMap implements Parcelable {
1215         private final NotificationRankingUpdate mRankingUpdate;
1216         private ArrayMap<String,Integer> mRanks;
1217         private ArraySet<Object> mIntercepted;
1218         private ArrayMap<String, Integer> mVisibilityOverrides;
1219         private ArrayMap<String, Integer> mSuppressedVisualEffects;
1220         private ArrayMap<String, Integer> mImportance;
1221         private ArrayMap<String, String> mImportanceExplanation;
1222         private ArrayMap<String, String> mOverrideGroupKeys;
1223 
1224         private RankingMap(NotificationRankingUpdate rankingUpdate) {
1225             mRankingUpdate = rankingUpdate;
1226         }
1227 
1228         /**
1229          * Request the list of notification keys in their current ranking
1230          * order.
1231          *
1232          * @return An array of active notification keys, in their ranking order.
1233          */
1234         public String[] getOrderedKeys() {
1235             return mRankingUpdate.getOrderedKeys();
1236         }
1237 
1238         /**
1239          * Populates outRanking with ranking information for the notification
1240          * with the given key.
1241          *
1242          * @return true if a valid key has been passed and outRanking has
1243          *     been populated; false otherwise
1244          */
1245         public boolean getRanking(String key, Ranking outRanking) {
1246             int rank = getRank(key);
1247             outRanking.populate(key, rank, !isIntercepted(key),
1248                     getVisibilityOverride(key), getSuppressedVisualEffects(key),
1249                     getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key));
1250             return rank >= 0;
1251         }
1252 
getRank(String key)1253         private int getRank(String key) {
1254             synchronized (this) {
1255                 if (mRanks == null) {
1256                     buildRanksLocked();
1257                 }
1258             }
1259             Integer rank = mRanks.get(key);
1260             return rank != null ? rank : -1;
1261         }
1262 
isIntercepted(String key)1263         private boolean isIntercepted(String key) {
1264             synchronized (this) {
1265                 if (mIntercepted == null) {
1266                     buildInterceptedSetLocked();
1267                 }
1268             }
1269             return mIntercepted.contains(key);
1270         }
1271 
getVisibilityOverride(String key)1272         private int getVisibilityOverride(String key) {
1273             synchronized (this) {
1274                 if (mVisibilityOverrides == null) {
1275                     buildVisibilityOverridesLocked();
1276                 }
1277             }
1278             Integer override = mVisibilityOverrides.get(key);
1279             if (override == null) {
1280                 return Ranking.VISIBILITY_NO_OVERRIDE;
1281             }
1282             return override.intValue();
1283         }
1284 
getSuppressedVisualEffects(String key)1285         private int getSuppressedVisualEffects(String key) {
1286             synchronized (this) {
1287                 if (mSuppressedVisualEffects == null) {
1288                     buildSuppressedVisualEffectsLocked();
1289                 }
1290             }
1291             Integer suppressed = mSuppressedVisualEffects.get(key);
1292             if (suppressed == null) {
1293                 return 0;
1294             }
1295             return suppressed.intValue();
1296         }
1297 
getImportance(String key)1298         private int getImportance(String key) {
1299             synchronized (this) {
1300                 if (mImportance == null) {
1301                     buildImportanceLocked();
1302                 }
1303             }
1304             Integer importance = mImportance.get(key);
1305             if (importance == null) {
1306                 return Ranking.IMPORTANCE_DEFAULT;
1307             }
1308             return importance.intValue();
1309         }
1310 
getImportanceExplanation(String key)1311         private String getImportanceExplanation(String key) {
1312             synchronized (this) {
1313                 if (mImportanceExplanation == null) {
1314                     buildImportanceExplanationLocked();
1315                 }
1316             }
1317             return mImportanceExplanation.get(key);
1318         }
1319 
getOverrideGroupKey(String key)1320         private String getOverrideGroupKey(String key) {
1321             synchronized (this) {
1322                 if (mOverrideGroupKeys == null) {
1323                     buildOverrideGroupKeys();
1324                 }
1325             }
1326             return mOverrideGroupKeys.get(key);
1327         }
1328 
1329         // Locked by 'this'
buildRanksLocked()1330         private void buildRanksLocked() {
1331             String[] orderedKeys = mRankingUpdate.getOrderedKeys();
1332             mRanks = new ArrayMap<>(orderedKeys.length);
1333             for (int i = 0; i < orderedKeys.length; i++) {
1334                 String key = orderedKeys[i];
1335                 mRanks.put(key, i);
1336             }
1337         }
1338 
1339         // Locked by 'this'
buildInterceptedSetLocked()1340         private void buildInterceptedSetLocked() {
1341             String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys();
1342             mIntercepted = new ArraySet<>(dndInterceptedKeys.length);
1343             Collections.addAll(mIntercepted, dndInterceptedKeys);
1344         }
1345 
1346         // Locked by 'this'
buildVisibilityOverridesLocked()1347         private void buildVisibilityOverridesLocked() {
1348             Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides();
1349             mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size());
1350             for (String key: visibilityBundle.keySet()) {
1351                mVisibilityOverrides.put(key, visibilityBundle.getInt(key));
1352             }
1353         }
1354 
1355         // Locked by 'this'
buildSuppressedVisualEffectsLocked()1356         private void buildSuppressedVisualEffectsLocked() {
1357             Bundle suppressedBundle = mRankingUpdate.getSuppressedVisualEffects();
1358             mSuppressedVisualEffects = new ArrayMap<>(suppressedBundle.size());
1359             for (String key: suppressedBundle.keySet()) {
1360                 mSuppressedVisualEffects.put(key, suppressedBundle.getInt(key));
1361             }
1362         }
1363         // Locked by 'this'
buildImportanceLocked()1364         private void buildImportanceLocked() {
1365             String[] orderedKeys = mRankingUpdate.getOrderedKeys();
1366             int[] importance = mRankingUpdate.getImportance();
1367             mImportance = new ArrayMap<>(orderedKeys.length);
1368             for (int i = 0; i < orderedKeys.length; i++) {
1369                 String key = orderedKeys[i];
1370                 mImportance.put(key, importance[i]);
1371             }
1372         }
1373 
1374         // Locked by 'this'
buildImportanceExplanationLocked()1375         private void buildImportanceExplanationLocked() {
1376             Bundle explanationBundle = mRankingUpdate.getImportanceExplanation();
1377             mImportanceExplanation = new ArrayMap<>(explanationBundle.size());
1378             for (String key: explanationBundle.keySet()) {
1379                 mImportanceExplanation.put(key, explanationBundle.getString(key));
1380             }
1381         }
1382 
1383         // Locked by 'this'
buildOverrideGroupKeys()1384         private void buildOverrideGroupKeys() {
1385             Bundle overrideGroupKeys = mRankingUpdate.getOverrideGroupKeys();
1386             mOverrideGroupKeys = new ArrayMap<>(overrideGroupKeys.size());
1387             for (String key: overrideGroupKeys.keySet()) {
1388                 mOverrideGroupKeys.put(key, overrideGroupKeys.getString(key));
1389             }
1390         }
1391 
1392         // ----------- Parcelable
1393 
1394         @Override
describeContents()1395         public int describeContents() {
1396             return 0;
1397         }
1398 
1399         @Override
writeToParcel(Parcel dest, int flags)1400         public void writeToParcel(Parcel dest, int flags) {
1401             dest.writeParcelable(mRankingUpdate, flags);
1402         }
1403 
1404         public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() {
1405             @Override
1406             public RankingMap createFromParcel(Parcel source) {
1407                 NotificationRankingUpdate rankingUpdate = source.readParcelable(null);
1408                 return new RankingMap(rankingUpdate);
1409             }
1410 
1411             @Override
1412             public RankingMap[] newArray(int size) {
1413                 return new RankingMap[size];
1414             }
1415         };
1416     }
1417 
1418     private final class MyHandler extends Handler {
1419         public static final int MSG_ON_NOTIFICATION_POSTED = 1;
1420         public static final int MSG_ON_NOTIFICATION_REMOVED = 2;
1421         public static final int MSG_ON_LISTENER_CONNECTED = 3;
1422         public static final int MSG_ON_NOTIFICATION_RANKING_UPDATE = 4;
1423         public static final int MSG_ON_LISTENER_HINTS_CHANGED = 5;
1424         public static final int MSG_ON_INTERRUPTION_FILTER_CHANGED = 6;
1425 
MyHandler(Looper looper)1426         public MyHandler(Looper looper) {
1427             super(looper, null, false);
1428         }
1429 
1430         @Override
handleMessage(Message msg)1431         public void handleMessage(Message msg) {
1432             if (!isConnected) {
1433                 return;
1434             }
1435             switch (msg.what) {
1436                 case MSG_ON_NOTIFICATION_POSTED: {
1437                     SomeArgs args = (SomeArgs) msg.obj;
1438                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
1439                     RankingMap rankingMap = (RankingMap) args.arg2;
1440                     args.recycle();
1441                     onNotificationPosted(sbn, rankingMap);
1442                 } break;
1443 
1444                 case MSG_ON_NOTIFICATION_REMOVED: {
1445                     SomeArgs args = (SomeArgs) msg.obj;
1446                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
1447                     RankingMap rankingMap = (RankingMap) args.arg2;
1448                     args.recycle();
1449                     onNotificationRemoved(sbn, rankingMap);
1450                 } break;
1451 
1452                 case MSG_ON_LISTENER_CONNECTED: {
1453                     onListenerConnected();
1454                 } break;
1455 
1456                 case MSG_ON_NOTIFICATION_RANKING_UPDATE: {
1457                     RankingMap rankingMap = (RankingMap) msg.obj;
1458                     onNotificationRankingUpdate(rankingMap);
1459                 } break;
1460 
1461                 case MSG_ON_LISTENER_HINTS_CHANGED: {
1462                     final int hints = msg.arg1;
1463                     onListenerHintsChanged(hints);
1464                 } break;
1465 
1466                 case MSG_ON_INTERRUPTION_FILTER_CHANGED: {
1467                     final int interruptionFilter = msg.arg1;
1468                     onInterruptionFilterChanged(interruptionFilter);
1469                 } break;
1470             }
1471         }
1472     }
1473 }
1474