NotificationListenerService.java revision 05ad48206a082057e17723d32493c153faa6881e
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.Parcel; 28import android.os.Parcelable; 29import android.os.RemoteException; 30import android.os.ServiceManager; 31import android.util.Log; 32 33/** 34 * A service that receives calls from the system when new notifications are 35 * posted or removed, or their ranking changed. 36 * <p>To extend this class, you must declare the service in your manifest file with 37 * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission 38 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p> 39 * <pre> 40 * <service android:name=".NotificationListener" 41 * android:label="@string/service_name" 42 * android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> 43 * <intent-filter> 44 * <action android:name="android.service.notification.NotificationListenerService" /> 45 * </intent-filter> 46 * </service></pre> 47 */ 48public abstract class NotificationListenerService extends Service { 49 // TAG = "NotificationListenerService[MySubclass]" 50 private final String TAG = NotificationListenerService.class.getSimpleName() 51 + "[" + getClass().getSimpleName() + "]"; 52 53 private INotificationListenerWrapper mWrapper = null; 54 private Ranking mRanking; 55 56 private INotificationManager mNoMan; 57 58 /** Only valid after a successful call to (@link registerAsService}. */ 59 private int mCurrentUser; 60 61 /** 62 * The {@link Intent} that must be declared as handled by the service. 63 */ 64 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 65 public static final String SERVICE_INTERFACE 66 = "android.service.notification.NotificationListenerService"; 67 68 /** 69 * Implement this method to learn about new notifications as they are posted by apps. 70 * 71 * @param sbn A data structure encapsulating the original {@link android.app.Notification} 72 * object as well as its identifying information (tag and id) and source 73 * (package name). 74 */ 75 public abstract void onNotificationPosted(StatusBarNotification sbn); 76 77 /** 78 * Implement this method to learn when notifications are removed. 79 * <P> 80 * This might occur because the user has dismissed the notification using system UI (or another 81 * notification listener) or because the app has withdrawn the notification. 82 * <P> 83 * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the 84 * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight 85 * fields such as {@link android.app.Notification#contentView} and 86 * {@link android.app.Notification#largeIcon}. However, all other fields on 87 * {@link StatusBarNotification}, sufficient to match this call with a prior call to 88 * {@link #onNotificationPosted(StatusBarNotification)}, will be intact. 89 * 90 * @param sbn A data structure encapsulating at least the original information (tag and id) 91 * and source (package name) used to post the {@link android.app.Notification} that 92 * was just removed. 93 */ 94 public abstract void onNotificationRemoved(StatusBarNotification sbn); 95 96 /** 97 * Implement this method to learn about when the listener is enabled and connected to 98 * the notification manager. You are safe to call {@link #getActiveNotifications(String[]) 99 * at this time. 100 * 101 * @param notificationKeys The notification keys for all currently posted notifications. 102 */ 103 public void onListenerConnected(String[] notificationKeys) { 104 // optional 105 } 106 107 /** 108 * Implement this method to be notified when the notification ranking changes. 109 * <P> 110 * Call {@link #getCurrentRanking()} to retrieve the new ranking. 111 */ 112 public void onNotificationRankingUpdate() { 113 // optional 114 } 115 116 private final INotificationManager getNotificationInterface() { 117 if (mNoMan == null) { 118 mNoMan = INotificationManager.Stub.asInterface( 119 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 120 } 121 return mNoMan; 122 } 123 124 /** 125 * Inform the notification manager about dismissal of a single notification. 126 * <p> 127 * Use this if your listener has a user interface that allows the user to dismiss individual 128 * notifications, similar to the behavior of Android's status bar and notification panel. 129 * It should be called after the user dismisses a single notification using your UI; 130 * upon being informed, the notification manager will actually remove the notification 131 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. 132 * <P> 133 * <b>Note:</b> If your listener allows the user to fire a notification's 134 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call 135 * this method at that time <i>if</i> the Notification in question has the 136 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set. 137 * 138 * @param pkg Package of the notifying app. 139 * @param tag Tag of the notification as specified by the notifying app in 140 * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}. 141 * @param id ID of the notification as specified by the notifying app in 142 * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}. 143 * <p> 144 * @deprecated Use {@link #cancelNotification(String key)} 145 * instead. Beginning with {@link android.os.Build.VERSION_CODES#L} this method will no longer 146 * cancel the notification. It will continue to cancel the notification for applications 147 * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#L}. 148 */ 149 public final void cancelNotification(String pkg, String tag, int id) { 150 if (!isBound()) return; 151 try { 152 getNotificationInterface().cancelNotificationFromListener( 153 mWrapper, pkg, tag, id); 154 } catch (android.os.RemoteException ex) { 155 Log.v(TAG, "Unable to contact notification manager", ex); 156 } 157 } 158 159 /** 160 * Inform the notification manager about dismissal of a single notification. 161 * <p> 162 * Use this if your listener has a user interface that allows the user to dismiss individual 163 * notifications, similar to the behavior of Android's status bar and notification panel. 164 * It should be called after the user dismisses a single notification using your UI; 165 * upon being informed, the notification manager will actually remove the notification 166 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. 167 * <P> 168 * <b>Note:</b> If your listener allows the user to fire a notification's 169 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call 170 * this method at that time <i>if</i> the Notification in question has the 171 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set. 172 * <p> 173 * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}. 174 */ 175 public final void cancelNotification(String key) { 176 if (!isBound()) return; 177 try { 178 getNotificationInterface().cancelNotificationsFromListener(mWrapper, 179 new String[] {key}); 180 } catch (android.os.RemoteException ex) { 181 Log.v(TAG, "Unable to contact notification manager", ex); 182 } 183 } 184 185 /** 186 * Inform the notification manager about dismissal of all notifications. 187 * <p> 188 * Use this if your listener has a user interface that allows the user to dismiss all 189 * notifications, similar to the behavior of Android's status bar and notification panel. 190 * It should be called after the user invokes the "dismiss all" function of your UI; 191 * upon being informed, the notification manager will actually remove all active notifications 192 * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks. 193 * 194 * {@see #cancelNotification(String, String, int)} 195 */ 196 public final void cancelAllNotifications() { 197 cancelNotifications(null /*all*/); 198 } 199 200 /** 201 * Inform the notification manager about dismissal of specific notifications. 202 * <p> 203 * Use this if your listener has a user interface that allows the user to dismiss 204 * multiple notifications at once. 205 * 206 * @param keys Notifications to dismiss, or {@code null} to dismiss all. 207 * 208 * {@see #cancelNotification(String, String, int)} 209 */ 210 public final void cancelNotifications(String[] keys) { 211 if (!isBound()) return; 212 try { 213 getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys); 214 } catch (android.os.RemoteException ex) { 215 Log.v(TAG, "Unable to contact notification manager", ex); 216 } 217 } 218 219 /** 220 * Request the list of outstanding notifications (that is, those that are visible to the 221 * current user). Useful when you don't know what's already been posted. 222 * 223 * @return An array of active notifications, sorted in natural order. 224 */ 225 public StatusBarNotification[] getActiveNotifications() { 226 return getActiveNotifications(null /*all*/); 227 } 228 229 /** 230 * Request the list of notification keys in their current ranking order. 231 * <p> 232 * You can use the notification keys for subsequent retrieval via 233 * {@link #getActiveNotifications(String[]) or dismissal via 234 * {@link #cancelNotifications(String[]). 235 * 236 * @return An array of active notification keys, in their ranking order. 237 */ 238 public String[] getActiveNotificationKeys() { 239 return mRanking.getOrderedKeys(); 240 } 241 242 /** 243 * Request the list of outstanding notifications (that is, those that are visible to the 244 * current user). Useful when you don't know what's already been posted. 245 * 246 * @param keys A specific list of notification keys, or {@code null} for all. 247 * @return An array of active notifications, sorted in natural order 248 * if {@code keys} is {@code null}. 249 */ 250 public StatusBarNotification[] getActiveNotifications(String[] keys) { 251 if (!isBound()) return null; 252 try { 253 return getNotificationInterface().getActiveNotificationsFromListener(mWrapper, keys); 254 } catch (android.os.RemoteException ex) { 255 Log.v(TAG, "Unable to contact notification manager", ex); 256 } 257 return null; 258 } 259 260 /** 261 * Returns current ranking information. 262 * 263 * <p> 264 * The returned object represents the current ranking snapshot and only 265 * applies for currently active notifications. Hence you must retrieve a 266 * new Ranking after each notification event such as 267 * {@link #onNotificationPosted(StatusBarNotification)}, 268 * {@link #onNotificationRemoved(StatusBarNotification)}, etc. 269 * 270 * @return A {@link NotificationListenerService.Ranking} object providing 271 * access to ranking information 272 */ 273 public Ranking getCurrentRanking() { 274 return mRanking; 275 } 276 277 @Override 278 public IBinder onBind(Intent intent) { 279 if (mWrapper == null) { 280 mWrapper = new INotificationListenerWrapper(); 281 } 282 return mWrapper; 283 } 284 285 private boolean isBound() { 286 if (mWrapper == null) { 287 Log.w(TAG, "Notification listener service not yet bound."); 288 return false; 289 } 290 return true; 291 } 292 293 /** 294 * Directly register this service with the Notification Manager. 295 * 296 * <p>Only system services may use this call. It will fail for non-system callers. 297 * Apps should ask the user to add their listener in Settings. 298 * 299 * @param componentName the component that will consume the notification information 300 * @param currentUser the user to use as the stream filter 301 * @hide 302 */ 303 @PrivateApi 304 public void registerAsSystemService(ComponentName componentName, int currentUser) 305 throws RemoteException { 306 if (mWrapper == null) { 307 mWrapper = new INotificationListenerWrapper(); 308 } 309 INotificationManager noMan = getNotificationInterface(); 310 noMan.registerListener(mWrapper, componentName, currentUser); 311 mCurrentUser = currentUser; 312 } 313 314 /** 315 * Directly unregister this service from the Notification Manager. 316 * 317 * <P>This method will fail for listeners that were not registered 318 * with (@link registerAsService). 319 * @hide 320 */ 321 @PrivateApi 322 public void unregisterAsSystemService() throws RemoteException { 323 if (mWrapper != null) { 324 INotificationManager noMan = getNotificationInterface(); 325 noMan.unregisterListener(mWrapper, mCurrentUser); 326 } 327 } 328 329 private class INotificationListenerWrapper extends INotificationListener.Stub { 330 @Override 331 public void onNotificationPosted(StatusBarNotification sbn, 332 NotificationRankingUpdate update) { 333 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 334 synchronized (mWrapper) { 335 applyUpdate(update); 336 try { 337 NotificationListenerService.this.onNotificationPosted(sbn); 338 } catch (Throwable t) { 339 Log.w(TAG, "Error running onNotificationPosted", t); 340 } 341 } 342 } 343 @Override 344 public void onNotificationRemoved(StatusBarNotification sbn, 345 NotificationRankingUpdate update) { 346 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 347 synchronized (mWrapper) { 348 applyUpdate(update); 349 try { 350 NotificationListenerService.this.onNotificationRemoved(sbn); 351 } catch (Throwable t) { 352 Log.w(TAG, "Error running onNotificationRemoved", t); 353 } 354 } 355 } 356 @Override 357 public void onListenerConnected(NotificationRankingUpdate update) { 358 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 359 synchronized (mWrapper) { 360 applyUpdate(update); 361 try { 362 NotificationListenerService.this.onListenerConnected( 363 mRanking.getOrderedKeys()); 364 } catch (Throwable t) { 365 Log.w(TAG, "Error running onListenerConnected", t); 366 } 367 } 368 } 369 @Override 370 public void onNotificationRankingUpdate(NotificationRankingUpdate update) 371 throws RemoteException { 372 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 373 synchronized (mWrapper) { 374 applyUpdate(update); 375 try { 376 NotificationListenerService.this.onNotificationRankingUpdate(); 377 } catch (Throwable t) { 378 Log.w(TAG, "Error running onNotificationRankingUpdate", t); 379 } 380 } 381 } 382 } 383 384 private void applyUpdate(NotificationRankingUpdate update) { 385 mRanking = new Ranking(update); 386 } 387 388 /** 389 * Provides access to ranking information on currently active 390 * notifications. 391 * 392 * <p> 393 * Note that this object represents a ranking snapshot that only applies to 394 * notifications active at the time of retrieval. 395 */ 396 public static class Ranking implements Parcelable { 397 private final NotificationRankingUpdate mRankingUpdate; 398 399 private Ranking(NotificationRankingUpdate rankingUpdate) { 400 mRankingUpdate = rankingUpdate; 401 } 402 403 /** 404 * Request the list of notification keys in their current ranking 405 * order. 406 * 407 * @return An array of active notification keys, in their ranking order. 408 */ 409 public String[] getOrderedKeys() { 410 return mRankingUpdate.getOrderedKeys(); 411 } 412 413 /** 414 * Returns the rank of the notification with the given key, that is the 415 * index of <code>key</code> in the array of keys returned by 416 * {@link #getOrderedKeys()}. 417 * 418 * @return The rank of the notification with the given key; -1 when the 419 * given key is unknown. 420 */ 421 public int getIndexOfKey(String key) { 422 // TODO: Optimize. 423 String[] orderedKeys = mRankingUpdate.getOrderedKeys(); 424 for (int i = 0; i < orderedKeys.length; i++) { 425 if (orderedKeys[i].equals(key)) { 426 return i; 427 } 428 } 429 return -1; 430 } 431 432 /** 433 * Returns whether the notification with the given key was intercepted 434 * by "Do not disturb". 435 */ 436 public boolean isInterceptedByDoNotDisturb(String key) { 437 // TODO: Optimize. 438 for (String interceptedKey : mRankingUpdate.getDndInterceptedKeys()) { 439 if (interceptedKey.equals(key)) { 440 return true; 441 } 442 } 443 return false; 444 } 445 446 /** 447 * Returns whether the notification with the given key is an ambient 448 * notification, that is a notification that doesn't require the user's 449 * immediate attention. 450 */ 451 public boolean isAmbient(String key) { 452 // TODO: Optimize. 453 int firstAmbientIndex = mRankingUpdate.getFirstAmbientIndex(); 454 if (firstAmbientIndex < 0) { 455 return false; 456 } 457 String[] orderedKeys = mRankingUpdate.getOrderedKeys(); 458 for (int i = firstAmbientIndex; i < orderedKeys.length; i++) { 459 if (orderedKeys[i].equals(key)) { 460 return true; 461 } 462 } 463 return false; 464 } 465 466 // ----------- Parcelable 467 468 @Override 469 public int describeContents() { 470 return 0; 471 } 472 473 @Override 474 public void writeToParcel(Parcel dest, int flags) { 475 dest.writeParcelable(mRankingUpdate, flags); 476 } 477 478 public static final Creator<Ranking> CREATOR = new Creator<Ranking>() { 479 @Override 480 public Ranking createFromParcel(Parcel source) { 481 NotificationRankingUpdate rankingUpdate = source.readParcelable(null); 482 return new Ranking(rankingUpdate); 483 } 484 485 @Override 486 public Ranking[] newArray(int size) { 487 return new Ranking[size]; 488 } 489 }; 490 } 491} 492