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 19b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikasimport android.app.AppOpsManager; 20ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.app.Notification; 21ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.app.NotificationManager; 22ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.app.Service; 23ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.content.ComponentName; 24ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.content.Context; 25ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.content.Intent; 26ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.content.ServiceConnection; 27b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikasimport android.content.pm.ApplicationInfo; 28ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.content.pm.PackageManager; 29ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.content.pm.ResolveInfo; 30ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.Build; 31ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.Bundle; 32ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.DeadObjectException; 33ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.Handler; 34ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.HandlerThread; 35ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.IBinder; 36ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.Message; 37ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.RemoteException; 38ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.provider.Settings; 399455ad06a2852e9edb3231c1a609fc6a9761ebb0Siyamed Sinirimport android.support.annotation.GuardedBy; 40ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.util.Log; 41ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 42b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikasimport java.lang.reflect.Field; 43b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikasimport java.lang.reflect.InvocationTargetException; 44b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikasimport java.lang.reflect.Method; 45ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport java.util.HashMap; 46ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport java.util.HashSet; 47ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport java.util.Iterator; 48ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport java.util.LinkedList; 49ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport java.util.List; 50ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport java.util.Map; 51ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport java.util.Set; 52ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 53ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen/** 54ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Compatibility library for NotificationManager with fallbacks for older platforms. 55ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * 56ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * <p>To use this class, call the static function {@link #from} to get a 57ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * {@link NotificationManagerCompat} object, and then call one of its 58ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * methods to post or cancel notifications. 59ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 60c5847d13e40f5d52459f5c0dab32dc08f1a9a683Chris Banespublic final class NotificationManagerCompat { 61ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static final String TAG = "NotifManCompat"; 62b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas private static final String CHECK_OP_NO_THROW = "checkOpNoThrow"; 63b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas private static final String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION"; 64ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 65ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 66ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Notification extras key: if set to true, the posted notification should use 67ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * the side channel for delivery instead of using notification manager. 68ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 69ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public static final String EXTRA_USE_SIDE_CHANNEL = 70ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen NotificationCompatJellybean.EXTRA_USE_SIDE_CHANNEL; 71ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 72ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 73ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Intent action to register for on a service to receive side channel 74ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * notifications. The listening service must be in the same package as an enabled 75ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * {@link android.service.notification.NotificationListenerService}. 76ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 77ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public static final String ACTION_BIND_SIDE_CHANNEL = 781a7f163fad9e7d0f5bc67ad44d6bf9d73d672a86Griff Hazen "android.support.BIND_NOTIFICATION_SIDE_CHANNEL"; 79ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 806d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen /** 816d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen * Maximum sdk build version which needs support for side channeled notifications. 826d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen * Currently the only needed use is for side channeling group children before KITKAT_WATCH. 836d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen */ 846d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen static final int MAX_SIDE_CHANNEL_SDK_VERSION = 19; 856d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen 86ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** Base time delay for a side channel listener queue retry. */ 87ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static final int SIDE_CHANNEL_RETRY_BASE_INTERVAL_MS = 1000; 88ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** Maximum retries for a side channel listener before dropping tasks. */ 89ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static final int SIDE_CHANNEL_RETRY_MAX_COUNT = 6; 90ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */ 91ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static final String SETTING_ENABLED_NOTIFICATION_LISTENERS = 92ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen "enabled_notification_listeners"; 93ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 94ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** Cache of enabled notification listener components */ 95ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static final Object sEnabledNotificationListenersLock = new Object(); 969455ad06a2852e9edb3231c1a609fc6a9761ebb0Siyamed Sinir @GuardedBy("sEnabledNotificationListenersLock") 97ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static String sEnabledNotificationListeners; 989455ad06a2852e9edb3231c1a609fc6a9761ebb0Siyamed Sinir @GuardedBy("sEnabledNotificationListenersLock") 99ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static Set<String> sEnabledNotificationListenerPackages = new HashSet<String>(); 100ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 101ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private final Context mContext; 102ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private final NotificationManager mNotificationManager; 103ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** Lock for mutable static fields */ 104ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static final Object sLock = new Object(); 1059455ad06a2852e9edb3231c1a609fc6a9761ebb0Siyamed Sinir @GuardedBy("sLock") 106ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static SideChannelManager sSideChannelManager; 107ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 108355267b12d57afc082bd253919229321396b12cdChris Wren /** 109355267b12d57afc082bd253919229321396b12cdChris Wren * Value signifying that the user has not expressed an importance. 110355267b12d57afc082bd253919229321396b12cdChris Wren * 111355267b12d57afc082bd253919229321396b12cdChris Wren * This value is for persisting preferences, and should never be associated with 112355267b12d57afc082bd253919229321396b12cdChris Wren * an actual notification. 113355267b12d57afc082bd253919229321396b12cdChris Wren */ 114355267b12d57afc082bd253919229321396b12cdChris Wren public static final int IMPORTANCE_UNSPECIFIED = -1000; 115355267b12d57afc082bd253919229321396b12cdChris Wren 116355267b12d57afc082bd253919229321396b12cdChris Wren /** 117355267b12d57afc082bd253919229321396b12cdChris Wren * A notification with no importance: shows nowhere, is blocked. 118355267b12d57afc082bd253919229321396b12cdChris Wren */ 119355267b12d57afc082bd253919229321396b12cdChris Wren public static final int IMPORTANCE_NONE = 0; 120355267b12d57afc082bd253919229321396b12cdChris Wren 121355267b12d57afc082bd253919229321396b12cdChris Wren /** 122355267b12d57afc082bd253919229321396b12cdChris Wren * Min notification importance: only shows in the shade, below the fold. 123355267b12d57afc082bd253919229321396b12cdChris Wren */ 124355267b12d57afc082bd253919229321396b12cdChris Wren public static final int IMPORTANCE_MIN = 1; 125355267b12d57afc082bd253919229321396b12cdChris Wren 126355267b12d57afc082bd253919229321396b12cdChris Wren /** 127355267b12d57afc082bd253919229321396b12cdChris Wren * Low notification importance: shows everywhere, but is not intrusive. 128355267b12d57afc082bd253919229321396b12cdChris Wren */ 129355267b12d57afc082bd253919229321396b12cdChris Wren public static final int IMPORTANCE_LOW = 2; 130355267b12d57afc082bd253919229321396b12cdChris Wren 131355267b12d57afc082bd253919229321396b12cdChris Wren /** 132355267b12d57afc082bd253919229321396b12cdChris Wren * Default notification importance: shows everywhere, allowed to makes noise, 133355267b12d57afc082bd253919229321396b12cdChris Wren * but does not visually intrude. 134355267b12d57afc082bd253919229321396b12cdChris Wren */ 135355267b12d57afc082bd253919229321396b12cdChris Wren public static final int IMPORTANCE_DEFAULT = 3; 136355267b12d57afc082bd253919229321396b12cdChris Wren 137355267b12d57afc082bd253919229321396b12cdChris Wren /** 138355267b12d57afc082bd253919229321396b12cdChris Wren * Higher notification importance: shows everywhere, allowed to makes noise and peek. 139355267b12d57afc082bd253919229321396b12cdChris Wren */ 140355267b12d57afc082bd253919229321396b12cdChris Wren public static final int IMPORTANCE_HIGH = 4; 141355267b12d57afc082bd253919229321396b12cdChris Wren 142355267b12d57afc082bd253919229321396b12cdChris Wren /** 143355267b12d57afc082bd253919229321396b12cdChris Wren * Highest notification importance: shows everywhere, allowed to makes noise, peek, and 144355267b12d57afc082bd253919229321396b12cdChris Wren * use full screen intents. 145355267b12d57afc082bd253919229321396b12cdChris Wren */ 146355267b12d57afc082bd253919229321396b12cdChris Wren public static final int IMPORTANCE_MAX = 5; 147355267b12d57afc082bd253919229321396b12cdChris Wren 148ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** Get a {@link NotificationManagerCompat} instance for a provided context. */ 149ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public static NotificationManagerCompat from(Context context) { 150ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return new NotificationManagerCompat(context); 151ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 152ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 153ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private NotificationManagerCompat(Context context) { 154ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mContext = context; 155ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mNotificationManager = (NotificationManager) mContext.getSystemService( 156ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Context.NOTIFICATION_SERVICE); 157ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 158ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 159ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 160ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Cancel a previously shown notification. 161ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * @param id the ID of the notification 162ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 163ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public void cancel(int id) { 164ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen cancel(null, id); 165ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 166ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 167ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 168ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Cancel a previously shown notification. 169ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * @param tag the string identifier of the notification. 170ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * @param id the ID of the notification 171ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 172ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public void cancel(String tag, int id) { 173b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas mNotificationManager.cancel(tag, id); 1746d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen if (Build.VERSION.SDK_INT <= MAX_SIDE_CHANNEL_SDK_VERSION) { 1756d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen pushSideChannelQueue(new CancelTask(mContext.getPackageName(), id, tag)); 1766d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen } 177ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 178ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 179ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** Cancel all previously shown notifications. */ 180ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public void cancelAll() { 181ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mNotificationManager.cancelAll(); 1826d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen if (Build.VERSION.SDK_INT <= MAX_SIDE_CHANNEL_SDK_VERSION) { 1836d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen pushSideChannelQueue(new CancelTask(mContext.getPackageName())); 1846d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen } 185ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 186ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 187ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 188ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Post a notification to be shown in the status bar, stream, etc. 189ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * @param id the ID of the notification 190ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * @param notification the notification to post to the system 191ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 192ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public void notify(int id, Notification notification) { 193ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen notify(null, id, notification); 194ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 195ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 196ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 197ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Post a notification to be shown in the status bar, stream, etc. 198ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * @param tag the string identifier for a notification. Can be {@code null}. 199ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * @param id the ID of the notification. The pair (tag, id) must be unique within your app. 200ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * @param notification the notification to post to the system 201ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 202ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public void notify(String tag, int id, Notification notification) { 203ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (useSideChannelForNotification(notification)) { 204ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen pushSideChannelQueue(new NotifyTask(mContext.getPackageName(), id, tag, notification)); 2056d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen // Cancel this notification in notification manager if it just transitioned to being 2066d7958ab7712efa7d6aece311208e4f2d43c1886Griff Hazen // side channelled. 207b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas mNotificationManager.cancel(tag, id); 208ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } else { 209b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas mNotificationManager.notify(tag, id, notification); 210ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 211ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 212ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 213ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 21485e038ee7cac169b3a9878cba881dc7eb401de2aChris Wren * Returns whether notifications from the calling package are not blocked. 21585e038ee7cac169b3a9878cba881dc7eb401de2aChris Wren */ 21685e038ee7cac169b3a9878cba881dc7eb401de2aChris Wren public boolean areNotificationsEnabled() { 217b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas if (Build.VERSION.SDK_INT >= 24) { 218b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas return mNotificationManager.areNotificationsEnabled(); 219b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas } else if (Build.VERSION.SDK_INT >= 19) { 220b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas AppOpsManager appOps = 221b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); 222b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas ApplicationInfo appInfo = mContext.getApplicationInfo(); 223b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas String pkg = mContext.getApplicationContext().getPackageName(); 224b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas int uid = appInfo.uid; 225b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas try { 226b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas Class<?> appOpsClass = Class.forName(AppOpsManager.class.getName()); 227b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, 228b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas Integer.TYPE, String.class); 229b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION); 230b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas int value = (int) opPostNotificationValue.get(Integer.class); 231b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas return ((int) checkOpNoThrowMethod.invoke(appOps, value, uid, pkg) 232b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas == AppOpsManager.MODE_ALLOWED); 233b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas } catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException 234b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas | InvocationTargetException | IllegalAccessException | RuntimeException e) { 235b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas return true; 236b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas } 237b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas } else { 238b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas return true; 239b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas } 24085e038ee7cac169b3a9878cba881dc7eb401de2aChris Wren } 24185e038ee7cac169b3a9878cba881dc7eb401de2aChris Wren 24285e038ee7cac169b3a9878cba881dc7eb401de2aChris Wren /** 24368efa9b82032ba6e7cf88d56ae7f0662f2702736Chris Wren * Returns the user specified importance for notifications from the calling package. 244355267b12d57afc082bd253919229321396b12cdChris Wren * 245355267b12d57afc082bd253919229321396b12cdChris Wren * @return An importance level, such as {@link #IMPORTANCE_DEFAULT}. 24668efa9b82032ba6e7cf88d56ae7f0662f2702736Chris Wren */ 24768efa9b82032ba6e7cf88d56ae7f0662f2702736Chris Wren public int getImportance() { 248b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas if (Build.VERSION.SDK_INT >= 24) { 249b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas return mNotificationManager.getImportance(); 250b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas } else { 251b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas return IMPORTANCE_UNSPECIFIED; 252b532ea071639e4799fe51d0cae397561f69bd903Aurimas Liutikas } 25368efa9b82032ba6e7cf88d56ae7f0662f2702736Chris Wren } 25468efa9b82032ba6e7cf88d56ae7f0662f2702736Chris Wren 25568efa9b82032ba6e7cf88d56ae7f0662f2702736Chris Wren /** 256ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Get the set of packages that have an enabled notification listener component within them. 257ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 258ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public static Set<String> getEnabledListenerPackages(Context context) { 259ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen final String enabledNotificationListeners = Settings.Secure.getString( 260ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen context.getContentResolver(), 261ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen SETTING_ENABLED_NOTIFICATION_LISTENERS); 262c7784e83eb72a5fa7e612a9457bd938639ed211fAlan Viverette synchronized (sEnabledNotificationListenersLock) { 263c7784e83eb72a5fa7e612a9457bd938639ed211fAlan Viverette // Parse the string again if it is different from the last time this method was called. 264c7784e83eb72a5fa7e612a9457bd938639ed211fAlan Viverette if (enabledNotificationListeners != null 265c7784e83eb72a5fa7e612a9457bd938639ed211fAlan Viverette && !enabledNotificationListeners.equals(sEnabledNotificationListeners)) { 266c7784e83eb72a5fa7e612a9457bd938639ed211fAlan Viverette final String[] components = enabledNotificationListeners.split(":"); 267c7784e83eb72a5fa7e612a9457bd938639ed211fAlan Viverette Set<String> packageNames = new HashSet<String>(components.length); 268c7784e83eb72a5fa7e612a9457bd938639ed211fAlan Viverette for (String component : components) { 269c7784e83eb72a5fa7e612a9457bd938639ed211fAlan Viverette ComponentName componentName = ComponentName.unflattenFromString(component); 270c7784e83eb72a5fa7e612a9457bd938639ed211fAlan Viverette if (componentName != null) { 271c7784e83eb72a5fa7e612a9457bd938639ed211fAlan Viverette packageNames.add(componentName.getPackageName()); 272c7784e83eb72a5fa7e612a9457bd938639ed211fAlan Viverette } 273ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 274ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen sEnabledNotificationListenerPackages = packageNames; 275ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen sEnabledNotificationListeners = enabledNotificationListeners; 276ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 277c7784e83eb72a5fa7e612a9457bd938639ed211fAlan Viverette return sEnabledNotificationListenerPackages; 278ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 279ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 280ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 281ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 282ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Returns true if this notification should use the side channel for delivery. 283ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 284ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static boolean useSideChannelForNotification(Notification notification) { 285ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Bundle extras = NotificationCompat.getExtras(notification); 286ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return extras != null && extras.getBoolean(EXTRA_USE_SIDE_CHANNEL); 287ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 288ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 289ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 290ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Push a notification task for distribution to notification side channels. 291ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 292ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private void pushSideChannelQueue(Task task) { 293ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen synchronized (sLock) { 294ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (sSideChannelManager == null) { 295ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen sSideChannelManager = new SideChannelManager(mContext.getApplicationContext()); 296ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 297c7784e83eb72a5fa7e612a9457bd938639ed211fAlan Viverette sSideChannelManager.queueTask(task); 298ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 299ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 300ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 301ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 302ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Helper class to manage a queue of pending tasks to send to notification side channel 303ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * listeners. 304ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 305ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static class SideChannelManager implements Handler.Callback, ServiceConnection { 306ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static final int MSG_QUEUE_TASK = 0; 307ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static final int MSG_SERVICE_CONNECTED = 1; 308ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static final int MSG_SERVICE_DISCONNECTED = 2; 309ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static final int MSG_RETRY_LISTENER_QUEUE = 3; 310ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 311ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private final Context mContext; 312ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private final HandlerThread mHandlerThread; 313ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private final Handler mHandler; 314ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private final Map<ComponentName, ListenerRecord> mRecordMap = 315ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen new HashMap<ComponentName, ListenerRecord>(); 316ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private Set<String> mCachedEnabledPackages = new HashSet<String>(); 317ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 318ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public SideChannelManager(Context context) { 319ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mContext = context; 320ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mHandlerThread = new HandlerThread("NotificationManagerCompat"); 321ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mHandlerThread.start(); 322ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mHandler = new Handler(mHandlerThread.getLooper(), this); 323ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 324ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 325ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 326ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Queue a new task to be sent to all listeners. This function can be called 327ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * from any thread. 328ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 329ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public void queueTask(Task task) { 330ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mHandler.obtainMessage(MSG_QUEUE_TASK, task).sendToTarget(); 331ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 332ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 333ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen @Override 334ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public boolean handleMessage(Message msg) { 335ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen switch (msg.what) { 336ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen case MSG_QUEUE_TASK: 337ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen handleQueueTask((Task) msg.obj); 338ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return true; 339ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen case MSG_SERVICE_CONNECTED: 340ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen ServiceConnectedEvent event = (ServiceConnectedEvent) msg.obj; 341ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen handleServiceConnected(event.componentName, event.iBinder); 342ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return true; 343ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen case MSG_SERVICE_DISCONNECTED: 344ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen handleServiceDisconnected((ComponentName) msg.obj); 345ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return true; 346ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen case MSG_RETRY_LISTENER_QUEUE: 347ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen handleRetryListenerQueue((ComponentName) msg.obj); 348ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return true; 349ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 350ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return false; 351ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 352ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 353ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private void handleQueueTask(Task task) { 354ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen updateListenerMap(); 355ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen for (ListenerRecord record : mRecordMap.values()) { 356ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen record.taskQueue.add(task); 357ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen processListenerQueue(record); 358ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 359ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 360ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 361ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private void handleServiceConnected(ComponentName componentName, IBinder iBinder) { 362ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen ListenerRecord record = mRecordMap.get(componentName); 363ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (record != null) { 364ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen record.service = INotificationSideChannel.Stub.asInterface(iBinder); 365ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen record.retryCount = 0; 366ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen processListenerQueue(record); 367ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 368ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 369ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 370ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private void handleServiceDisconnected(ComponentName componentName) { 371ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen ListenerRecord record = mRecordMap.get(componentName); 372ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (record != null) { 373ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen ensureServiceUnbound(record); 374ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 375ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 376ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 377ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private void handleRetryListenerQueue(ComponentName componentName) { 378ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen ListenerRecord record = mRecordMap.get(componentName); 379ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (record != null) { 380ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen processListenerQueue(record); 381ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 382ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 383ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 384ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen @Override 385ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 386ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (Log.isLoggable(TAG, Log.DEBUG)) { 387ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Log.d(TAG, "Connected to service " + componentName); 388ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 389ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mHandler.obtainMessage(MSG_SERVICE_CONNECTED, 390ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen new ServiceConnectedEvent(componentName, iBinder)) 391ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen .sendToTarget(); 392ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 393ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 394ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen @Override 395ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public void onServiceDisconnected(ComponentName componentName) { 396ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (Log.isLoggable(TAG, Log.DEBUG)) { 397ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Log.d(TAG, "Disconnected from service " + componentName); 398ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 399ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mHandler.obtainMessage(MSG_SERVICE_DISCONNECTED, componentName).sendToTarget(); 400ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 401ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 402ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 403ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Check the current list of enabled listener packages and update the records map 404ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * accordingly. 405ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 406ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private void updateListenerMap() { 407ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Set<String> enabledPackages = getEnabledListenerPackages(mContext); 408ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (enabledPackages.equals(mCachedEnabledPackages)) { 409ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen // Short-circuit when the list of enabled packages has not changed. 410ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return; 411ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 412ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mCachedEnabledPackages = enabledPackages; 413ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServices( 414ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen new Intent().setAction(ACTION_BIND_SIDE_CHANNEL), PackageManager.GET_SERVICES); 415ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Set<ComponentName> enabledComponents = new HashSet<ComponentName>(); 416ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen for (ResolveInfo resolveInfo : resolveInfos) { 417ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (!enabledPackages.contains(resolveInfo.serviceInfo.packageName)) { 418ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen continue; 419ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 420ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen ComponentName componentName = new ComponentName( 421ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); 422ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (resolveInfo.serviceInfo.permission != null) { 423ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Log.w(TAG, "Permission present on component " + componentName 424ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen + ", not adding listener record."); 425ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen continue; 426ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 427ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen enabledComponents.add(componentName); 428ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 429ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen // Ensure all enabled components have a record in the listener map. 430ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen for (ComponentName componentName : enabledComponents) { 431ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (!mRecordMap.containsKey(componentName)) { 432ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (Log.isLoggable(TAG, Log.DEBUG)) { 433ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Log.d(TAG, "Adding listener record for " + componentName); 434ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 435ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mRecordMap.put(componentName, new ListenerRecord(componentName)); 436ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 437ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 438ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen // Remove listener records that are no longer for enabled components. 439ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Iterator<Map.Entry<ComponentName, ListenerRecord>> it = 440ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mRecordMap.entrySet().iterator(); 441ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen while (it.hasNext()) { 442ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Map.Entry<ComponentName, ListenerRecord> entry = it.next(); 443ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (!enabledComponents.contains(entry.getKey())) { 444ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (Log.isLoggable(TAG, Log.DEBUG)) { 445ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Log.d(TAG, "Removing listener record for " + entry.getKey()); 446ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 447ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen ensureServiceUnbound(entry.getValue()); 448ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen it.remove(); 449ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 450ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 451ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 452ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 453ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 454ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Ensure we are already attempting to bind to a service, or start a new binding if not. 455ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * @return Whether the service bind attempt was successful. 456ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 457ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private boolean ensureServiceBound(ListenerRecord record) { 458ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (record.bound) { 459ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return true; 460ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 461ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Intent intent = new Intent(ACTION_BIND_SIDE_CHANNEL).setComponent(record.componentName); 462bb8067a02aea52e24708f17933b3689650b2c0abAurimas Liutikas record.bound = mContext.bindService(intent, this, Service.BIND_AUTO_CREATE 463bb8067a02aea52e24708f17933b3689650b2c0abAurimas Liutikas | Service.BIND_WAIVE_PRIORITY); 464ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (record.bound) { 465ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen record.retryCount = 0; 466ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } else { 467ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Log.w(TAG, "Unable to bind to listener " + record.componentName); 468ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mContext.unbindService(this); 469ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 470ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return record.bound; 471ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 472ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 473ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 474ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Ensure we have unbound from a service. 475ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 476ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private void ensureServiceUnbound(ListenerRecord record) { 477ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (record.bound) { 478ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mContext.unbindService(this); 479ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen record.bound = false; 480ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 481ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen record.service = null; 482ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 483ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 484ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 485ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Schedule a delayed retry to communicate with a listener service. 486ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * After a maximum number of attempts (with exponential back-off), start 487ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * dropping pending tasks for this listener. 488ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 489ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private void scheduleListenerRetry(ListenerRecord record) { 490ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (mHandler.hasMessages(MSG_RETRY_LISTENER_QUEUE, record.componentName)) { 491ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return; 492ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 493ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen record.retryCount++; 494ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (record.retryCount > SIDE_CHANNEL_RETRY_MAX_COUNT) { 495ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Log.w(TAG, "Giving up on delivering " + record.taskQueue.size() + " tasks to " 496ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen + record.componentName + " after " + record.retryCount + " retries"); 497ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen record.taskQueue.clear(); 498ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return; 499ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 500ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen int delayMs = SIDE_CHANNEL_RETRY_BASE_INTERVAL_MS * (1 << (record.retryCount - 1)); 501ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (Log.isLoggable(TAG, Log.DEBUG)) { 502ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Log.d(TAG, "Scheduling retry for " + delayMs + " ms"); 503ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 504ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Message msg = mHandler.obtainMessage(MSG_RETRY_LISTENER_QUEUE, record.componentName); 505ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen mHandler.sendMessageDelayed(msg, delayMs); 506ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 507ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 508ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** 509ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Perform a processing step for a listener. First check the bind state, then attempt 510ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * to flush the task queue, and if an error is encountered, schedule a retry. 511ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */ 512ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private void processListenerQueue(ListenerRecord record) { 513ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (Log.isLoggable(TAG, Log.DEBUG)) { 514ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Log.d(TAG, "Processing component " + record.componentName + ", " 515ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen + record.taskQueue.size() + " queued tasks"); 516ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 517ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (record.taskQueue.isEmpty()) { 518ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return; 519ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 520ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (!ensureServiceBound(record) || record.service == null) { 521ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen // Ensure bind has started and that a service interface is ready to use. 522ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen scheduleListenerRetry(record); 523ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return; 524ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 525ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen // Attempt to flush all items in the task queue. 526ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen while (true) { 527ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Task task = record.taskQueue.peek(); 528ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (task == null) { 529ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen break; 530ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 531ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen try { 532ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (Log.isLoggable(TAG, Log.DEBUG)) { 533ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Log.d(TAG, "Sending task " + task); 534ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 535ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen task.send(record.service); 536ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen record.taskQueue.remove(); 537ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } catch (DeadObjectException e) { 538ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (Log.isLoggable(TAG, Log.DEBUG)) { 539ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Log.d(TAG, "Remote service has died: " + record.componentName); 540ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 541ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen break; 542ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } catch (RemoteException e) { 543ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen Log.w(TAG, "RemoteException communicating with " + record.componentName, e); 544ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen break; 545ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 546ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 547ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (!record.taskQueue.isEmpty()) { 548ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen // Some tasks were not sent, meaning an error was encountered, schedule a retry. 549ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen scheduleListenerRetry(record); 550ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 551ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 552ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 553ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** A per-side-channel-service listener state record */ 554ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static class ListenerRecord { 555ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public final ComponentName componentName; 556ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** Whether the service is currently bound to. */ 557ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public boolean bound = false; 558ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** The service stub provided by onServiceConnected */ 559ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public INotificationSideChannel service; 560ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** Queue of pending tasks to send to this listener service */ 561ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public LinkedList<Task> taskQueue = new LinkedList<Task>(); 562ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen /** Number of retries attempted while connecting to this listener service */ 563ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public int retryCount = 0; 564ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 565ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public ListenerRecord(ComponentName componentName) { 566ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.componentName = componentName; 567ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 568ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 569ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 570ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 571ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static class ServiceConnectedEvent { 572ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen final ComponentName componentName; 573ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen final IBinder iBinder; 574ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 575fa79d6fc148a0642f240377b8ce82acfee7bb890Aurimas Liutikas ServiceConnectedEvent(ComponentName componentName, 576ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen final IBinder iBinder) { 577ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.componentName = componentName; 578ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.iBinder = iBinder; 579ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 580ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 581ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 582ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private interface Task { 583fa79d6fc148a0642f240377b8ce82acfee7bb890Aurimas Liutikas void send(INotificationSideChannel service) throws RemoteException; 584ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 585ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 586ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static class NotifyTask implements Task { 587ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen final String packageName; 588ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen final int id; 589ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen final String tag; 590ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen final Notification notif; 591ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 592fa79d6fc148a0642f240377b8ce82acfee7bb890Aurimas Liutikas NotifyTask(String packageName, int id, String tag, Notification notif) { 593ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.packageName = packageName; 594ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.id = id; 595ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.tag = tag; 596ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.notif = notif; 597ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 598ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 599ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen @Override 600ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public void send(INotificationSideChannel service) throws RemoteException { 601ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen service.notify(packageName, id, tag, notif); 602ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 603ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 604fa79d6fc148a0642f240377b8ce82acfee7bb890Aurimas Liutikas @Override 605ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public String toString() { 606ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen StringBuilder sb = new StringBuilder("NotifyTask["); 607ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen sb.append("packageName:").append(packageName); 608ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen sb.append(", id:").append(id); 609ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen sb.append(", tag:").append(tag); 610ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen sb.append("]"); 611ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return sb.toString(); 612ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 613ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 614ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 615ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen private static class CancelTask implements Task { 616ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen final String packageName; 617ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen final int id; 618ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen final String tag; 619ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen final boolean all; 620ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 621fa79d6fc148a0642f240377b8ce82acfee7bb890Aurimas Liutikas CancelTask(String packageName) { 622ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.packageName = packageName; 623ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.id = 0; 624ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.tag = null; 625ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.all = true; 626ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 627ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 628fa79d6fc148a0642f240377b8ce82acfee7bb890Aurimas Liutikas CancelTask(String packageName, int id, String tag) { 629ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.packageName = packageName; 630ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.id = id; 631ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.tag = tag; 632ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen this.all = false; 633ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 634ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 635ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen @Override 636ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public void send(INotificationSideChannel service) throws RemoteException { 637ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen if (all) { 638ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen service.cancelAll(packageName); 639ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } else { 640ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen service.cancel(packageName, id, tag); 641ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 642ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 643ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen 644fa79d6fc148a0642f240377b8ce82acfee7bb890Aurimas Liutikas @Override 645ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen public String toString() { 646ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen StringBuilder sb = new StringBuilder("CancelTask["); 647ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen sb.append("packageName:").append(packageName); 648ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen sb.append(", id:").append(id); 649ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen sb.append(", tag:").append(tag); 650ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen sb.append(", all:").append(all); 651ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen sb.append("]"); 652ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen return sb.toString(); 653ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 654ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen } 655ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen} 656