NotificationListenerService.java revision f953664dc17dca23bd724bd64f89189c16c83263
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.service.notification; 18 19import android.annotation.SdkConstant; 20import android.app.INotificationManager; 21import android.app.Service; 22import android.content.Context; 23import android.content.Intent; 24import android.os.IBinder; 25import android.os.RemoteException; 26import android.os.ServiceManager; 27import android.util.Log; 28 29import java.util.Comparator; 30import java.util.HashMap; 31 32/** 33 * A service that receives calls from the system when new notifications are posted or removed. 34 * <p>To extend this class, you must declare the service in your manifest file with 35 * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission 36 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p> 37 * <pre> 38 * <service android:name=".NotificationListener" 39 * android:label="@string/service_name" 40 * android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> 41 * <intent-filter> 42 * <action android:name="android.service.notification.NotificationListenerService" /> 43 * </intent-filter> 44 * </service></pre> 45 */ 46public abstract class NotificationListenerService extends Service { 47 // TAG = "NotificationListenerService[MySubclass]" 48 private final String TAG = NotificationListenerService.class.getSimpleName() 49 + "[" + getClass().getSimpleName() + "]"; 50 51 private INotificationListenerWrapper mWrapper = null; 52 private String[] mNotificationKeys; 53 54 private INotificationManager mNoMan; 55 56 /** 57 * The {@link Intent} that must be declared as handled by the service. 58 */ 59 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 60 public static final String SERVICE_INTERFACE 61 = "android.service.notification.NotificationListenerService"; 62 63 /** 64 * Implement this method to learn about new notifications as they are posted by apps. 65 * 66 * @param sbn A data structure encapsulating the original {@link android.app.Notification} 67 * object as well as its identifying information (tag and id) and source 68 * (package name). 69 */ 70 public abstract void onNotificationPosted(StatusBarNotification sbn); 71 72 /** 73 * Implement this method to learn when notifications are removed. 74 * <P> 75 * This might occur because the user has dismissed the notification using system UI (or another 76 * notification listener) or because the app has withdrawn the notification. 77 * <P> 78 * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the 79 * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight 80 * fields such as {@link android.app.Notification#contentView} and 81 * {@link android.app.Notification#largeIcon}. However, all other fields on 82 * {@link StatusBarNotification}, sufficient to match this call with a prior call to 83 * {@link #onNotificationPosted(StatusBarNotification)}, will be intact. 84 * 85 * @param sbn A data structure encapsulating at least the original information (tag and id) 86 * and source (package name) used to post the {@link android.app.Notification} that 87 * was just removed. 88 */ 89 public abstract void onNotificationRemoved(StatusBarNotification sbn); 90 91 /** 92 * Implement this method to learn about when the listener is enabled and connected to 93 * the notification manager. You are safe to call {@link #getActiveNotifications(String[]) 94 * at this time. 95 * 96 * @param notificationKeys The notification keys for all currently posted notifications. 97 */ 98 public void onListenerConnected(String[] notificationKeys) { 99 // optional 100 } 101 102 /** 103 * Implement this method to be notified when the notification order cahnges. 104 * 105 * Call {@link #getOrderedNotificationKeys()} to retrieve the new order. 106 */ 107 public void onNotificationOrderUpdate() { 108 // optional 109 } 110 111 private final INotificationManager getNotificationInterface() { 112 if (mNoMan == null) { 113 mNoMan = INotificationManager.Stub.asInterface( 114 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 115 } 116 return mNoMan; 117 } 118 119 /** 120 * Inform the notification manager about dismissal of a single notification. 121 * <p> 122 * Use this if your listener has a user interface that allows the user to dismiss individual 123 * notifications, similar to the behavior of Android's status bar and notification panel. 124 * It should be called after the user dismisses a single notification using your UI; 125 * upon being informed, the notification manager will actually remove the notification 126 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. 127 * <P> 128 * <b>Note:</b> If your listener allows the user to fire a notification's 129 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call 130 * this method at that time <i>if</i> the Notification in question has the 131 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set. 132 * 133 * @param pkg Package of the notifying app. 134 * @param tag Tag of the notification as specified by the notifying app in 135 * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}. 136 * @param id ID of the notification as specified by the notifying app in 137 * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}. 138 * <p> 139 * @deprecated Use {@link #cancelNotification(String key)} 140 * instead. Beginning with {@link android.os.Build.VERSION_CODES#L} this method will no longer 141 * cancel the notification. It will continue to cancel the notification for applications 142 * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#L}. 143 */ 144 public final void cancelNotification(String pkg, String tag, int id) { 145 if (!isBound()) return; 146 try { 147 getNotificationInterface().cancelNotificationFromListener( 148 mWrapper, pkg, tag, id); 149 } catch (android.os.RemoteException ex) { 150 Log.v(TAG, "Unable to contact notification manager", ex); 151 } 152 } 153 154 /** 155 * Inform the notification manager about dismissal of a single notification. 156 * <p> 157 * Use this if your listener has a user interface that allows the user to dismiss individual 158 * notifications, similar to the behavior of Android's status bar and notification panel. 159 * It should be called after the user dismisses a single notification using your UI; 160 * upon being informed, the notification manager will actually remove the notification 161 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. 162 * <P> 163 * <b>Note:</b> If your listener allows the user to fire a notification's 164 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call 165 * this method at that time <i>if</i> the Notification in question has the 166 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set. 167 * <p> 168 * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}. 169 */ 170 public final void cancelNotification(String key) { 171 if (!isBound()) return; 172 try { 173 getNotificationInterface().cancelNotificationsFromListener(mWrapper, 174 new String[] {key}); 175 } catch (android.os.RemoteException ex) { 176 Log.v(TAG, "Unable to contact notification manager", ex); 177 } 178 } 179 180 /** 181 * Inform the notification manager about dismissal of all notifications. 182 * <p> 183 * Use this if your listener has a user interface that allows the user to dismiss all 184 * notifications, similar to the behavior of Android's status bar and notification panel. 185 * It should be called after the user invokes the "dismiss all" function of your UI; 186 * upon being informed, the notification manager will actually remove all active notifications 187 * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks. 188 * 189 * {@see #cancelNotification(String, String, int)} 190 */ 191 public final void cancelAllNotifications() { 192 cancelNotifications(null /*all*/); 193 } 194 195 /** 196 * Inform the notification manager about dismissal of specific notifications. 197 * <p> 198 * Use this if your listener has a user interface that allows the user to dismiss 199 * multiple notifications at once. 200 * 201 * @param keys Notifications to dismiss, or {@code null} to dismiss all. 202 * 203 * {@see #cancelNotification(String, String, int)} 204 */ 205 public final void cancelNotifications(String[] keys) { 206 if (!isBound()) return; 207 try { 208 getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys); 209 } catch (android.os.RemoteException ex) { 210 Log.v(TAG, "Unable to contact notification manager", ex); 211 } 212 } 213 214 /** 215 * Request the list of outstanding notifications (that is, those that are visible to the 216 * current user). Useful when you don't know what's already been posted. 217 * 218 * @return An array of active notifications, sorted in natural order. 219 */ 220 public StatusBarNotification[] getActiveNotifications() { 221 return getActiveNotifications(null /*all*/); 222 } 223 224 /** 225 * Request the list of outstanding notifications (that is, those that are visible to the 226 * current user). Useful when you don't know what's already been posted. 227 * 228 * @param keys A specific list of notification keys, or {@code null} for all. 229 * @return An array of active notifications, sorted in natural order 230 * if {@code keys} is {@code null}. 231 */ 232 public StatusBarNotification[] getActiveNotifications(String[] keys) { 233 if (!isBound()) return null; 234 try { 235 return getNotificationInterface().getActiveNotificationsFromListener(mWrapper, keys); 236 } catch (android.os.RemoteException ex) { 237 Log.v(TAG, "Unable to contact notification manager", ex); 238 } 239 return null; 240 } 241 242 /** 243 * Request the list of notification keys in their current natural order. 244 * You can use the notification keys for subsequent retrieval via 245 * {@link #getActiveNotifications(String[]) or dismissal via 246 * {@link #cancelNotifications(String[]). 247 * 248 * @return An array of active notification keys, in their natural order. 249 */ 250 public String[] getOrderedNotificationKeys() { 251 return mNotificationKeys; 252 } 253 254 @Override 255 public IBinder onBind(Intent intent) { 256 if (mWrapper == null) { 257 mWrapper = new INotificationListenerWrapper(); 258 } 259 return mWrapper; 260 } 261 262 private boolean isBound() { 263 if (mWrapper == null) { 264 Log.w(TAG, "Notification listener service not yet bound."); 265 return false; 266 } 267 return true; 268 } 269 270 private class INotificationListenerWrapper extends INotificationListener.Stub { 271 @Override 272 public void onNotificationPosted(StatusBarNotification sbn, 273 NotificationOrderUpdate update) { 274 try { 275 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 276 synchronized (mWrapper) { 277 updateNotificationKeys(update); 278 NotificationListenerService.this.onNotificationPosted(sbn); 279 } 280 } catch (Throwable t) { 281 Log.w(TAG, "Error running onOrderedNotificationPosted", t); 282 } 283 } 284 @Override 285 public void onNotificationRemoved(StatusBarNotification sbn, 286 NotificationOrderUpdate update) { 287 try { 288 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 289 synchronized (mWrapper) { 290 updateNotificationKeys(update); 291 NotificationListenerService.this.onNotificationRemoved(sbn); 292 } 293 } catch (Throwable t) { 294 Log.w(TAG, "Error running onNotificationRemoved", t); 295 } 296 } 297 @Override 298 public void onListenerConnected(NotificationOrderUpdate update) { 299 try { 300 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 301 synchronized (mWrapper) { 302 updateNotificationKeys(update); 303 NotificationListenerService.this.onListenerConnected(mNotificationKeys); 304 } 305 } catch (Throwable t) { 306 Log.w(TAG, "Error running onListenerConnected", t); 307 } 308 } 309 @Override 310 public void onNotificationOrderUpdate(NotificationOrderUpdate update) 311 throws RemoteException { 312 try { 313 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 314 synchronized (mWrapper) { 315 updateNotificationKeys(update); 316 NotificationListenerService.this.onNotificationOrderUpdate(); 317 } 318 } catch (Throwable t) { 319 Log.w(TAG, "Error running onNotificationOrderUpdate", t); 320 } 321 } 322 } 323 324 private void updateNotificationKeys(NotificationOrderUpdate update) { 325 // TODO: avoid garbage by comparing the lists 326 mNotificationKeys = update.getOrderedKeys(); 327 } 328} 329