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 17 package android.service.notification; 18 19 import android.annotation.SystemApi; 20 import android.annotation.SdkConstant; 21 import android.app.INotificationManager; 22 import android.app.Notification; 23 import android.app.Notification.Builder; 24 import android.app.Service; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.ParceledListSlice; 29 import android.os.Bundle; 30 import android.os.IBinder; 31 import android.os.Parcel; 32 import android.os.Parcelable; 33 import android.os.RemoteException; 34 import android.os.ServiceManager; 35 import android.util.ArrayMap; 36 import android.util.ArraySet; 37 import android.util.Log; 38 39 import java.util.Collections; 40 import 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 */ 57 public 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#LOLLIPOP} 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#LOLLIPOP}. 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().getInterruptionFilterFromListener(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(IStatusBarNotificationHolder sbnHolder, 607 NotificationRankingUpdate update) { 608 StatusBarNotification sbn; 609 try { 610 sbn = sbnHolder.get(); 611 } catch (RemoteException e) { 612 Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e); 613 return; 614 } 615 Notification.Builder.rebuild(getContext(), sbn.getNotification()); 616 617 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 618 synchronized (mWrapper) { 619 applyUpdate(update); 620 try { 621 NotificationListenerService.this.onNotificationPosted(sbn, mRankingMap); 622 } catch (Throwable t) { 623 Log.w(TAG, "Error running onNotificationPosted", t); 624 } 625 } 626 } 627 @Override 628 public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder, 629 NotificationRankingUpdate update) { 630 StatusBarNotification sbn; 631 try { 632 sbn = sbnHolder.get(); 633 } catch (RemoteException e) { 634 Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e); 635 return; 636 } 637 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 638 synchronized (mWrapper) { 639 applyUpdate(update); 640 try { 641 NotificationListenerService.this.onNotificationRemoved(sbn, mRankingMap); 642 } catch (Throwable t) { 643 Log.w(TAG, "Error running onNotificationRemoved", t); 644 } 645 } 646 } 647 @Override 648 public void onListenerConnected(NotificationRankingUpdate update) { 649 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 650 synchronized (mWrapper) { 651 applyUpdate(update); 652 try { 653 NotificationListenerService.this.onListenerConnected(); 654 } catch (Throwable t) { 655 Log.w(TAG, "Error running onListenerConnected", t); 656 } 657 } 658 } 659 @Override 660 public void onNotificationRankingUpdate(NotificationRankingUpdate update) 661 throws RemoteException { 662 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 663 synchronized (mWrapper) { 664 applyUpdate(update); 665 try { 666 NotificationListenerService.this.onNotificationRankingUpdate(mRankingMap); 667 } catch (Throwable t) { 668 Log.w(TAG, "Error running onNotificationRankingUpdate", t); 669 } 670 } 671 } 672 @Override 673 public void onListenerHintsChanged(int hints) throws RemoteException { 674 try { 675 NotificationListenerService.this.onListenerHintsChanged(hints); 676 } catch (Throwable t) { 677 Log.w(TAG, "Error running onListenerHintsChanged", t); 678 } 679 } 680 681 @Override 682 public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException { 683 try { 684 NotificationListenerService.this.onInterruptionFilterChanged(interruptionFilter); 685 } catch (Throwable t) { 686 Log.w(TAG, "Error running onInterruptionFilterChanged", t); 687 } 688 } 689 } 690 691 private void applyUpdate(NotificationRankingUpdate update) { 692 mRankingMap = new RankingMap(update); 693 } 694 695 private Context getContext() { 696 if (mSystemContext != null) { 697 return mSystemContext; 698 } 699 return this; 700 } 701 702 /** 703 * Stores ranking related information on a currently active notification. 704 * 705 * <p> 706 * Ranking objects aren't automatically updated as notification events 707 * occur. Instead, ranking information has to be retrieved again via the 708 * current {@link RankingMap}. 709 */ 710 public static class Ranking { 711 /** Value signifying that the user has not expressed a per-app visibility override value. 712 * @hide */ 713 public static final int VISIBILITY_NO_OVERRIDE = -1000; 714 715 private String mKey; 716 private int mRank = -1; 717 private boolean mIsAmbient; 718 private boolean mMatchesInterruptionFilter; 719 private int mVisibilityOverride; 720 721 public Ranking() {} 722 723 /** 724 * Returns the key of the notification this Ranking applies to. 725 */ 726 public String getKey() { 727 return mKey; 728 } 729 730 /** 731 * Returns the rank of the notification. 732 * 733 * @return the rank of the notification, that is the 0-based index in 734 * the list of active notifications. 735 */ 736 public int getRank() { 737 return mRank; 738 } 739 740 /** 741 * Returns whether the notification is an ambient notification, that is 742 * a notification that doesn't require the user's immediate attention. 743 */ 744 public boolean isAmbient() { 745 return mIsAmbient; 746 } 747 748 /** 749 * Returns the user specificed visibility for the package that posted 750 * this notification, or 751 * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if 752 * no such preference has been expressed. 753 * @hide 754 */ 755 public int getVisibilityOverride() { 756 return mVisibilityOverride; 757 } 758 759 760 /** 761 * Returns whether the notification matches the user's interruption 762 * filter. 763 * 764 * @return {@code true} if the notification is allowed by the filter, or 765 * {@code false} if it is blocked. 766 */ 767 public boolean matchesInterruptionFilter() { 768 return mMatchesInterruptionFilter; 769 } 770 771 private void populate(String key, int rank, boolean isAmbient, 772 boolean matchesInterruptionFilter, int visibilityOverride) { 773 mKey = key; 774 mRank = rank; 775 mIsAmbient = isAmbient; 776 mMatchesInterruptionFilter = matchesInterruptionFilter; 777 mVisibilityOverride = visibilityOverride; 778 } 779 } 780 781 /** 782 * Provides access to ranking information on currently active 783 * notifications. 784 * 785 * <p> 786 * Note that this object represents a ranking snapshot that only applies to 787 * notifications active at the time of retrieval. 788 */ 789 public static class RankingMap implements Parcelable { 790 private final NotificationRankingUpdate mRankingUpdate; 791 private ArrayMap<String,Integer> mRanks; 792 private ArraySet<Object> mIntercepted; 793 private ArrayMap<String, Integer> mVisibilityOverrides; 794 795 private RankingMap(NotificationRankingUpdate rankingUpdate) { 796 mRankingUpdate = rankingUpdate; 797 } 798 799 /** 800 * Request the list of notification keys in their current ranking 801 * order. 802 * 803 * @return An array of active notification keys, in their ranking order. 804 */ 805 public String[] getOrderedKeys() { 806 return mRankingUpdate.getOrderedKeys(); 807 } 808 809 /** 810 * Populates outRanking with ranking information for the notification 811 * with the given key. 812 * 813 * @return true if a valid key has been passed and outRanking has 814 * been populated; false otherwise 815 */ 816 public boolean getRanking(String key, Ranking outRanking) { 817 int rank = getRank(key); 818 outRanking.populate(key, rank, isAmbient(key), !isIntercepted(key), 819 getVisibilityOverride(key)); 820 return rank >= 0; 821 } 822 823 private int getRank(String key) { 824 synchronized (this) { 825 if (mRanks == null) { 826 buildRanksLocked(); 827 } 828 } 829 Integer rank = mRanks.get(key); 830 return rank != null ? rank : -1; 831 } 832 833 private boolean isAmbient(String key) { 834 int firstAmbientIndex = mRankingUpdate.getFirstAmbientIndex(); 835 if (firstAmbientIndex < 0) { 836 return false; 837 } 838 int rank = getRank(key); 839 return rank >= 0 && rank >= firstAmbientIndex; 840 } 841 842 private boolean isIntercepted(String key) { 843 synchronized (this) { 844 if (mIntercepted == null) { 845 buildInterceptedSetLocked(); 846 } 847 } 848 return mIntercepted.contains(key); 849 } 850 851 private int getVisibilityOverride(String key) { 852 synchronized (this) { 853 if (mVisibilityOverrides == null) { 854 buildVisibilityOverridesLocked(); 855 } 856 } 857 Integer overide = mVisibilityOverrides.get(key); 858 if (overide == null) { 859 return Ranking.VISIBILITY_NO_OVERRIDE; 860 } 861 return overide.intValue(); 862 } 863 864 // Locked by 'this' 865 private void buildRanksLocked() { 866 String[] orderedKeys = mRankingUpdate.getOrderedKeys(); 867 mRanks = new ArrayMap<>(orderedKeys.length); 868 for (int i = 0; i < orderedKeys.length; i++) { 869 String key = orderedKeys[i]; 870 mRanks.put(key, i); 871 } 872 } 873 874 // Locked by 'this' 875 private void buildInterceptedSetLocked() { 876 String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys(); 877 mIntercepted = new ArraySet<>(dndInterceptedKeys.length); 878 Collections.addAll(mIntercepted, dndInterceptedKeys); 879 } 880 881 // Locked by 'this' 882 private void buildVisibilityOverridesLocked() { 883 Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides(); 884 mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size()); 885 for (String key: visibilityBundle.keySet()) { 886 mVisibilityOverrides.put(key, visibilityBundle.getInt(key)); 887 } 888 } 889 890 // ----------- Parcelable 891 892 @Override 893 public int describeContents() { 894 return 0; 895 } 896 897 @Override 898 public void writeToParcel(Parcel dest, int flags) { 899 dest.writeParcelable(mRankingUpdate, flags); 900 } 901 902 public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() { 903 @Override 904 public RankingMap createFromParcel(Parcel source) { 905 NotificationRankingUpdate rankingUpdate = source.readParcelable(null); 906 return new RankingMap(rankingUpdate); 907 } 908 909 @Override 910 public RankingMap[] newArray(int size) { 911 return new RankingMap[size]; 912 } 913 }; 914 } 915 } 916