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