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