NotificationListenerService.java revision 3ad4e3a45bbe44129b14c4d391431e44f1e04f0c
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.SystemApi; 20import android.annotation.SdkConstant; 21import android.app.INotificationManager; 22import android.app.Notification; 23import android.app.Notification.Builder; 24import android.app.Service; 25import android.content.ComponentName; 26import android.content.Context; 27import android.content.Intent; 28import android.content.pm.ParceledListSlice; 29import android.os.Bundle; 30import android.os.IBinder; 31import android.os.Parcel; 32import android.os.Parcelable; 33import android.os.RemoteException; 34import android.os.ServiceManager; 35import android.util.ArrayMap; 36import android.util.ArraySet; 37import android.util.Log; 38 39import java.util.Collections; 40import java.util.List; 41 42/** 43 * A service that receives calls from the system when new notifications are 44 * posted or removed, or their ranking changed. 45 * <p>To extend this class, you must declare the service in your manifest file with 46 * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission 47 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p> 48 * <pre> 49 * <service android:name=".NotificationListener" 50 * android:label="@string/service_name" 51 * android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> 52 * <intent-filter> 53 * <action android:name="android.service.notification.NotificationListenerService" /> 54 * </intent-filter> 55 * </service></pre> 56 */ 57public abstract class NotificationListenerService extends Service { 58 // TAG = "NotificationListenerService[MySubclass]" 59 private final String TAG = NotificationListenerService.class.getSimpleName() 60 + "[" + getClass().getSimpleName() + "]"; 61 62 /** 63 * {@link #getCurrentInterruptionFilter() Interruption filter} constant - 64 * Normal interruption filter. 65 */ 66 public static final int INTERRUPTION_FILTER_ALL = 1; 67 68 /** 69 * {@link #getCurrentInterruptionFilter() Interruption filter} constant - 70 * Priority interruption filter. 71 */ 72 public static final int INTERRUPTION_FILTER_PRIORITY = 2; 73 74 /** 75 * {@link #getCurrentInterruptionFilter() Interruption filter} constant - 76 * No interruptions filter. 77 */ 78 public static final int INTERRUPTION_FILTER_NONE = 3; 79 80 /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI 81 * should disable notification sound, vibrating and other visual or aural effects. 82 * This does not change the interruption filter, only the effects. **/ 83 public static final int HINT_HOST_DISABLE_EFFECTS = 1; 84 85 /** 86 * The full trim of the StatusBarNotification including all its features. 87 * 88 * @hide 89 */ 90 @SystemApi 91 public static final int TRIM_FULL = 0; 92 93 /** 94 * A light trim of the StatusBarNotification excluding the following features: 95 * 96 * <ol> 97 * <li>{@link Notification#tickerView tickerView}</li> 98 * <li>{@link Notification#contentView contentView}</li> 99 * <li>{@link Notification#largeIcon largeIcon}</li> 100 * <li>{@link Notification#bigContentView bigContentView}</li> 101 * <li>{@link Notification#headsUpContentView headsUpContentView}</li> 102 * <li>{@link Notification#EXTRA_LARGE_ICON extras[EXTRA_LARGE_ICON]}</li> 103 * <li>{@link Notification#EXTRA_LARGE_ICON_BIG extras[EXTRA_LARGE_ICON_BIG]}</li> 104 * <li>{@link Notification#EXTRA_PICTURE extras[EXTRA_PICTURE]}</li> 105 * <li>{@link Notification#EXTRA_BIG_TEXT extras[EXTRA_BIG_TEXT]}</li> 106 * </ol> 107 * 108 * @hide 109 */ 110 @SystemApi 111 public static final int TRIM_LIGHT = 1; 112 113 private INotificationListenerWrapper mWrapper = null; 114 private RankingMap mRankingMap; 115 116 private INotificationManager mNoMan; 117 118 /** Only valid after a successful call to (@link registerAsService}. */ 119 private int mCurrentUser; 120 121 122 // This context is required for system services since NotificationListenerService isn't 123 // started as a real Service and hence no context is available. 124 private Context mSystemContext; 125 126 /** 127 * The {@link Intent} that must be declared as handled by the service. 128 */ 129 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 130 public static final String SERVICE_INTERFACE 131 = "android.service.notification.NotificationListenerService"; 132 133 /** 134 * Implement this method to learn about new notifications as they are posted by apps. 135 * 136 * @param sbn A data structure encapsulating the original {@link android.app.Notification} 137 * object as well as its identifying information (tag and id) and source 138 * (package name). 139 */ 140 public void onNotificationPosted(StatusBarNotification sbn) { 141 // optional 142 } 143 144 /** 145 * Implement this method to learn about new notifications as they are posted by apps. 146 * 147 * @param sbn A data structure encapsulating the original {@link android.app.Notification} 148 * object as well as its identifying information (tag and id) and source 149 * (package name). 150 * @param rankingMap The current ranking map that can be used to retrieve ranking information 151 * for active notifications, including the newly posted one. 152 */ 153 public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { 154 onNotificationPosted(sbn); 155 } 156 157 /** 158 * Implement this method to learn when notifications are removed. 159 * <P> 160 * This might occur because the user has dismissed the notification using system UI (or another 161 * notification listener) or because the app has withdrawn the notification. 162 * <P> 163 * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the 164 * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight 165 * fields such as {@link android.app.Notification#contentView} and 166 * {@link android.app.Notification#largeIcon}. However, all other fields on 167 * {@link StatusBarNotification}, sufficient to match this call with a prior call to 168 * {@link #onNotificationPosted(StatusBarNotification)}, will be intact. 169 * 170 * @param sbn A data structure encapsulating at least the original information (tag and id) 171 * and source (package name) used to post the {@link android.app.Notification} that 172 * was just removed. 173 */ 174 public void onNotificationRemoved(StatusBarNotification sbn) { 175 // optional 176 } 177 178 /** 179 * Implement this method to learn when notifications are removed. 180 * <P> 181 * This might occur because the user has dismissed the notification using system UI (or another 182 * notification listener) or because the app has withdrawn the notification. 183 * <P> 184 * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the 185 * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight 186 * fields such as {@link android.app.Notification#contentView} and 187 * {@link android.app.Notification#largeIcon}. However, all other fields on 188 * {@link StatusBarNotification}, sufficient to match this call with a prior call to 189 * {@link #onNotificationPosted(StatusBarNotification)}, will be intact. 190 * 191 * @param sbn A data structure encapsulating at least the original information (tag and id) 192 * and source (package name) used to post the {@link android.app.Notification} that 193 * was just removed. 194 * @param rankingMap The current ranking map that can be used to retrieve ranking information 195 * for active notifications. 196 * 197 */ 198 public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) { 199 onNotificationRemoved(sbn); 200 } 201 202 /** 203 * Implement this method to learn about when the listener is enabled and connected to 204 * the notification manager. You are safe to call {@link #getActiveNotifications()} 205 * at this time. 206 */ 207 public void onListenerConnected() { 208 // optional 209 } 210 211 /** 212 * Implement this method to be notified when the notification ranking changes. 213 * 214 * @param rankingMap The current ranking map that can be used to retrieve ranking information 215 * for active notifications. 216 */ 217 public void onNotificationRankingUpdate(RankingMap rankingMap) { 218 // optional 219 } 220 221 /** 222 * Implement this method to be notified when the 223 * {@link #getCurrentListenerHints() Listener hints} change. 224 * 225 * @param hints The current {@link #getCurrentListenerHints() listener hints}. 226 */ 227 public void onListenerHintsChanged(int hints) { 228 // optional 229 } 230 231 /** 232 * Implement this method to be notified when the 233 * {@link #getCurrentInterruptionFilter() interruption filter} changed. 234 * 235 * @param interruptionFilter The current 236 * {@link #getCurrentInterruptionFilter() interruption filter}. 237 */ 238 public void onInterruptionFilterChanged(int interruptionFilter) { 239 // optional 240 } 241 242 private final INotificationManager getNotificationInterface() { 243 if (mNoMan == null) { 244 mNoMan = INotificationManager.Stub.asInterface( 245 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 246 } 247 return mNoMan; 248 } 249 250 /** 251 * Inform the notification manager about dismissal of a single notification. 252 * <p> 253 * Use this if your listener has a user interface that allows the user to dismiss individual 254 * notifications, similar to the behavior of Android's status bar and notification panel. 255 * It should be called after the user dismisses a single notification using your UI; 256 * upon being informed, the notification manager will actually remove the notification 257 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. 258 * <P> 259 * <b>Note:</b> If your listener allows the user to fire a notification's 260 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call 261 * this method at that time <i>if</i> the Notification in question has the 262 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set. 263 * 264 * @param pkg Package of the notifying app. 265 * @param tag Tag of the notification as specified by the notifying app in 266 * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}. 267 * @param id ID of the notification as specified by the notifying app in 268 * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}. 269 * <p> 270 * @deprecated Use {@link #cancelNotification(String key)} 271 * instead. Beginning with {@link android.os.Build.VERSION_CODES#L} this method will no longer 272 * cancel the notification. It will continue to cancel the notification for applications 273 * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#L}. 274 */ 275 public final void cancelNotification(String pkg, String tag, int id) { 276 if (!isBound()) return; 277 try { 278 getNotificationInterface().cancelNotificationFromListener( 279 mWrapper, pkg, tag, id); 280 } catch (android.os.RemoteException ex) { 281 Log.v(TAG, "Unable to contact notification manager", ex); 282 } 283 } 284 285 /** 286 * Inform the notification manager about dismissal of a single notification. 287 * <p> 288 * Use this if your listener has a user interface that allows the user to dismiss individual 289 * notifications, similar to the behavior of Android's status bar and notification panel. 290 * It should be called after the user dismisses a single notification using your UI; 291 * upon being informed, the notification manager will actually remove the notification 292 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. 293 * <P> 294 * <b>Note:</b> If your listener allows the user to fire a notification's 295 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call 296 * this method at that time <i>if</i> the Notification in question has the 297 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set. 298 * <p> 299 * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}. 300 */ 301 public final void cancelNotification(String key) { 302 if (!isBound()) return; 303 try { 304 getNotificationInterface().cancelNotificationsFromListener(mWrapper, 305 new String[] {key}); 306 } catch (android.os.RemoteException ex) { 307 Log.v(TAG, "Unable to contact notification manager", ex); 308 } 309 } 310 311 /** 312 * Inform the notification manager about dismissal of all notifications. 313 * <p> 314 * Use this if your listener has a user interface that allows the user to dismiss all 315 * notifications, similar to the behavior of Android's status bar and notification panel. 316 * It should be called after the user invokes the "dismiss all" function of your UI; 317 * upon being informed, the notification manager will actually remove all active notifications 318 * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks. 319 * 320 * {@see #cancelNotification(String, String, int)} 321 */ 322 public final void cancelAllNotifications() { 323 cancelNotifications(null /*all*/); 324 } 325 326 /** 327 * Inform the notification manager about dismissal of specific notifications. 328 * <p> 329 * Use this if your listener has a user interface that allows the user to dismiss 330 * multiple notifications at once. 331 * 332 * @param keys Notifications to dismiss, or {@code null} to dismiss all. 333 * 334 * {@see #cancelNotification(String, String, int)} 335 */ 336 public final void cancelNotifications(String[] keys) { 337 if (!isBound()) return; 338 try { 339 getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys); 340 } catch (android.os.RemoteException ex) { 341 Log.v(TAG, "Unable to contact notification manager", ex); 342 } 343 } 344 345 /** 346 * Sets the notification trim that will be received via {@link #onNotificationPosted}. 347 * 348 * <p> 349 * Setting a trim other than {@link #TRIM_FULL} enables listeners that don't need access to the 350 * full notification features right away to reduce their memory footprint. Full notifications 351 * can be requested on-demand via {@link #getActiveNotifications(int)}. 352 * 353 * <p> 354 * Set to {@link #TRIM_FULL} initially. 355 * 356 * @hide 357 * 358 * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}. 359 * See <code>TRIM_*</code> constants. 360 */ 361 @SystemApi 362 public final void setOnNotificationPostedTrim(int trim) { 363 if (!isBound()) return; 364 try { 365 getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim); 366 } catch (RemoteException ex) { 367 Log.v(TAG, "Unable to contact notification manager", ex); 368 } 369 } 370 371 /** 372 * Request the list of outstanding notifications (that is, those that are visible to the 373 * current user). Useful when you don't know what's already been posted. 374 * 375 * @return An array of active notifications, sorted in natural order. 376 */ 377 public StatusBarNotification[] getActiveNotifications() { 378 return getActiveNotifications(null, TRIM_FULL); 379 } 380 381 /** 382 * Request the list of outstanding notifications (that is, those that are visible to the 383 * current user). Useful when you don't know what's already been posted. 384 * 385 * @hide 386 * 387 * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants. 388 * @return An array of active notifications, sorted in natural order. 389 */ 390 @SystemApi 391 public StatusBarNotification[] getActiveNotifications(int trim) { 392 return getActiveNotifications(null, trim); 393 } 394 395 /** 396 * Request one or more notifications by key. Useful if you have been keeping track of 397 * notifications but didn't want to retain the bits, and now need to go back and extract 398 * more data out of those notifications. 399 * 400 * @param keys the keys of the notifications to request 401 * @return An array of notifications corresponding to the requested keys, in the 402 * same order as the key list. 403 */ 404 public StatusBarNotification[] getActiveNotifications(String[] keys) { 405 return getActiveNotifications(keys, TRIM_FULL); 406 } 407 408 /** 409 * Request one or more notifications by key. Useful if you have been keeping track of 410 * notifications but didn't want to retain the bits, and now need to go back and extract 411 * more data out of those notifications. 412 * 413 * @hide 414 * 415 * @param keys the keys of the notifications to request 416 * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants. 417 * @return An array of notifications corresponding to the requested keys, in the 418 * same order as the key list. 419 */ 420 @SystemApi 421 public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) { 422 if (!isBound()) 423 return null; 424 try { 425 ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface() 426 .getActiveNotificationsFromListener(mWrapper, keys, trim); 427 List<StatusBarNotification> list = parceledList.getList(); 428 429 int N = list.size(); 430 for (int i = 0; i < N; i++) { 431 Notification notification = list.get(i).getNotification(); 432 Builder.rebuild(getContext(), notification); 433 } 434 return list.toArray(new StatusBarNotification[N]); 435 } catch (android.os.RemoteException ex) { 436 Log.v(TAG, "Unable to contact notification manager", ex); 437 } 438 return null; 439 } 440 441 /** 442 * Gets the set of hints representing current state. 443 * 444 * <p> 445 * The current state may differ from the requested state if the hint represents state 446 * shared across all listeners or a feature the notification host does not support or refuses 447 * to grant. 448 * 449 * @return Zero or more of the HINT_ constants. 450 */ 451 public final int getCurrentListenerHints() { 452 if (!isBound()) return 0; 453 try { 454 return getNotificationInterface().getHintsFromListener(mWrapper); 455 } catch (android.os.RemoteException ex) { 456 Log.v(TAG, "Unable to contact notification manager", ex); 457 return 0; 458 } 459 } 460 461 /** 462 * Gets the current notification interruption filter active on the host. 463 * 464 * <p> 465 * The interruption filter defines which notifications are allowed to interrupt the user 466 * (e.g. via sound & vibration) and is applied globally. Listeners can find out whether 467 * a specific notification matched the interruption filter via 468 * {@link Ranking#matchesInterruptionFilter()}. 469 * <p> 470 * The current filter may differ from the previously requested filter if the notification host 471 * does not support or refuses to apply the requested filter, or if another component changed 472 * the filter in the meantime. 473 * <p> 474 * Listen for updates using {@link #onInterruptionFilterChanged(int)}. 475 * 476 * @return One of the INTERRUPTION_FILTER_ constants, or 0 on errors. 477 */ 478 public final int getCurrentInterruptionFilter() { 479 if (!isBound()) return 0; 480 try { 481 return getNotificationInterface().getHintsFromListener(mWrapper); 482 } catch (android.os.RemoteException ex) { 483 Log.v(TAG, "Unable to contact notification manager", ex); 484 return 0; 485 } 486 } 487 488 /** 489 * Sets the desired {@link #getCurrentListenerHints() listener hints}. 490 * 491 * <p> 492 * This is merely a request, the host may or may not choose to take action depending 493 * on other listener requests or other global state. 494 * <p> 495 * Listen for updates using {@link #onListenerHintsChanged(int)}. 496 * 497 * @param hints One or more of the HINT_ constants. 498 */ 499 public final void requestListenerHints(int hints) { 500 if (!isBound()) return; 501 try { 502 getNotificationInterface().requestHintsFromListener(mWrapper, hints); 503 } catch (android.os.RemoteException ex) { 504 Log.v(TAG, "Unable to contact notification manager", ex); 505 } 506 } 507 508 /** 509 * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}. 510 * 511 * <p> 512 * This is merely a request, the host may or may not choose to apply the requested 513 * interruption filter depending on other listener requests or other global state. 514 * <p> 515 * Listen for updates using {@link #onInterruptionFilterChanged(int)}. 516 * 517 * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants. 518 */ 519 public final void requestInterruptionFilter(int interruptionFilter) { 520 if (!isBound()) return; 521 try { 522 getNotificationInterface() 523 .requestInterruptionFilterFromListener(mWrapper, interruptionFilter); 524 } catch (android.os.RemoteException ex) { 525 Log.v(TAG, "Unable to contact notification manager", ex); 526 } 527 } 528 529 /** 530 * Returns current ranking information. 531 * 532 * <p> 533 * The returned object represents the current ranking snapshot and only 534 * applies for currently active notifications. 535 * <p> 536 * Generally you should use the RankingMap that is passed with events such 537 * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)}, 538 * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and 539 * so on. This method should only be used when needing access outside of 540 * such events, for example to retrieve the RankingMap right after 541 * initialization. 542 * 543 * @return A {@link RankingMap} object providing access to ranking information 544 */ 545 public RankingMap getCurrentRanking() { 546 return mRankingMap; 547 } 548 549 @Override 550 public IBinder onBind(Intent intent) { 551 if (mWrapper == null) { 552 mWrapper = new INotificationListenerWrapper(); 553 } 554 return mWrapper; 555 } 556 557 private boolean isBound() { 558 if (mWrapper == null) { 559 Log.w(TAG, "Notification listener service not yet bound."); 560 return false; 561 } 562 return true; 563 } 564 565 /** 566 * Directly register this service with the Notification Manager. 567 * 568 * <p>Only system services may use this call. It will fail for non-system callers. 569 * Apps should ask the user to add their listener in Settings. 570 * 571 * @param context Context required for accessing resources. Since this service isn't 572 * launched as a real Service when using this method, a context has to be passed in. 573 * @param componentName the component that will consume the notification information 574 * @param currentUser the user to use as the stream filter 575 * @hide 576 */ 577 @SystemApi 578 public void registerAsSystemService(Context context, ComponentName componentName, 579 int currentUser) throws RemoteException { 580 mSystemContext = context; 581 if (mWrapper == null) { 582 mWrapper = new INotificationListenerWrapper(); 583 } 584 INotificationManager noMan = getNotificationInterface(); 585 noMan.registerListener(mWrapper, componentName, currentUser); 586 mCurrentUser = currentUser; 587 } 588 589 /** 590 * Directly unregister this service from the Notification Manager. 591 * 592 * <P>This method will fail for listeners that were not registered 593 * with (@link registerAsService). 594 * @hide 595 */ 596 @SystemApi 597 public void unregisterAsSystemService() throws RemoteException { 598 if (mWrapper != null) { 599 INotificationManager noMan = getNotificationInterface(); 600 noMan.unregisterListener(mWrapper, mCurrentUser); 601 } 602 } 603 604 private class INotificationListenerWrapper extends INotificationListener.Stub { 605 @Override 606 public void onNotificationPosted(StatusBarNotification sbn, 607 NotificationRankingUpdate update) { 608 Notification.Builder.rebuild(getContext(), sbn.getNotification()); 609 610 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 611 synchronized (mWrapper) { 612 applyUpdate(update); 613 try { 614 NotificationListenerService.this.onNotificationPosted(sbn, mRankingMap); 615 } catch (Throwable t) { 616 Log.w(TAG, "Error running onNotificationPosted", t); 617 } 618 } 619 } 620 @Override 621 public void onNotificationRemoved(StatusBarNotification sbn, 622 NotificationRankingUpdate update) { 623 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 624 synchronized (mWrapper) { 625 applyUpdate(update); 626 try { 627 NotificationListenerService.this.onNotificationRemoved(sbn, mRankingMap); 628 } catch (Throwable t) { 629 Log.w(TAG, "Error running onNotificationRemoved", t); 630 } 631 } 632 } 633 @Override 634 public void onListenerConnected(NotificationRankingUpdate update) { 635 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 636 synchronized (mWrapper) { 637 applyUpdate(update); 638 try { 639 NotificationListenerService.this.onListenerConnected(); 640 } catch (Throwable t) { 641 Log.w(TAG, "Error running onListenerConnected", t); 642 } 643 } 644 } 645 @Override 646 public void onNotificationRankingUpdate(NotificationRankingUpdate update) 647 throws RemoteException { 648 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 649 synchronized (mWrapper) { 650 applyUpdate(update); 651 try { 652 NotificationListenerService.this.onNotificationRankingUpdate(mRankingMap); 653 } catch (Throwable t) { 654 Log.w(TAG, "Error running onNotificationRankingUpdate", t); 655 } 656 } 657 } 658 @Override 659 public void onListenerHintsChanged(int hints) throws RemoteException { 660 try { 661 NotificationListenerService.this.onListenerHintsChanged(hints); 662 } catch (Throwable t) { 663 Log.w(TAG, "Error running onListenerHintsChanged", t); 664 } 665 } 666 667 @Override 668 public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException { 669 try { 670 NotificationListenerService.this.onInterruptionFilterChanged(interruptionFilter); 671 } catch (Throwable t) { 672 Log.w(TAG, "Error running onInterruptionFilterChanged", t); 673 } 674 } 675 } 676 677 private void applyUpdate(NotificationRankingUpdate update) { 678 mRankingMap = new RankingMap(update); 679 } 680 681 private Context getContext() { 682 if (mSystemContext != null) { 683 return mSystemContext; 684 } 685 return this; 686 } 687 688 /** 689 * Stores ranking related information on a currently active notification. 690 * 691 * <p> 692 * Ranking objects aren't automatically updated as notification events 693 * occur. Instead, ranking information has to be retrieved again via the 694 * current {@link RankingMap}. 695 */ 696 public static class Ranking { 697 /** Value signifying that the user has not expressed a per-app visibility override value. 698 * @hide */ 699 public static final int VISIBILITY_NO_OVERRIDE = -1000; 700 701 private String mKey; 702 private int mRank = -1; 703 private boolean mIsAmbient; 704 private boolean mMatchesInterruptionFilter; 705 private int mVisibilityOverride; 706 707 public Ranking() {} 708 709 /** 710 * Returns the key of the notification this Ranking applies to. 711 */ 712 public String getKey() { 713 return mKey; 714 } 715 716 /** 717 * Returns the rank of the notification. 718 * 719 * @return the rank of the notification, that is the 0-based index in 720 * the list of active notifications. 721 */ 722 public int getRank() { 723 return mRank; 724 } 725 726 /** 727 * Returns whether the notification is an ambient notification, that is 728 * a notification that doesn't require the user's immediate attention. 729 */ 730 public boolean isAmbient() { 731 return mIsAmbient; 732 } 733 734 /** 735 * Returns the user specificed visibility for the package that posted 736 * this notification, or 737 * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if 738 * no such preference has been expressed. 739 * @hide 740 */ 741 public int getVisibilityOverride() { 742 return mVisibilityOverride; 743 } 744 745 /** 746 * Returns whether the notification meets the user's interruption 747 * filter. 748 * 749 * @removed 750 */ 751 public boolean meetsInterruptionFilter() { 752 return mMatchesInterruptionFilter; 753 } 754 755 /** 756 * Returns whether the notification matches the user's interruption 757 * filter. 758 */ 759 public boolean matchesInterruptionFilter() { 760 return mMatchesInterruptionFilter; 761 } 762 763 private void populate(String key, int rank, boolean isAmbient, 764 boolean matchesInterruptionFilter, int visibilityOverride) { 765 mKey = key; 766 mRank = rank; 767 mIsAmbient = isAmbient; 768 mMatchesInterruptionFilter = matchesInterruptionFilter; 769 mVisibilityOverride = visibilityOverride; 770 } 771 } 772 773 /** 774 * Provides access to ranking information on currently active 775 * notifications. 776 * 777 * <p> 778 * Note that this object represents a ranking snapshot that only applies to 779 * notifications active at the time of retrieval. 780 */ 781 public static class RankingMap implements Parcelable { 782 private final NotificationRankingUpdate mRankingUpdate; 783 private ArrayMap<String,Integer> mRanks; 784 private ArraySet<Object> mIntercepted; 785 private ArrayMap<String, Integer> mVisibilityOverrides; 786 787 private RankingMap(NotificationRankingUpdate rankingUpdate) { 788 mRankingUpdate = rankingUpdate; 789 } 790 791 /** 792 * Request the list of notification keys in their current ranking 793 * order. 794 * 795 * @return An array of active notification keys, in their ranking order. 796 */ 797 public String[] getOrderedKeys() { 798 return mRankingUpdate.getOrderedKeys(); 799 } 800 801 /** 802 * Populates outRanking with ranking information for the notification 803 * with the given key. 804 * 805 * @return true if a valid key has been passed and outRanking has 806 * been populated; false otherwise 807 */ 808 public boolean getRanking(String key, Ranking outRanking) { 809 int rank = getRank(key); 810 outRanking.populate(key, rank, isAmbient(key), !isIntercepted(key), 811 getVisibilityOverride(key)); 812 return rank >= 0; 813 } 814 815 private int getRank(String key) { 816 synchronized (this) { 817 if (mRanks == null) { 818 buildRanksLocked(); 819 } 820 } 821 Integer rank = mRanks.get(key); 822 return rank != null ? rank : -1; 823 } 824 825 private boolean isAmbient(String key) { 826 int firstAmbientIndex = mRankingUpdate.getFirstAmbientIndex(); 827 if (firstAmbientIndex < 0) { 828 return false; 829 } 830 int rank = getRank(key); 831 return rank >= 0 && rank >= firstAmbientIndex; 832 } 833 834 private boolean isIntercepted(String key) { 835 synchronized (this) { 836 if (mIntercepted == null) { 837 buildInterceptedSetLocked(); 838 } 839 } 840 return mIntercepted.contains(key); 841 } 842 843 private int getVisibilityOverride(String key) { 844 synchronized (this) { 845 if (mVisibilityOverrides == null) { 846 buildVisibilityOverridesLocked(); 847 } 848 } 849 Integer overide = mVisibilityOverrides.get(key); 850 if (overide == null) { 851 return Ranking.VISIBILITY_NO_OVERRIDE; 852 } 853 return overide.intValue(); 854 } 855 856 // Locked by 'this' 857 private void buildRanksLocked() { 858 String[] orderedKeys = mRankingUpdate.getOrderedKeys(); 859 mRanks = new ArrayMap<>(orderedKeys.length); 860 for (int i = 0; i < orderedKeys.length; i++) { 861 String key = orderedKeys[i]; 862 mRanks.put(key, i); 863 } 864 } 865 866 // Locked by 'this' 867 private void buildInterceptedSetLocked() { 868 String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys(); 869 mIntercepted = new ArraySet<>(dndInterceptedKeys.length); 870 Collections.addAll(mIntercepted, dndInterceptedKeys); 871 } 872 873 // Locked by 'this' 874 private void buildVisibilityOverridesLocked() { 875 Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides(); 876 mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size()); 877 for (String key: visibilityBundle.keySet()) { 878 mVisibilityOverrides.put(key, visibilityBundle.getInt(key)); 879 } 880 } 881 882 // ----------- Parcelable 883 884 @Override 885 public int describeContents() { 886 return 0; 887 } 888 889 @Override 890 public void writeToParcel(Parcel dest, int flags) { 891 dest.writeParcelable(mRankingUpdate, flags); 892 } 893 894 public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() { 895 @Override 896 public RankingMap createFromParcel(Parcel source) { 897 NotificationRankingUpdate rankingUpdate = source.readParcelable(null); 898 return new RankingMap(rankingUpdate); 899 } 900 901 @Override 902 public RankingMap[] newArray(int size) { 903 return new RankingMap[size]; 904 } 905 }; 906 } 907} 908