NotificationListenerService.java revision 514c5ef8d5774d8820ed1bf90fe53af1606cf106
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.os.Handler;
20import android.os.Looper;
21import android.os.Message;
22
23import android.annotation.IntDef;
24import android.annotation.SystemApi;
25import android.annotation.SdkConstant;
26import android.app.INotificationManager;
27import android.app.Notification;
28import android.app.Notification.Builder;
29import android.app.NotificationManager;
30import android.app.Service;
31import android.content.ComponentName;
32import android.content.Context;
33import android.content.Intent;
34import android.content.pm.ParceledListSlice;
35import android.graphics.drawable.BitmapDrawable;
36import android.graphics.drawable.Drawable;
37import android.graphics.drawable.Icon;
38import android.graphics.Bitmap;
39import android.os.Build;
40import android.os.Bundle;
41import android.os.IBinder;
42import android.os.Parcel;
43import android.os.Parcelable;
44import android.os.RemoteException;
45import android.os.ServiceManager;
46import android.util.ArrayMap;
47import android.util.ArraySet;
48import android.util.Log;
49import android.widget.RemoteViews;
50import com.android.internal.annotations.GuardedBy;
51import com.android.internal.os.SomeArgs;
52import java.lang.annotation.Retention;
53import java.lang.annotation.RetentionPolicy;
54import java.util.ArrayList;
55import java.util.Collections;
56import 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 */
79public 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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
330    public void onInterruptionFilterChanged(int interruptionFilter) {
331        // optional
332    }
333
334    /** @hide */
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     */
371    @Deprecated
372    public final void cancelNotification(String pkg, String tag, int id) {
373        if (!isBound()) return;
374        try {
375            getNotificationInterface().cancelNotificationFromListener(
376                    mWrapper, pkg, tag, id);
377        } catch (android.os.RemoteException ex) {
378            Log.v(TAG, "Unable to contact notification manager", ex);
379        }
380    }
381
382    /**
383     * Inform the notification manager about dismissal of a single notification.
384     * <p>
385     * Use this if your listener has a user interface that allows the user to dismiss individual
386     * notifications, similar to the behavior of Android's status bar and notification panel.
387     * It should be called after the user dismisses a single notification using your UI;
388     * upon being informed, the notification manager will actually remove the notification
389     * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
390     * <p>
391     * <b>Note:</b> If your listener allows the user to fire a notification's
392     * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
393     * this method at that time <i>if</i> the Notification in question has the
394     * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
395     * <p>
396     *
397     * <p>The service should wait for the {@link #onListenerConnected()} event
398     * before performing this operation.
399     *
400     * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}.
401     */
402    public final void cancelNotification(String key) {
403        if (!isBound()) return;
404        try {
405            getNotificationInterface().cancelNotificationsFromListener(mWrapper,
406                    new String[] { key });
407        } catch (android.os.RemoteException ex) {
408            Log.v(TAG, "Unable to contact notification manager", ex);
409        }
410    }
411
412    /**
413     * Inform the notification manager about dismissal of all notifications.
414     * <p>
415     * Use this if your listener has a user interface that allows the user to dismiss all
416     * notifications, similar to the behavior of Android's status bar and notification panel.
417     * It should be called after the user invokes the "dismiss all" function of your UI;
418     * upon being informed, the notification manager will actually remove all active notifications
419     * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks.
420     *
421     * <p>The service should wait for the {@link #onListenerConnected()} event
422     * before performing this operation.
423     *
424     * {@see #cancelNotification(String, String, int)}
425     */
426    public final void cancelAllNotifications() {
427        cancelNotifications(null /*all*/);
428    }
429
430    /**
431     * Inform the notification manager about dismissal of specific notifications.
432     * <p>
433     * Use this if your listener has a user interface that allows the user to dismiss
434     * multiple notifications at once.
435     *
436     * <p>The service should wait for the {@link #onListenerConnected()} event
437     * before performing this operation.
438     *
439     * @param keys Notifications to dismiss, or {@code null} to dismiss all.
440     *
441     * {@see #cancelNotification(String, String, int)}
442     */
443    public final void cancelNotifications(String[] keys) {
444        if (!isBound()) return;
445        try {
446            getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys);
447        } catch (android.os.RemoteException ex) {
448            Log.v(TAG, "Unable to contact notification manager", ex);
449        }
450    }
451
452    /**
453     * Inform the notification manager that these notifications have been viewed by the
454     * user. This should only be called when there is sufficient confidence that the user is
455     * looking at the notifications, such as when the notifications appear on the screen due to
456     * an explicit user interaction.
457     *
458     * <p>The service should wait for the {@link #onListenerConnected()} event
459     * before performing this operation.
460     *
461     * @param keys Notifications to mark as seen.
462     */
463    public final void setNotificationsShown(String[] keys) {
464        if (!isBound()) return;
465        try {
466            getNotificationInterface().setNotificationsShownFromListener(mWrapper, keys);
467        } catch (android.os.RemoteException ex) {
468            Log.v(TAG, "Unable to contact notification manager", ex);
469        }
470    }
471
472    /**
473     * Sets the notification trim that will be received via {@link #onNotificationPosted}.
474     *
475     * <p>
476     * Setting a trim other than {@link #TRIM_FULL} enables listeners that don't need access to the
477     * full notification features right away to reduce their memory footprint. Full notifications
478     * can be requested on-demand via {@link #getActiveNotifications(int)}.
479     *
480     * <p>
481     * Set to {@link #TRIM_FULL} initially.
482     *
483     * <p>The service should wait for the {@link #onListenerConnected()} event
484     * before performing this operation.
485     *
486     * @hide
487     *
488     * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}.
489     *             See <code>TRIM_*</code> constants.
490     */
491    @SystemApi
492    public final void setOnNotificationPostedTrim(int trim) {
493        if (!isBound()) return;
494        try {
495            getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim);
496        } catch (RemoteException ex) {
497            Log.v(TAG, "Unable to contact notification manager", ex);
498        }
499    }
500
501    /**
502     * Request the list of outstanding notifications (that is, those that are visible to the
503     * current user). Useful when you don't know what's already been posted.
504     *
505     * <p>The service should wait for the {@link #onListenerConnected()} event
506     * before performing this operation.
507     *
508     * @return An array of active notifications, sorted in natural order.
509     */
510    public StatusBarNotification[] getActiveNotifications() {
511        return getActiveNotifications(null, TRIM_FULL);
512    }
513
514    /**
515     * Request the list of outstanding notifications (that is, those that are visible to the
516     * current user). Useful when you don't know what's already been posted.
517     *
518     * @hide
519     *
520     * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
521     * @return An array of active notifications, sorted in natural order.
522     */
523    @SystemApi
524    public StatusBarNotification[] getActiveNotifications(int trim) {
525        return getActiveNotifications(null, trim);
526    }
527
528    /**
529     * Request one or more notifications by key. Useful if you have been keeping track of
530     * notifications but didn't want to retain the bits, and now need to go back and extract
531     * more data out of those notifications.
532     *
533     * <p>The service should wait for the {@link #onListenerConnected()} event
534     * before performing this operation.
535     *
536     * @param keys the keys of the notifications to request
537     * @return An array of notifications corresponding to the requested keys, in the
538     * same order as the key list.
539     */
540    public StatusBarNotification[] getActiveNotifications(String[] keys) {
541        return getActiveNotifications(keys, TRIM_FULL);
542    }
543
544    /**
545     * Request one or more notifications by key. Useful if you have been keeping track of
546     * notifications but didn't want to retain the bits, and now need to go back and extract
547     * more data out of those notifications.
548     *
549     * @hide
550     *
551     * @param keys the keys of the notifications to request
552     * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants.
553     * @return An array of notifications corresponding to the requested keys, in the
554     * same order as the key list.
555     */
556    @SystemApi
557    public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) {
558        if (!isBound())
559            return null;
560        try {
561            ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
562                    .getActiveNotificationsFromListener(mWrapper, keys, trim);
563            List<StatusBarNotification> list = parceledList.getList();
564            ArrayList<StatusBarNotification> corruptNotifications = null;
565            int N = list.size();
566            for (int i = 0; i < N; i++) {
567                StatusBarNotification sbn = list.get(i);
568                Notification notification = sbn.getNotification();
569                try {
570                    // convert icon metadata to legacy format for older clients
571                    createLegacyIconExtras(notification);
572                    // populate remote views for older clients.
573                    maybePopulateRemoteViews(notification);
574                } catch (IllegalArgumentException e) {
575                    if (corruptNotifications == null) {
576                        corruptNotifications = new ArrayList<>(N);
577                    }
578                    corruptNotifications.add(sbn);
579                    Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
580                            sbn.getPackageName());
581                }
582            }
583            if (corruptNotifications != null) {
584                list.removeAll(corruptNotifications);
585            }
586            return list.toArray(new StatusBarNotification[list.size()]);
587        } catch (android.os.RemoteException ex) {
588            Log.v(TAG, "Unable to contact notification manager", ex);
589        }
590        return null;
591    }
592
593    /**
594     * Gets the set of hints representing current state.
595     *
596     * <p>
597     * The current state may differ from the requested state if the hint represents state
598     * shared across all listeners or a feature the notification host does not support or refuses
599     * to grant.
600     *
601     * <p>The service should wait for the {@link #onListenerConnected()} event
602     * before performing this operation.
603     *
604     * @return Zero or more of the HINT_ constants.
605     */
606    public final int getCurrentListenerHints() {
607        if (!isBound()) return 0;
608        try {
609            return getNotificationInterface().getHintsFromListener(mWrapper);
610        } catch (android.os.RemoteException ex) {
611            Log.v(TAG, "Unable to contact notification manager", ex);
612            return 0;
613        }
614    }
615
616    /**
617     * Gets the current notification interruption filter active on the host.
618     *
619     * <p>
620     * The interruption filter defines which notifications are allowed to interrupt the user
621     * (e.g. via sound &amp; vibration) and is applied globally. Listeners can find out whether
622     * a specific notification matched the interruption filter via
623     * {@link Ranking#matchesInterruptionFilter()}.
624     * <p>
625     * The current filter may differ from the previously requested filter if the notification host
626     * does not support or refuses to apply the requested filter, or if another component changed
627     * the filter in the meantime.
628     * <p>
629     * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
630     *
631     * <p>The service should wait for the {@link #onListenerConnected()} event
632     * before performing this operation.
633     *
634     * @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when
635     * unavailable.
636     */
637    public final int getCurrentInterruptionFilter() {
638        if (!isBound()) return INTERRUPTION_FILTER_UNKNOWN;
639        try {
640            return getNotificationInterface().getInterruptionFilterFromListener(mWrapper);
641        } catch (android.os.RemoteException ex) {
642            Log.v(TAG, "Unable to contact notification manager", ex);
643            return INTERRUPTION_FILTER_UNKNOWN;
644        }
645    }
646
647    /**
648     * Sets the desired {@link #getCurrentListenerHints() listener hints}.
649     *
650     * <p>
651     * This is merely a request, the host may or may not choose to take action depending
652     * on other listener requests or other global state.
653     * <p>
654     * Listen for updates using {@link #onListenerHintsChanged(int)}.
655     *
656     * <p>The service should wait for the {@link #onListenerConnected()} event
657     * before performing this operation.
658     *
659     * @param hints One or more of the HINT_ constants.
660     */
661    public final void requestListenerHints(int hints) {
662        if (!isBound()) return;
663        try {
664            getNotificationInterface().requestHintsFromListener(mWrapper, hints);
665        } catch (android.os.RemoteException ex) {
666            Log.v(TAG, "Unable to contact notification manager", ex);
667        }
668    }
669
670    /**
671     * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}.
672     *
673     * <p>
674     * This is merely a request, the host may or may not choose to apply the requested
675     * interruption filter depending on other listener requests or other global state.
676     * <p>
677     * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
678     *
679     * <p>The service should wait for the {@link #onListenerConnected()} event
680     * before performing this operation.
681     *
682     * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants.
683     */
684    public final void requestInterruptionFilter(int interruptionFilter) {
685        if (!isBound()) return;
686        try {
687            getNotificationInterface()
688                    .requestInterruptionFilterFromListener(mWrapper, interruptionFilter);
689        } catch (android.os.RemoteException ex) {
690            Log.v(TAG, "Unable to contact notification manager", ex);
691        }
692    }
693
694    /**
695     * Returns current ranking information.
696     *
697     * <p>
698     * The returned object represents the current ranking snapshot and only
699     * applies for currently active notifications.
700     * <p>
701     * Generally you should use the RankingMap that is passed with events such
702     * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)},
703     * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and
704     * so on. This method should only be used when needing access outside of
705     * such events, for example to retrieve the RankingMap right after
706     * initialization.
707     *
708     * <p>The service should wait for the {@link #onListenerConnected()} event
709     * before performing this operation.
710     *
711     * @return A {@link RankingMap} object providing access to ranking information
712     */
713    public RankingMap getCurrentRanking() {
714        synchronized (mLock) {
715            return mRankingMap;
716        }
717    }
718
719    /**
720     * This is not the lifecycle event you are looking for.
721     *
722     * <p>The service should wait for the {@link #onListenerConnected()} event
723     * before performing any operations.
724     */
725    @Override
726    public IBinder onBind(Intent intent) {
727        if (mWrapper == null) {
728            mWrapper = new NotificationListenerWrapper();
729        }
730        return mWrapper;
731    }
732
733    /** @hide */
734    protected boolean isBound() {
735        if (mWrapper == null) {
736            Log.w(TAG, "Notification listener service not yet bound.");
737            return false;
738        }
739        return true;
740    }
741
742    @Override
743    public void onDestroy() {
744        onListenerDisconnected();
745        super.onDestroy();
746    }
747
748    /**
749     * Directly register this service with the Notification Manager.
750     *
751     * <p>Only system services may use this call. It will fail for non-system callers.
752     * Apps should ask the user to add their listener in Settings.
753     *
754     * @param context Context required for accessing resources. Since this service isn't
755     *    launched as a real Service when using this method, a context has to be passed in.
756     * @param componentName the component that will consume the notification information
757     * @param currentUser the user to use as the stream filter
758     * @hide
759     */
760    @SystemApi
761    public void registerAsSystemService(Context context, ComponentName componentName,
762            int currentUser) throws RemoteException {
763        if (mWrapper == null) {
764            mWrapper = new NotificationListenerWrapper();
765        }
766        mSystemContext = context;
767        INotificationManager noMan = getNotificationInterface();
768        mHandler = new MyHandler(context.getMainLooper());
769        mCurrentUser = currentUser;
770        noMan.registerListener(mWrapper, componentName, currentUser);
771    }
772
773    /**
774     * Directly unregister this service from the Notification Manager.
775     *
776     * <p>This method will fail for listeners that were not registered
777     * with (@link registerAsService).
778     * @hide
779     */
780    @SystemApi
781    public void unregisterAsSystemService() throws RemoteException {
782        if (mWrapper != null) {
783            INotificationManager noMan = getNotificationInterface();
784            noMan.unregisterListener(mWrapper, mCurrentUser);
785        }
786    }
787
788    /**
789     * Request that the listener be rebound, after a previous call to (@link requestUnbind).
790     *
791     * <p>This method will fail for listeners that have
792     * not been granted the permission by the user.
793     */
794    public static void requestRebind(ComponentName componentName) {
795        INotificationManager noMan = INotificationManager.Stub.asInterface(
796                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
797        try {
798            noMan.requestBindListener(componentName);
799        } catch (RemoteException ex) {
800            throw ex.rethrowFromSystemServer();
801        }
802    }
803
804    /**
805     * Request that the service be unbound.
806     *
807     * <p>This will no longer receive updates until
808     * {@link #requestRebind(ComponentName)} is called.
809     * The service will likely be kiled by the system after this call.
810     *
811     * <p>The service should wait for the {@link #onListenerConnected()} event
812     * before performing this operation. I know it's tempting, but you must wait.
813     */
814    public final void requestUnbind() {
815        if (mWrapper != null) {
816            INotificationManager noMan = getNotificationInterface();
817            try {
818                noMan.requestUnbindListener(mWrapper);
819                // Disable future messages.
820                isConnected = false;
821            } catch (RemoteException ex) {
822                throw ex.rethrowFromSystemServer();
823            }
824        }
825    }
826
827    /** Convert new-style Icons to legacy representations for pre-M clients. */
828    private void createLegacyIconExtras(Notification n) {
829        Icon smallIcon = n.getSmallIcon();
830        Icon largeIcon = n.getLargeIcon();
831        if (smallIcon != null && smallIcon.getType() == Icon.TYPE_RESOURCE) {
832            n.extras.putInt(Notification.EXTRA_SMALL_ICON, smallIcon.getResId());
833            n.icon = smallIcon.getResId();
834        }
835        if (largeIcon != null) {
836            Drawable d = largeIcon.loadDrawable(getContext());
837            if (d != null && d instanceof BitmapDrawable) {
838                final Bitmap largeIconBits = ((BitmapDrawable) d).getBitmap();
839                n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, largeIconBits);
840                n.largeIcon = largeIconBits;
841            }
842        }
843    }
844
845    /**
846     * Populates remote views for pre-N targeting apps.
847     */
848    private void maybePopulateRemoteViews(Notification notification) {
849        if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
850            Builder builder = Builder.recoverBuilder(getContext(), notification);
851
852            // Some styles wrap Notification's contentView, bigContentView and headsUpContentView.
853            // First inflate them all, only then set them to avoid recursive wrapping.
854            RemoteViews content = builder.createContentView();
855            RemoteViews big = builder.createBigContentView();
856            RemoteViews headsUp = builder.createHeadsUpContentView();
857
858            notification.contentView = content;
859            notification.bigContentView = big;
860            notification.headsUpContentView = headsUp;
861        }
862    }
863
864    /** @hide */
865    protected class NotificationListenerWrapper extends INotificationListener.Stub {
866        @Override
867        public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
868                NotificationRankingUpdate update) {
869            StatusBarNotification sbn;
870            try {
871                sbn = sbnHolder.get();
872            } catch (RemoteException e) {
873                Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
874                return;
875            }
876
877            try {
878                // convert icon metadata to legacy format for older clients
879                createLegacyIconExtras(sbn.getNotification());
880                maybePopulateRemoteViews(sbn.getNotification());
881            } catch (IllegalArgumentException e) {
882                // warn and drop corrupt notification
883                Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
884                        sbn.getPackageName());
885                sbn = null;
886            }
887
888            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
889            synchronized (mLock) {
890                applyUpdateLocked(update);
891                if (sbn != null) {
892                    SomeArgs args = SomeArgs.obtain();
893                    args.arg1 = sbn;
894                    args.arg2 = mRankingMap;
895                    mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
896                            args).sendToTarget();
897                } else {
898                    // still pass along the ranking map, it may contain other information
899                    mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
900                            mRankingMap).sendToTarget();
901                }
902            }
903
904        }
905
906        @Override
907        public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
908                NotificationRankingUpdate update) {
909            StatusBarNotification sbn;
910            try {
911                sbn = sbnHolder.get();
912            } catch (RemoteException e) {
913                Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e);
914                return;
915            }
916            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
917            synchronized (mLock) {
918                applyUpdateLocked(update);
919                SomeArgs args = SomeArgs.obtain();
920                args.arg1 = sbn;
921                args.arg2 = mRankingMap;
922                mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED,
923                        args).sendToTarget();
924            }
925
926        }
927
928        @Override
929        public void onListenerConnected(NotificationRankingUpdate update) {
930            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
931            synchronized (mLock) {
932                applyUpdateLocked(update);
933            }
934            isConnected = true;
935            mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_CONNECTED).sendToTarget();
936        }
937
938        @Override
939        public void onNotificationRankingUpdate(NotificationRankingUpdate update)
940                throws RemoteException {
941            // protect subclass from concurrent modifications of (@link mNotificationKeys}.
942            synchronized (mLock) {
943                applyUpdateLocked(update);
944                mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
945                        mRankingMap).sendToTarget();
946            }
947
948        }
949
950        @Override
951        public void onListenerHintsChanged(int hints) throws RemoteException {
952            mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_HINTS_CHANGED,
953                    hints, 0).sendToTarget();
954        }
955
956        @Override
957        public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException {
958            mHandler.obtainMessage(MyHandler.MSG_ON_INTERRUPTION_FILTER_CHANGED,
959                    interruptionFilter, 0).sendToTarget();
960        }
961
962        @Override
963        public void onNotificationEnqueued(IStatusBarNotificationHolder notificationHolder,
964                                           int importance, boolean user) throws RemoteException {
965            // no-op in the listener
966        }
967
968        @Override
969        public void onNotificationVisibilityChanged(String key, long time, boolean visible)
970                throws RemoteException {
971            // no-op in the listener
972        }
973
974        @Override
975        public void onNotificationClick(String key, long time) throws RemoteException {
976            // no-op in the listener
977        }
978
979        @Override
980        public void onNotificationActionClick(String key, long time, int actionIndex)
981                throws RemoteException {
982            // no-op in the listener
983        }
984
985        @Override
986        public void onNotificationRemovedReason(String key, long time, int reason)
987                throws RemoteException {
988            // no-op in the listener
989        }
990    }
991
992    private void applyUpdateLocked(NotificationRankingUpdate update) {
993        mRankingMap = new RankingMap(update);
994    }
995
996    /** @hide */
997    protected Context getContext() {
998        if (mSystemContext != null) {
999            return mSystemContext;
1000        }
1001        return this;
1002    }
1003
1004    /**
1005     * Stores ranking related information on a currently active notification.
1006     *
1007     * <p>
1008     * Ranking objects aren't automatically updated as notification events
1009     * occur. Instead, ranking information has to be retrieved again via the
1010     * current {@link RankingMap}.
1011     */
1012    public static class Ranking {
1013
1014        /** Value signifying that the user has not expressed a per-app visibility override value.
1015         * @hide */
1016        public static final int VISIBILITY_NO_OVERRIDE = NotificationManager.VISIBILITY_NO_OVERRIDE;
1017
1018        /**
1019         * Value signifying that the user has not expressed an importance.
1020         *
1021         * This value is for persisting preferences, and should never be associated with
1022         * an actual notification.
1023         *
1024         * @hide
1025         */
1026        public static final int IMPORTANCE_UNSPECIFIED = NotificationManager.IMPORTANCE_UNSPECIFIED;
1027
1028        /**
1029         * A notification with no importance: shows nowhere, is blocked.
1030         *
1031         * @hide
1032         */
1033        public static final int IMPORTANCE_NONE = NotificationManager.IMPORTANCE_NONE;
1034
1035        /**
1036         * Min notification importance: only shows in the shade, below the fold.
1037         *
1038         * @hide
1039         */
1040        public static final int IMPORTANCE_MIN = NotificationManager.IMPORTANCE_MIN;
1041
1042        /**
1043         * Low notification importance: shows everywhere, but is not intrusive.
1044         *
1045         * @hide
1046         */
1047        public static final int IMPORTANCE_LOW = NotificationManager.IMPORTANCE_LOW;
1048
1049        /**
1050         * Default notification importance: shows everywhere, allowed to makes noise,
1051         * but does not visually intrude.
1052         *
1053         * @hide
1054         */
1055        public static final int IMPORTANCE_DEFAULT = NotificationManager.IMPORTANCE_DEFAULT;
1056
1057        /**
1058         * Higher notification importance: shows everywhere, allowed to makes noise and peek.
1059         *
1060         * @hide
1061         */
1062        public static final int IMPORTANCE_HIGH = NotificationManager.IMPORTANCE_HIGH;
1063
1064        /**
1065         * Highest notification importance: shows everywhere, allowed to makes noise, peek, and
1066         * use full screen intents.
1067         *
1068         * @hide
1069         */
1070        public static final int IMPORTANCE_MAX = NotificationManager.IMPORTANCE_MAX;
1071
1072        private String mKey;
1073        private int mRank = -1;
1074        private boolean mIsAmbient;
1075        private boolean mMatchesInterruptionFilter;
1076        private int mVisibilityOverride;
1077        private int mSuppressedVisualEffects;
1078        private @NotificationManager.Importance int mImportance;
1079        private CharSequence mImportanceExplanation;
1080        // System specified group key.
1081        private String mOverrideGroupKey;
1082
1083        public Ranking() {}
1084
1085        /**
1086         * Returns the key of the notification this Ranking applies to.
1087         */
1088        public String getKey() {
1089            return mKey;
1090        }
1091
1092        /**
1093         * Returns the rank of the notification.
1094         *
1095         * @return the rank of the notification, that is the 0-based index in
1096         *     the list of active notifications.
1097         */
1098        public int getRank() {
1099            return mRank;
1100        }
1101
1102        /**
1103         * Returns whether the notification is an ambient notification, that is
1104         * a notification that doesn't require the user's immediate attention.
1105         */
1106        public boolean isAmbient() {
1107            return mIsAmbient;
1108        }
1109
1110        /**
1111         * Returns the user specificed visibility for the package that posted
1112         * this notification, or
1113         * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
1114         * no such preference has been expressed.
1115         * @hide
1116         */
1117        public int getVisibilityOverride() {
1118            return mVisibilityOverride;
1119        }
1120
1121        /**
1122         * Returns the type(s) of visual effects that should be suppressed for this notification.
1123         * See {@link #SUPPRESSED_EFFECT_SCREEN_OFF}, {@link #SUPPRESSED_EFFECT_SCREEN_ON}.
1124         */
1125        public int getSuppressedVisualEffects() {
1126            return mSuppressedVisualEffects;
1127        }
1128
1129        /**
1130         * Returns whether the notification matches the user's interruption
1131         * filter.
1132         *
1133         * @return {@code true} if the notification is allowed by the filter, or
1134         * {@code false} if it is blocked.
1135         */
1136        public boolean matchesInterruptionFilter() {
1137            return mMatchesInterruptionFilter;
1138        }
1139
1140        /**
1141         * Returns the importance of the notification, which dictates its
1142         * modes of presentation, see: {@link NotificationManager#IMPORTANCE_DEFAULT}, etc.
1143         *
1144         * @return the rank of the notification
1145         */
1146        public @NotificationManager.Importance int getImportance() {
1147            return mImportance;
1148        }
1149
1150        /**
1151         * If the importance has been overriden by user preference, then this will be non-null,
1152         * and should be displayed to the user.
1153         *
1154         * @return the explanation for the importance, or null if it is the natural importance
1155         */
1156        public CharSequence getImportanceExplanation() {
1157            return mImportanceExplanation;
1158        }
1159
1160        /**
1161         * If the system has overriden the group key, then this will be non-null, and this
1162         * key should be used to bundle notifications.
1163         */
1164        public String getOverrideGroupKey() {
1165            return mOverrideGroupKey;
1166        }
1167
1168        private void populate(String key, int rank, boolean matchesInterruptionFilter,
1169                int visibilityOverride, int suppressedVisualEffects, int importance,
1170                CharSequence explanation, String overrideGroupKey) {
1171            mKey = key;
1172            mRank = rank;
1173            mIsAmbient = importance < IMPORTANCE_LOW;
1174            mMatchesInterruptionFilter = matchesInterruptionFilter;
1175            mVisibilityOverride = visibilityOverride;
1176            mSuppressedVisualEffects = suppressedVisualEffects;
1177            mImportance = importance;
1178            mImportanceExplanation = explanation;
1179            mOverrideGroupKey = overrideGroupKey;
1180        }
1181
1182        /**
1183         * {@hide}
1184         */
1185        public static String importanceToString(int importance) {
1186            switch (importance) {
1187                case IMPORTANCE_UNSPECIFIED:
1188                    return "UNSPECIFIED";
1189                case IMPORTANCE_NONE:
1190                    return "NONE";
1191                case IMPORTANCE_MIN:
1192                    return "MIN";
1193                case IMPORTANCE_LOW:
1194                    return "LOW";
1195                case IMPORTANCE_DEFAULT:
1196                    return "DEFAULT";
1197                case IMPORTANCE_HIGH:
1198                    return "HIGH";
1199                case IMPORTANCE_MAX:
1200                    return "MAX";
1201                default:
1202                    return "UNKNOWN(" + String.valueOf(importance) + ")";
1203            }
1204        }
1205    }
1206
1207    /**
1208     * Provides access to ranking information on currently active
1209     * notifications.
1210     *
1211     * <p>
1212     * Note that this object represents a ranking snapshot that only applies to
1213     * notifications active at the time of retrieval.
1214     */
1215    public static class RankingMap implements Parcelable {
1216        private final NotificationRankingUpdate mRankingUpdate;
1217        private ArrayMap<String,Integer> mRanks;
1218        private ArraySet<Object> mIntercepted;
1219        private ArrayMap<String, Integer> mVisibilityOverrides;
1220        private ArrayMap<String, Integer> mSuppressedVisualEffects;
1221        private ArrayMap<String, Integer> mImportance;
1222        private ArrayMap<String, String> mImportanceExplanation;
1223        private ArrayMap<String, String> mOverrideGroupKeys;
1224
1225        private RankingMap(NotificationRankingUpdate rankingUpdate) {
1226            mRankingUpdate = rankingUpdate;
1227        }
1228
1229        /**
1230         * Request the list of notification keys in their current ranking
1231         * order.
1232         *
1233         * @return An array of active notification keys, in their ranking order.
1234         */
1235        public String[] getOrderedKeys() {
1236            return mRankingUpdate.getOrderedKeys();
1237        }
1238
1239        /**
1240         * Populates outRanking with ranking information for the notification
1241         * with the given key.
1242         *
1243         * @return true if a valid key has been passed and outRanking has
1244         *     been populated; false otherwise
1245         */
1246        public boolean getRanking(String key, Ranking outRanking) {
1247            int rank = getRank(key);
1248            outRanking.populate(key, rank, !isIntercepted(key),
1249                    getVisibilityOverride(key), getSuppressedVisualEffects(key),
1250                    getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key));
1251            return rank >= 0;
1252        }
1253
1254        private int getRank(String key) {
1255            synchronized (this) {
1256                if (mRanks == null) {
1257                    buildRanksLocked();
1258                }
1259            }
1260            Integer rank = mRanks.get(key);
1261            return rank != null ? rank : -1;
1262        }
1263
1264        private boolean isIntercepted(String key) {
1265            synchronized (this) {
1266                if (mIntercepted == null) {
1267                    buildInterceptedSetLocked();
1268                }
1269            }
1270            return mIntercepted.contains(key);
1271        }
1272
1273        private int getVisibilityOverride(String key) {
1274            synchronized (this) {
1275                if (mVisibilityOverrides == null) {
1276                    buildVisibilityOverridesLocked();
1277                }
1278            }
1279            Integer override = mVisibilityOverrides.get(key);
1280            if (override == null) {
1281                return Ranking.VISIBILITY_NO_OVERRIDE;
1282            }
1283            return override.intValue();
1284        }
1285
1286        private int getSuppressedVisualEffects(String key) {
1287            synchronized (this) {
1288                if (mSuppressedVisualEffects == null) {
1289                    buildSuppressedVisualEffectsLocked();
1290                }
1291            }
1292            Integer suppressed = mSuppressedVisualEffects.get(key);
1293            if (suppressed == null) {
1294                return 0;
1295            }
1296            return suppressed.intValue();
1297        }
1298
1299        private int getImportance(String key) {
1300            synchronized (this) {
1301                if (mImportance == null) {
1302                    buildImportanceLocked();
1303                }
1304            }
1305            Integer importance = mImportance.get(key);
1306            if (importance == null) {
1307                return Ranking.IMPORTANCE_DEFAULT;
1308            }
1309            return importance.intValue();
1310        }
1311
1312        private String getImportanceExplanation(String key) {
1313            synchronized (this) {
1314                if (mImportanceExplanation == null) {
1315                    buildImportanceExplanationLocked();
1316                }
1317            }
1318            return mImportanceExplanation.get(key);
1319        }
1320
1321        private String getOverrideGroupKey(String key) {
1322            synchronized (this) {
1323                if (mOverrideGroupKeys == null) {
1324                    buildOverrideGroupKeys();
1325                }
1326            }
1327            return mOverrideGroupKeys.get(key);
1328        }
1329
1330        // Locked by 'this'
1331        private void buildRanksLocked() {
1332            String[] orderedKeys = mRankingUpdate.getOrderedKeys();
1333            mRanks = new ArrayMap<>(orderedKeys.length);
1334            for (int i = 0; i < orderedKeys.length; i++) {
1335                String key = orderedKeys[i];
1336                mRanks.put(key, i);
1337            }
1338        }
1339
1340        // Locked by 'this'
1341        private void buildInterceptedSetLocked() {
1342            String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys();
1343            mIntercepted = new ArraySet<>(dndInterceptedKeys.length);
1344            Collections.addAll(mIntercepted, dndInterceptedKeys);
1345        }
1346
1347        // Locked by 'this'
1348        private void buildVisibilityOverridesLocked() {
1349            Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides();
1350            mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size());
1351            for (String key: visibilityBundle.keySet()) {
1352               mVisibilityOverrides.put(key, visibilityBundle.getInt(key));
1353            }
1354        }
1355
1356        // Locked by 'this'
1357        private void buildSuppressedVisualEffectsLocked() {
1358            Bundle suppressedBundle = mRankingUpdate.getSuppressedVisualEffects();
1359            mSuppressedVisualEffects = new ArrayMap<>(suppressedBundle.size());
1360            for (String key: suppressedBundle.keySet()) {
1361                mSuppressedVisualEffects.put(key, suppressedBundle.getInt(key));
1362            }
1363        }
1364        // Locked by 'this'
1365        private void buildImportanceLocked() {
1366            String[] orderedKeys = mRankingUpdate.getOrderedKeys();
1367            int[] importance = mRankingUpdate.getImportance();
1368            mImportance = new ArrayMap<>(orderedKeys.length);
1369            for (int i = 0; i < orderedKeys.length; i++) {
1370                String key = orderedKeys[i];
1371                mImportance.put(key, importance[i]);
1372            }
1373        }
1374
1375        // Locked by 'this'
1376        private void buildImportanceExplanationLocked() {
1377            Bundle explanationBundle = mRankingUpdate.getImportanceExplanation();
1378            mImportanceExplanation = new ArrayMap<>(explanationBundle.size());
1379            for (String key: explanationBundle.keySet()) {
1380                mImportanceExplanation.put(key, explanationBundle.getString(key));
1381            }
1382        }
1383
1384        // Locked by 'this'
1385        private void buildOverrideGroupKeys() {
1386            Bundle overrideGroupKeys = mRankingUpdate.getOverrideGroupKeys();
1387            mOverrideGroupKeys = new ArrayMap<>(overrideGroupKeys.size());
1388            for (String key: overrideGroupKeys.keySet()) {
1389                mOverrideGroupKeys.put(key, overrideGroupKeys.getString(key));
1390            }
1391        }
1392
1393        // ----------- Parcelable
1394
1395        @Override
1396        public int describeContents() {
1397            return 0;
1398        }
1399
1400        @Override
1401        public void writeToParcel(Parcel dest, int flags) {
1402            dest.writeParcelable(mRankingUpdate, flags);
1403        }
1404
1405        public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() {
1406            @Override
1407            public RankingMap createFromParcel(Parcel source) {
1408                NotificationRankingUpdate rankingUpdate = source.readParcelable(null);
1409                return new RankingMap(rankingUpdate);
1410            }
1411
1412            @Override
1413            public RankingMap[] newArray(int size) {
1414                return new RankingMap[size];
1415            }
1416        };
1417    }
1418
1419    private final class MyHandler extends Handler {
1420        public static final int MSG_ON_NOTIFICATION_POSTED = 1;
1421        public static final int MSG_ON_NOTIFICATION_REMOVED = 2;
1422        public static final int MSG_ON_LISTENER_CONNECTED = 3;
1423        public static final int MSG_ON_NOTIFICATION_RANKING_UPDATE = 4;
1424        public static final int MSG_ON_LISTENER_HINTS_CHANGED = 5;
1425        public static final int MSG_ON_INTERRUPTION_FILTER_CHANGED = 6;
1426
1427        public MyHandler(Looper looper) {
1428            super(looper, null, false);
1429        }
1430
1431        @Override
1432        public void handleMessage(Message msg) {
1433            if (!isConnected) {
1434                return;
1435            }
1436            switch (msg.what) {
1437                case MSG_ON_NOTIFICATION_POSTED: {
1438                    SomeArgs args = (SomeArgs) msg.obj;
1439                    StatusBarNotification sbn = (StatusBarNotification) args.arg1;
1440                    RankingMap rankingMap = (RankingMap) args.arg2;
1441                    args.recycle();
1442                    onNotificationPosted(sbn, rankingMap);
1443                } break;
1444
1445                case MSG_ON_NOTIFICATION_REMOVED: {
1446                    SomeArgs args = (SomeArgs) msg.obj;
1447                    StatusBarNotification sbn = (StatusBarNotification) args.arg1;
1448                    RankingMap rankingMap = (RankingMap) args.arg2;
1449                    args.recycle();
1450                    onNotificationRemoved(sbn, rankingMap);
1451                } break;
1452
1453                case MSG_ON_LISTENER_CONNECTED: {
1454                    onListenerConnected();
1455                } break;
1456
1457                case MSG_ON_NOTIFICATION_RANKING_UPDATE: {
1458                    RankingMap rankingMap = (RankingMap) msg.obj;
1459                    onNotificationRankingUpdate(rankingMap);
1460                } break;
1461
1462                case MSG_ON_LISTENER_HINTS_CHANGED: {
1463                    final int hints = msg.arg1;
1464                    onListenerHintsChanged(hints);
1465                } break;
1466
1467                case MSG_ON_INTERRUPTION_FILTER_CHANGED: {
1468                    final int interruptionFilter = msg.arg1;
1469                    onInterruptionFilterChanged(interruptionFilter);
1470                } break;
1471            }
1472        }
1473    }
1474}
1475