1ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen/*
2ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Copyright (C) 2014 The Android Open Source Project
3ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen *
4ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Licensed under the Apache License, Version 2.0 (the "License");
5ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * you may not use this file except in compliance with the License.
6ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * You may obtain a copy of the License at
7ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen *
8ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen *      http://www.apache.org/licenses/LICENSE-2.0
9ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen *
10ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Unless required by applicable law or agreed to in writing, software
11ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * distributed under the License is distributed on an "AS IS" BASIS,
12ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * See the License for the specific language governing permissions and
14ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * limitations under the License.
15ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */
16ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
17ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenpackage android.support.v4.app;
18ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
19ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.app.Notification;
20ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.app.NotificationManager;
21ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.app.Service;
22ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.content.ComponentName;
23ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.content.Context;
24ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.content.Intent;
25ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.content.ServiceConnection;
26ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.content.pm.PackageManager;
27ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.content.pm.ResolveInfo;
28ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.Build;
29ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.Bundle;
30ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.DeadObjectException;
31ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.Handler;
32ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.HandlerThread;
33ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.IBinder;
34ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.Message;
35ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.RemoteException;
36ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.provider.Settings;
37ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.util.Log;
38ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
39ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport java.util.HashMap;
40ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport java.util.HashSet;
41ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport java.util.Iterator;
42ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport java.util.LinkedList;
43ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport java.util.List;
44ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport java.util.Map;
45ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport java.util.Set;
46ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
47ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen/**
48ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Compatibility library for NotificationManager with fallbacks for older platforms.
49ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen *
50ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * <p>To use this class, call the static function {@link #from} to get a
51ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * {@link NotificationManagerCompat} object, and then call one of its
52ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * methods to post or cancel notifications.
53ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */
54ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenpublic class NotificationManagerCompat {
55ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static final String TAG = "NotifManCompat";
56ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
57ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /**
58ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * Notification extras key: if set to true, the posted notification should use
59ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * the side channel for delivery instead of using notification manager.
60ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     */
61ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    public static final String EXTRA_USE_SIDE_CHANNEL =
62ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            NotificationCompatJellybean.EXTRA_USE_SIDE_CHANNEL;
63ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
64ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /**
65ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * Intent action to register for on a service to receive side channel
66ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * notifications. The listening service must be in the same package as an enabled
67ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * {@link android.service.notification.NotificationListenerService}.
68ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     */
69ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    public static final String ACTION_BIND_SIDE_CHANNEL =
701a7f163fad9e7d0f5bc67ad44d6bf9d73d672a86Griff Hazen            "android.support.BIND_NOTIFICATION_SIDE_CHANNEL";
71ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
726d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen    /**
736d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen     * Maximum sdk build version which needs support for side channeled notifications.
746d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen     * Currently the only needed use is for side channeling group children before KITKAT_WATCH.
756d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen     */
766d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen    static final int MAX_SIDE_CHANNEL_SDK_VERSION = 19;
776d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen
78ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /** Base time delay for a side channel listener queue retry. */
79ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static final int SIDE_CHANNEL_RETRY_BASE_INTERVAL_MS = 1000;
80ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /** Maximum retries for a side channel listener before dropping tasks. */
81ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static final int SIDE_CHANNEL_RETRY_MAX_COUNT = 6;
82ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
83ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static final String SETTING_ENABLED_NOTIFICATION_LISTENERS =
84ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            "enabled_notification_listeners";
85ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static final int SIDE_CHANNEL_BIND_FLAGS;
86ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
87ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /** Cache of enabled notification listener components */
88ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static final Object sEnabledNotificationListenersLock = new Object();
89ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /** Guarded by {@link #sEnabledNotificationListenersLock} */
90ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static String sEnabledNotificationListeners;
91ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /** Guarded by {@link #sEnabledNotificationListenersLock} */
92ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static Set<String> sEnabledNotificationListenerPackages = new HashSet<String>();
93ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
94ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private final Context mContext;
95ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private final NotificationManager mNotificationManager;
96ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /** Lock for mutable static fields */
97ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static final Object sLock = new Object();
98ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /** Guarded by {@link #sLock} */
99ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static SideChannelManager sSideChannelManager;
100ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
101ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /** Get a {@link NotificationManagerCompat} instance for a provided context. */
102ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    public static NotificationManagerCompat from(Context context) {
103ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        return new NotificationManagerCompat(context);
104ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
105ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
106ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private NotificationManagerCompat(Context context) {
107ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        mContext = context;
108ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        mNotificationManager = (NotificationManager) mContext.getSystemService(
109ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                Context.NOTIFICATION_SERVICE);
110ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
111ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
112ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static final Impl IMPL;
113ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
114ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    interface Impl {
115ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        void cancelNotification(NotificationManager notificationManager, String tag, int id);
116ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
117ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        void postNotification(NotificationManager notificationManager, String tag, int id,
118ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                Notification notification);
119ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
120ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        int getSideChannelBindFlags();
121ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
122ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
123ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    static class ImplBase implements Impl {
124ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        @Override
125ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public void cancelNotification(NotificationManager notificationManager, String tag,
126ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                int id) {
127ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            notificationManager.cancel(id);
128ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
129ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
130ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        @Override
131ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public void postNotification(NotificationManager notificationManager, String tag, int id,
132ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                Notification notification) {
133ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            notificationManager.notify(id, notification);
134ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
135ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
136ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        @Override
137ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public int getSideChannelBindFlags() {
138ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            return Service.BIND_AUTO_CREATE;
139ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
140ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
141ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
142ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    static class ImplEclair extends ImplBase {
143ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        @Override
144ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public void cancelNotification(NotificationManager notificationManager, String tag,
145ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                int id) {
146ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            NotificationManagerCompatEclair.cancelNotification(notificationManager, tag, id);
147ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
148ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
149ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        @Override
150ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public void postNotification(NotificationManager notificationManager, String tag, int id,
151ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                Notification notification) {
152ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            NotificationManagerCompatEclair.postNotification(notificationManager, tag, id,
153ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    notification);
154ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
155ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
156ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
1578dd0fbde8e9c7ae4bee382c5f7a75217b3830795Hui Lu    static class ImplIceCreamSandwich extends ImplEclair {
158ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        @Override
159ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public int getSideChannelBindFlags() {
160ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            return NotificationManagerCompatIceCreamSandwich.SIDE_CHANNEL_BIND_FLAGS;
161ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
162ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
163ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
164ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    static {
1658dd0fbde8e9c7ae4bee382c5f7a75217b3830795Hui Lu        if (Build.VERSION.SDK_INT >= 14) {
1668dd0fbde8e9c7ae4bee382c5f7a75217b3830795Hui Lu            IMPL = new ImplIceCreamSandwich();
1678dd0fbde8e9c7ae4bee382c5f7a75217b3830795Hui Lu        } else if (Build.VERSION.SDK_INT >= 5) {
168ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            IMPL = new ImplEclair();
169ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        } else {
170ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            IMPL = new ImplBase();
171ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
172ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        SIDE_CHANNEL_BIND_FLAGS = IMPL.getSideChannelBindFlags();
173ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
174ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
175ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /**
176ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * Cancel a previously shown notification.
177ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * @param id the ID of the notification
178ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     */
179ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    public void cancel(int id) {
180ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        cancel(null, id);
181ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
182ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
183ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /**
184ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * Cancel a previously shown notification.
185ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * @param tag the string identifier of the notification.
186ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * @param id the ID of the notification
187ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     */
188ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    public void cancel(String tag, int id) {
189ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        IMPL.cancelNotification(mNotificationManager, tag, id);
1906d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen        if (Build.VERSION.SDK_INT <= MAX_SIDE_CHANNEL_SDK_VERSION) {
1916d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen            pushSideChannelQueue(new CancelTask(mContext.getPackageName(), id, tag));
1926d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen        }
193ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
194ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
195ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /** Cancel all previously shown notifications. */
196ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    public void cancelAll() {
197ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        mNotificationManager.cancelAll();
1986d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen        if (Build.VERSION.SDK_INT <= MAX_SIDE_CHANNEL_SDK_VERSION) {
1996d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen            pushSideChannelQueue(new CancelTask(mContext.getPackageName()));
2006d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen        }
201ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
202ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
203ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /**
204ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * Post a notification to be shown in the status bar, stream, etc.
205ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * @param id the ID of the notification
206ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * @param notification the notification to post to the system
207ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     */
208ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    public void notify(int id, Notification notification) {
209ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        notify(null, id, notification);
210ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
211ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
212ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /**
213ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * Post a notification to be shown in the status bar, stream, etc.
214ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * @param tag the string identifier for a notification. Can be {@code null}.
215ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * @param id the ID of the notification. The pair (tag, id) must be unique within your app.
216ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * @param notification the notification to post to the system
217ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    */
218ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    public void notify(String tag, int id, Notification notification) {
219ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        if (useSideChannelForNotification(notification)) {
220ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            pushSideChannelQueue(new NotifyTask(mContext.getPackageName(), id, tag, notification));
2216d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen            // Cancel this notification in notification manager if it just transitioned to being
2226d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen            // side channelled.
2236d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen            IMPL.cancelNotification(mNotificationManager, tag, id);
224ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        } else {
225ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            IMPL.postNotification(mNotificationManager, tag, id, notification);
226ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
227ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
228ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
229ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /**
230ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * Get the set of packages that have an enabled notification listener component within them.
231ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     */
232ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    public static Set<String> getEnabledListenerPackages(Context context) {
233ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        final String enabledNotificationListeners = Settings.Secure.getString(
234ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                context.getContentResolver(),
235ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                SETTING_ENABLED_NOTIFICATION_LISTENERS);
236ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        // Parse the string again if it is different from the last time this method was called.
237ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        if (enabledNotificationListeners != null
238ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                && !enabledNotificationListeners.equals(sEnabledNotificationListeners)) {
239ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            final String[] components = enabledNotificationListeners.split(":");
240ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            Set<String> packageNames = new HashSet<String>(components.length);
241ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            for (String component : components) {
242ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                ComponentName componentName = ComponentName.unflattenFromString(component);
243ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                if (componentName != null) {
244ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    packageNames.add(componentName.getPackageName());
245ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                }
246ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
247ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            synchronized (sEnabledNotificationListenersLock) {
248ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                sEnabledNotificationListenerPackages = packageNames;
249ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                sEnabledNotificationListeners = enabledNotificationListeners;
250ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
251ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
252ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        return sEnabledNotificationListenerPackages;
253ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
254ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
255ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /**
256ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * Returns true if this notification should use the side channel for delivery.
257ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     */
258ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static boolean useSideChannelForNotification(Notification notification) {
259ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        Bundle extras = NotificationCompat.getExtras(notification);
260ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        return extras != null && extras.getBoolean(EXTRA_USE_SIDE_CHANNEL);
261ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
262ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
263ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /**
264ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * Push a notification task for distribution to notification side channels.
265ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     */
266ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private void pushSideChannelQueue(Task task) {
267ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        synchronized (sLock) {
268ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (sSideChannelManager == null) {
269ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                sSideChannelManager = new SideChannelManager(mContext.getApplicationContext());
270ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
271ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
272ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        sSideChannelManager.queueTask(task);
273ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
274ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
275ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    /**
276ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * Helper class to manage a queue of pending tasks to send to notification side channel
277ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     * listeners.
278ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen     */
279ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static class SideChannelManager implements Handler.Callback, ServiceConnection {
280ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private static final int MSG_QUEUE_TASK = 0;
281ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private static final int MSG_SERVICE_CONNECTED = 1;
282ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private static final int MSG_SERVICE_DISCONNECTED = 2;
283ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private static final int MSG_RETRY_LISTENER_QUEUE = 3;
284ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
285ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private static final String KEY_BINDER = "binder";
286ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
287ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private final Context mContext;
288ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private final HandlerThread mHandlerThread;
289ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private final Handler mHandler;
290ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private final Map<ComponentName, ListenerRecord> mRecordMap =
291ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                new HashMap<ComponentName, ListenerRecord>();
292ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private Set<String> mCachedEnabledPackages = new HashSet<String>();
293ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
294ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public SideChannelManager(Context context) {
295ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            mContext = context;
296ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            mHandlerThread = new HandlerThread("NotificationManagerCompat");
297ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            mHandlerThread.start();
298ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            mHandler = new Handler(mHandlerThread.getLooper(), this);
299ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
300ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
301ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        /**
302ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         * Queue a new task to be sent to all listeners. This function can be called
303ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         * from any thread.
304ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         */
305ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public void queueTask(Task task) {
306ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            mHandler.obtainMessage(MSG_QUEUE_TASK, task).sendToTarget();
307ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
308ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
309ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        @Override
310ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public boolean handleMessage(Message msg) {
311ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            switch (msg.what) {
312ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                case MSG_QUEUE_TASK:
313ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    handleQueueTask((Task) msg.obj);
314ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    return true;
315ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                case MSG_SERVICE_CONNECTED:
316ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    ServiceConnectedEvent event = (ServiceConnectedEvent) msg.obj;
317ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    handleServiceConnected(event.componentName, event.iBinder);
318ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    return true;
319ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                case MSG_SERVICE_DISCONNECTED:
320ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    handleServiceDisconnected((ComponentName) msg.obj);
321ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    return true;
322ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                case MSG_RETRY_LISTENER_QUEUE:
323ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    handleRetryListenerQueue((ComponentName) msg.obj);
324ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    return true;
325ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
326ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            return false;
327ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
328ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
329ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private void handleQueueTask(Task task) {
330ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            updateListenerMap();
331ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            for (ListenerRecord record : mRecordMap.values()) {
332ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                record.taskQueue.add(task);
333ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                processListenerQueue(record);
334ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
335ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
336ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
337ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private void handleServiceConnected(ComponentName componentName, IBinder iBinder) {
338ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            ListenerRecord record = mRecordMap.get(componentName);
339ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (record != null) {
340ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                record.service = INotificationSideChannel.Stub.asInterface(iBinder);
341ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                record.retryCount = 0;
342ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                processListenerQueue(record);
343ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
344ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
345ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
346ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private void handleServiceDisconnected(ComponentName componentName) {
347ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            ListenerRecord record = mRecordMap.get(componentName);
348ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (record != null) {
349ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                ensureServiceUnbound(record);
350ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
351ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
352ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
353ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private void handleRetryListenerQueue(ComponentName componentName) {
354ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            ListenerRecord record = mRecordMap.get(componentName);
355ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (record != null) {
356ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                processListenerQueue(record);
357ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
358ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
359ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
360ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        @Override
361ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
362ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (Log.isLoggable(TAG, Log.DEBUG)) {
363ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                Log.d(TAG, "Connected to service " + componentName);
364ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
365ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            mHandler.obtainMessage(MSG_SERVICE_CONNECTED,
366ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    new ServiceConnectedEvent(componentName, iBinder))
367ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    .sendToTarget();
368ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
369ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
370ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        @Override
371ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public void onServiceDisconnected(ComponentName componentName) {
372ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (Log.isLoggable(TAG, Log.DEBUG)) {
373ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                Log.d(TAG, "Disconnected from service " + componentName);
374ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
375ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            mHandler.obtainMessage(MSG_SERVICE_DISCONNECTED, componentName).sendToTarget();
376ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
377ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
378ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        /**
379ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         * Check the current list of enabled listener packages and update the records map
380ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         * accordingly.
381ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         */
382ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private void updateListenerMap() {
383ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            Set<String> enabledPackages = getEnabledListenerPackages(mContext);
384ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (enabledPackages.equals(mCachedEnabledPackages)) {
385ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                // Short-circuit when the list of enabled packages has not changed.
386ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                return;
387ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
388ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            mCachedEnabledPackages = enabledPackages;
389ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServices(
390ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    new Intent().setAction(ACTION_BIND_SIDE_CHANNEL), PackageManager.GET_SERVICES);
391ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            Set<ComponentName> enabledComponents = new HashSet<ComponentName>();
392ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            for (ResolveInfo resolveInfo : resolveInfos) {
393ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                if (!enabledPackages.contains(resolveInfo.serviceInfo.packageName)) {
394ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    continue;
395ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                }
396ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                ComponentName componentName = new ComponentName(
397ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                        resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
398ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                if (resolveInfo.serviceInfo.permission != null) {
399ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    Log.w(TAG, "Permission present on component " + componentName
400ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                            + ", not adding listener record.");
401ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    continue;
402ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                }
403ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                enabledComponents.add(componentName);
404ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
405ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            // Ensure all enabled components have a record in the listener map.
406ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            for (ComponentName componentName : enabledComponents) {
407ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                if (!mRecordMap.containsKey(componentName)) {
408ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    if (Log.isLoggable(TAG, Log.DEBUG)) {
409ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                        Log.d(TAG, "Adding listener record for " + componentName);
410ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    }
411ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    mRecordMap.put(componentName, new ListenerRecord(componentName));
412ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                }
413ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
414ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            // Remove listener records that are no longer for enabled components.
415ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            Iterator<Map.Entry<ComponentName, ListenerRecord>> it =
416ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    mRecordMap.entrySet().iterator();
417ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            while (it.hasNext()) {
418ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                Map.Entry<ComponentName, ListenerRecord> entry = it.next();
419ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                if (!enabledComponents.contains(entry.getKey())) {
420ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    if (Log.isLoggable(TAG, Log.DEBUG)) {
421ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                        Log.d(TAG, "Removing listener record for " + entry.getKey());
422ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    }
423ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    ensureServiceUnbound(entry.getValue());
424ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    it.remove();
425ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                }
426ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
427ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
428ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
429ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        /**
430ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         * Ensure we are already attempting to bind to a service, or start a new binding if not.
431ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         * @return Whether the service bind attempt was successful.
432ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         */
433ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private boolean ensureServiceBound(ListenerRecord record) {
434ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (record.bound) {
435ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                return true;
436ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
437ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            Intent intent = new Intent(ACTION_BIND_SIDE_CHANNEL).setComponent(record.componentName);
438ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            record.bound = mContext.bindService(intent, this, SIDE_CHANNEL_BIND_FLAGS);
439ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (record.bound) {
440ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                record.retryCount = 0;
441ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            } else {
442ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                Log.w(TAG, "Unable to bind to listener " + record.componentName);
443ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                mContext.unbindService(this);
444ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
445ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            return record.bound;
446ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
447ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
448ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        /**
449ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         * Ensure we have unbound from a service.
450ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         */
451ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private void ensureServiceUnbound(ListenerRecord record) {
452ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (record.bound) {
453ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                mContext.unbindService(this);
454ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                record.bound = false;
455ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
456ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            record.service = null;
457ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
458ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
459ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        /**
460ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         * Schedule a delayed retry to communicate with a listener service.
461ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         * After a maximum number of attempts (with exponential back-off), start
462ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         * dropping pending tasks for this listener.
463ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         */
464ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private void scheduleListenerRetry(ListenerRecord record) {
465ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (mHandler.hasMessages(MSG_RETRY_LISTENER_QUEUE, record.componentName)) {
466ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                return;
467ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
468ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            record.retryCount++;
469ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (record.retryCount > SIDE_CHANNEL_RETRY_MAX_COUNT) {
470ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                Log.w(TAG, "Giving up on delivering " + record.taskQueue.size() + " tasks to "
471ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                        + record.componentName + " after " + record.retryCount + " retries");
472ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                record.taskQueue.clear();
473ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                return;
474ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
475ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            int delayMs = SIDE_CHANNEL_RETRY_BASE_INTERVAL_MS * (1 << (record.retryCount - 1));
476ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (Log.isLoggable(TAG, Log.DEBUG)) {
477ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                Log.d(TAG, "Scheduling retry for " + delayMs + " ms");
478ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
479ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            Message msg = mHandler.obtainMessage(MSG_RETRY_LISTENER_QUEUE, record.componentName);
480ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            mHandler.sendMessageDelayed(msg, delayMs);
481ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
482ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
483ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        /**
484ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         * Perform a processing step for a listener. First check the bind state, then attempt
485ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         * to flush the task queue, and if an error is encountered, schedule a retry.
486ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen         */
487ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private void processListenerQueue(ListenerRecord record) {
488ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (Log.isLoggable(TAG, Log.DEBUG)) {
489ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                Log.d(TAG, "Processing component " + record.componentName + ", "
490ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                        + record.taskQueue.size() + " queued tasks");
491ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
492ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (record.taskQueue.isEmpty()) {
493ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                return;
494ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
495ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (!ensureServiceBound(record) || record.service == null) {
496ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                // Ensure bind has started and that a service interface is ready to use.
497ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                scheduleListenerRetry(record);
498ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                return;
499ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
500ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            // Attempt to flush all items in the task queue.
501ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            while (true) {
502ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                Task task = record.taskQueue.peek();
503ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                if (task == null) {
504ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    break;
505ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                }
506ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                try {
507ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    if (Log.isLoggable(TAG, Log.DEBUG)) {
508ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                        Log.d(TAG, "Sending task " + task);
509ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    }
510ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    task.send(record.service);
511ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    record.taskQueue.remove();
512ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                } catch (DeadObjectException e) {
513ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    if (Log.isLoggable(TAG, Log.DEBUG)) {
514ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                        Log.d(TAG, "Remote service has died: " + record.componentName);
515ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    }
516ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    break;
517ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                } catch (RemoteException e) {
518ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    Log.w(TAG, "RemoteException communicating with " + record.componentName, e);
519ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    break;
520ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                }
521ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
522ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (!record.taskQueue.isEmpty()) {
523ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                // Some tasks were not sent, meaning an error was encountered, schedule a retry.
524ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                scheduleListenerRetry(record);
525ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
526ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
527ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
528ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        /** A per-side-channel-service listener state record */
529ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        private static class ListenerRecord {
530ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            public final ComponentName componentName;
531ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            /** Whether the service is currently bound to. */
532ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            public boolean bound = false;
533ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            /** The service stub provided by onServiceConnected */
534ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            public INotificationSideChannel service;
535ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            /** Queue of pending tasks to send to this listener service */
536ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            public LinkedList<Task> taskQueue = new LinkedList<Task>();
537ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            /** Number of retries attempted while connecting to this listener service */
538ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            public int retryCount = 0;
539ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
540ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            public ListenerRecord(ComponentName componentName) {
541ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                this.componentName = componentName;
542ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
543ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
544ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
545ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
546ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static class ServiceConnectedEvent {
547ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        final ComponentName componentName;
548ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        final IBinder iBinder;
549ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
550ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public ServiceConnectedEvent(ComponentName componentName,
551ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                final IBinder iBinder) {
552ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            this.componentName = componentName;
553ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            this.iBinder = iBinder;
554ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
555ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
556ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
557ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private interface Task {
558ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public void send(INotificationSideChannel service) throws RemoteException;
559ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
560ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
561ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static class NotifyTask implements Task {
562ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        final String packageName;
563ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        final int id;
564ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        final String tag;
565ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        final Notification notif;
566ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
567ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public NotifyTask(String packageName, int id, String tag, Notification notif) {
568ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            this.packageName = packageName;
569ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            this.id = id;
570ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            this.tag = tag;
571ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            this.notif = notif;
572ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
573ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
574ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        @Override
575ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public void send(INotificationSideChannel service) throws RemoteException {
576ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            service.notify(packageName, id, tag, notif);
577ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
578ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
579ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public String toString() {
580ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            StringBuilder sb = new StringBuilder("NotifyTask[");
581ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            sb.append("packageName:").append(packageName);
582ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            sb.append(", id:").append(id);
583ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            sb.append(", tag:").append(tag);
584ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            sb.append("]");
585ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            return sb.toString();
586ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
587ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
588ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
589ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    private static class CancelTask implements Task {
590ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        final String packageName;
591ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        final int id;
592ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        final String tag;
593ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        final boolean all;
594ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
595ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public CancelTask(String packageName) {
596ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            this.packageName = packageName;
597ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            this.id = 0;
598ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            this.tag = null;
599ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            this.all = true;
600ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
601ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
602ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public CancelTask(String packageName, int id, String tag) {
603ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            this.packageName = packageName;
604ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            this.id = id;
605ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            this.tag = tag;
606ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            this.all = false;
607ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
608ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
609ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        @Override
610ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public void send(INotificationSideChannel service) throws RemoteException {
611ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            if (all) {
612ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                service.cancelAll(packageName);
613ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            } else {
614ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                service.cancel(packageName, id, tag);
615ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            }
616ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
617ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
618ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        public String toString() {
619ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            StringBuilder sb = new StringBuilder("CancelTask[");
620ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            sb.append("packageName:").append(packageName);
621ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            sb.append(", id:").append(id);
622ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            sb.append(", tag:").append(tag);
623ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            sb.append(", all:").append(all);
624ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            sb.append("]");
625ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            return sb.toString();
626ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
627ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
628ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen}
629