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