NotificationListenerService.java revision 0efdb88ccc9d650e7a644b9be8f63792f2c66841
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.os.Handler; 20import android.os.Looper; 21import android.os.Message; 22 23import android.annotation.IntDef; 24import android.annotation.SystemApi; 25import android.annotation.SdkConstant; 26import android.app.INotificationManager; 27import android.app.Notification; 28import android.app.Notification.Builder; 29import android.app.NotificationManager; 30import android.app.Service; 31import android.content.ComponentName; 32import android.content.Context; 33import android.content.Intent; 34import android.content.pm.ParceledListSlice; 35import android.graphics.drawable.BitmapDrawable; 36import android.graphics.drawable.Drawable; 37import android.graphics.drawable.Icon; 38import android.graphics.Bitmap; 39import android.os.Build; 40import android.os.Bundle; 41import android.os.IBinder; 42import android.os.Parcel; 43import android.os.Parcelable; 44import android.os.RemoteException; 45import android.os.ServiceManager; 46import android.util.ArrayMap; 47import android.util.ArraySet; 48import android.util.Log; 49import android.widget.RemoteViews; 50import com.android.internal.annotations.GuardedBy; 51import com.android.internal.os.SomeArgs; 52import java.lang.annotation.Retention; 53import java.lang.annotation.RetentionPolicy; 54import java.util.ArrayList; 55import java.util.Collections; 56import java.util.List; 57 58/** 59 * A service that receives calls from the system when new notifications are 60 * posted or removed, or their ranking changed. 61 * <p>To extend this class, you must declare the service in your manifest file with 62 * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission 63 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p> 64 * <pre> 65 * <service android:name=".NotificationListener" 66 * android:label="@string/service_name" 67 * android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> 68 * <intent-filter> 69 * <action android:name="android.service.notification.NotificationListenerService" /> 70 * </intent-filter> 71 * </service></pre> 72 * <p> Typically, while enabled in user settings, this service will be bound on boot or when a 73 * settings change occurs that could affect whether this service should run. However, for some 74 * system usage modes, the you may instead specify that this service is instead bound and unbound 75 * in response to mode changes by including a category in the intent filter. Currently 76 * supported categories are: 77 * <ul> 78 * <li>{@link #CATEGORY_VR_NOTIFICATIONS} - this service is bound when an Activity has enabled 79 * VR mode. {@see android.app.Activity#setVrMode(boolean)}.</li> 80 * </ul> 81 * </p> 82 */ 83public abstract class NotificationListenerService extends Service { 84 // TAG = "NotificationListenerService[MySubclass]" 85 private final String TAG = NotificationListenerService.class.getSimpleName() 86 + "[" + getClass().getSimpleName() + "]"; 87 88 /** 89 * {@link #getCurrentInterruptionFilter() Interruption filter} constant - 90 * Normal interruption filter. 91 */ 92 public static final int INTERRUPTION_FILTER_ALL 93 = NotificationManager.INTERRUPTION_FILTER_ALL; 94 95 /** 96 * {@link #getCurrentInterruptionFilter() Interruption filter} constant - 97 * Priority interruption filter. 98 */ 99 public static final int INTERRUPTION_FILTER_PRIORITY 100 = NotificationManager.INTERRUPTION_FILTER_PRIORITY; 101 102 /** 103 * {@link #getCurrentInterruptionFilter() Interruption filter} constant - 104 * No interruptions filter. 105 */ 106 public static final int INTERRUPTION_FILTER_NONE 107 = NotificationManager.INTERRUPTION_FILTER_NONE; 108 109 /** 110 * {@link #getCurrentInterruptionFilter() Interruption filter} constant - 111 * Alarms only interruption filter. 112 */ 113 public static final int INTERRUPTION_FILTER_ALARMS 114 = NotificationManager.INTERRUPTION_FILTER_ALARMS; 115 116 /** {@link #getCurrentInterruptionFilter() Interruption filter} constant - returned when 117 * the value is unavailable for any reason. For example, before the notification listener 118 * is connected. 119 * 120 * {@see #onListenerConnected()} 121 */ 122 public static final int INTERRUPTION_FILTER_UNKNOWN 123 = NotificationManager.INTERRUPTION_FILTER_UNKNOWN; 124 125 /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI 126 * should disable notification sound, vibrating and other visual or aural effects. 127 * This does not change the interruption filter, only the effects. **/ 128 public static final int HINT_HOST_DISABLE_EFFECTS = 1; 129 130 /** 131 * Whether notification suppressed by DND should not interruption visually when the screen is 132 * off. 133 */ 134 public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 135 NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; 136 /** 137 * Whether notification suppressed by DND should not interruption visually when the screen is 138 * on. 139 */ 140 public static final int SUPPRESSED_EFFECT_SCREEN_ON = 141 NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; 142 143 /** 144 * The full trim of the StatusBarNotification including all its features. 145 * 146 * @hide 147 */ 148 @SystemApi 149 public static final int TRIM_FULL = 0; 150 151 /** 152 * A light trim of the StatusBarNotification excluding the following features: 153 * 154 * <ol> 155 * <li>{@link Notification#tickerView tickerView}</li> 156 * <li>{@link Notification#contentView contentView}</li> 157 * <li>{@link Notification#largeIcon largeIcon}</li> 158 * <li>{@link Notification#bigContentView bigContentView}</li> 159 * <li>{@link Notification#headsUpContentView headsUpContentView}</li> 160 * <li>{@link Notification#EXTRA_LARGE_ICON extras[EXTRA_LARGE_ICON]}</li> 161 * <li>{@link Notification#EXTRA_LARGE_ICON_BIG extras[EXTRA_LARGE_ICON_BIG]}</li> 162 * <li>{@link Notification#EXTRA_PICTURE extras[EXTRA_PICTURE]}</li> 163 * <li>{@link Notification#EXTRA_BIG_TEXT extras[EXTRA_BIG_TEXT]}</li> 164 * </ol> 165 * 166 * @hide 167 */ 168 @SystemApi 169 public static final int TRIM_LIGHT = 1; 170 171 private final Object mLock = new Object(); 172 173 private Handler mHandler; 174 175 /** @hide */ 176 protected NotificationListenerWrapper mWrapper = null; 177 178 @GuardedBy("mLock") 179 private RankingMap mRankingMap; 180 181 private INotificationManager mNoMan; 182 183 /** 184 * Only valid after a successful call to (@link registerAsService}. 185 * @hide 186 */ 187 protected int mCurrentUser; 188 189 /** 190 * This context is required for system services since NotificationListenerService isn't 191 * started as a real Service and hence no context is available.. 192 * @hide 193 */ 194 protected Context mSystemContext; 195 196 /** 197 * The {@link Intent} that must be declared as handled by the service. 198 */ 199 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 200 public static final String SERVICE_INTERFACE 201 = "android.service.notification.NotificationListenerService"; 202 203 /** 204 * If this category is declared in the application manifest for a service of this type, this 205 * service will be bound when VR mode is enabled, and unbound when VR mode is disabled rather 206 * than the normal lifecycle for a notification service. 207 * 208 * {@see android.app.Activity#setVrMode(boolean)} 209 */ 210 @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY) 211 public static final String CATEGORY_VR_NOTIFICATIONS = 212 "android.intent.category.vr.notifications"; 213 214 @Override 215 protected void attachBaseContext(Context base) { 216 super.attachBaseContext(base); 217 mHandler = new MyHandler(getMainLooper()); 218 } 219 220 /** 221 * Implement this method to learn about new notifications as they are posted by apps. 222 * 223 * @param sbn A data structure encapsulating the original {@link android.app.Notification} 224 * object as well as its identifying information (tag and id) and source 225 * (package name). 226 */ 227 public void onNotificationPosted(StatusBarNotification sbn) { 228 // optional 229 } 230 231 /** 232 * Implement this method to learn about new notifications as they are posted by apps. 233 * 234 * @param sbn A data structure encapsulating the original {@link android.app.Notification} 235 * object as well as its identifying information (tag and id) and source 236 * (package name). 237 * @param rankingMap The current ranking map that can be used to retrieve ranking information 238 * for active notifications, including the newly posted one. 239 */ 240 public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { 241 onNotificationPosted(sbn); 242 } 243 244 /** 245 * Implement this method to learn when notifications are removed. 246 * <P> 247 * This might occur because the user has dismissed the notification using system UI (or another 248 * notification listener) or because the app has withdrawn the notification. 249 * <P> 250 * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the 251 * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight 252 * fields such as {@link android.app.Notification#contentView} and 253 * {@link android.app.Notification#largeIcon}. However, all other fields on 254 * {@link StatusBarNotification}, sufficient to match this call with a prior call to 255 * {@link #onNotificationPosted(StatusBarNotification)}, will be intact. 256 * 257 * @param sbn A data structure encapsulating at least the original information (tag and id) 258 * and source (package name) used to post the {@link android.app.Notification} that 259 * was just removed. 260 */ 261 public void onNotificationRemoved(StatusBarNotification sbn) { 262 // optional 263 } 264 265 /** 266 * Implement this method to learn when notifications are removed. 267 * <P> 268 * This might occur because the user has dismissed the notification using system UI (or another 269 * notification listener) or because the app has withdrawn the notification. 270 * <P> 271 * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the 272 * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight 273 * fields such as {@link android.app.Notification#contentView} and 274 * {@link android.app.Notification#largeIcon}. However, all other fields on 275 * {@link StatusBarNotification}, sufficient to match this call with a prior call to 276 * {@link #onNotificationPosted(StatusBarNotification)}, will be intact. 277 * 278 * @param sbn A data structure encapsulating at least the original information (tag and id) 279 * and source (package name) used to post the {@link android.app.Notification} that 280 * was just removed. 281 * @param rankingMap The current ranking map that can be used to retrieve ranking information 282 * for active notifications. 283 * 284 */ 285 public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) { 286 onNotificationRemoved(sbn); 287 } 288 289 /** 290 * Implement this method to learn about when the listener is enabled and connected to 291 * the notification manager. You are safe to call {@link #getActiveNotifications()} 292 * at this time. 293 */ 294 public void onListenerConnected() { 295 // optional 296 } 297 298 /** 299 * Implement this method to be notified when the notification ranking changes. 300 * 301 * @param rankingMap The current ranking map that can be used to retrieve ranking information 302 * for active notifications. 303 */ 304 public void onNotificationRankingUpdate(RankingMap rankingMap) { 305 // optional 306 } 307 308 /** 309 * Implement this method to be notified when the 310 * {@link #getCurrentListenerHints() Listener hints} change. 311 * 312 * @param hints The current {@link #getCurrentListenerHints() listener hints}. 313 */ 314 public void onListenerHintsChanged(int hints) { 315 // optional 316 } 317 318 /** 319 * Implement this method to be notified when the 320 * {@link #getCurrentInterruptionFilter() interruption filter} changed. 321 * 322 * @param interruptionFilter The current 323 * {@link #getCurrentInterruptionFilter() interruption filter}. 324 */ 325 public void onInterruptionFilterChanged(int interruptionFilter) { 326 // optional 327 } 328 329 /** @hide */ 330 protected final INotificationManager getNotificationInterface() { 331 if (mNoMan == null) { 332 mNoMan = INotificationManager.Stub.asInterface( 333 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 334 } 335 return mNoMan; 336 } 337 338 /** 339 * Inform the notification manager about dismissal of a single notification. 340 * <p> 341 * Use this if your listener has a user interface that allows the user to dismiss individual 342 * notifications, similar to the behavior of Android's status bar and notification panel. 343 * It should be called after the user dismisses a single notification using your UI; 344 * upon being informed, the notification manager will actually remove the notification 345 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. 346 * <P> 347 * <b>Note:</b> If your listener allows the user to fire a notification's 348 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call 349 * this method at that time <i>if</i> the Notification in question has the 350 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set. 351 * 352 * @param pkg Package of the notifying app. 353 * @param tag Tag of the notification as specified by the notifying app in 354 * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}. 355 * @param id ID of the notification as specified by the notifying app in 356 * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}. 357 * <p> 358 * @deprecated Use {@link #cancelNotification(String key)} 359 * instead. Beginning with {@link android.os.Build.VERSION_CODES#LOLLIPOP} this method will no longer 360 * cancel the notification. It will continue to cancel the notification for applications 361 * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}. 362 */ 363 public final void cancelNotification(String pkg, String tag, int id) { 364 if (!isBound()) return; 365 try { 366 getNotificationInterface().cancelNotificationFromListener( 367 mWrapper, pkg, tag, id); 368 } catch (android.os.RemoteException ex) { 369 Log.v(TAG, "Unable to contact notification manager", ex); 370 } 371 } 372 373 /** 374 * Inform the notification manager about dismissal of a single notification. 375 * <p> 376 * Use this if your listener has a user interface that allows the user to dismiss individual 377 * notifications, similar to the behavior of Android's status bar and notification panel. 378 * It should be called after the user dismisses a single notification using your UI; 379 * upon being informed, the notification manager will actually remove the notification 380 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. 381 * <P> 382 * <b>Note:</b> If your listener allows the user to fire a notification's 383 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call 384 * this method at that time <i>if</i> the Notification in question has the 385 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set. 386 * <p> 387 * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}. 388 */ 389 public final void cancelNotification(String key) { 390 if (!isBound()) return; 391 try { 392 getNotificationInterface().cancelNotificationsFromListener(mWrapper, 393 new String[] { key }); 394 } catch (android.os.RemoteException ex) { 395 Log.v(TAG, "Unable to contact notification manager", ex); 396 } 397 } 398 399 /** 400 * Inform the notification manager about dismissal of all notifications. 401 * <p> 402 * Use this if your listener has a user interface that allows the user to dismiss all 403 * notifications, similar to the behavior of Android's status bar and notification panel. 404 * It should be called after the user invokes the "dismiss all" function of your UI; 405 * upon being informed, the notification manager will actually remove all active notifications 406 * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks. 407 * 408 * {@see #cancelNotification(String, String, int)} 409 */ 410 public final void cancelAllNotifications() { 411 cancelNotifications(null /*all*/); 412 } 413 414 /** 415 * Inform the notification manager about dismissal of specific notifications. 416 * <p> 417 * Use this if your listener has a user interface that allows the user to dismiss 418 * multiple notifications at once. 419 * 420 * @param keys Notifications to dismiss, or {@code null} to dismiss all. 421 * 422 * {@see #cancelNotification(String, String, int)} 423 */ 424 public final void cancelNotifications(String[] keys) { 425 if (!isBound()) return; 426 try { 427 getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys); 428 } catch (android.os.RemoteException ex) { 429 Log.v(TAG, "Unable to contact notification manager", ex); 430 } 431 } 432 433 /** 434 * Inform the notification manager that these notifications have been viewed by the 435 * user. This should only be called when there is sufficient confidence that the user is 436 * looking at the notifications, such as when the notifications appear on the screen due to 437 * an explicit user interaction. 438 * @param keys Notifications to mark as seen. 439 */ 440 public final void setNotificationsShown(String[] keys) { 441 if (!isBound()) return; 442 try { 443 getNotificationInterface().setNotificationsShownFromListener(mWrapper, keys); 444 } catch (android.os.RemoteException ex) { 445 Log.v(TAG, "Unable to contact notification manager", ex); 446 } 447 } 448 449 /** 450 * Sets the notification trim that will be received via {@link #onNotificationPosted}. 451 * 452 * <p> 453 * Setting a trim other than {@link #TRIM_FULL} enables listeners that don't need access to the 454 * full notification features right away to reduce their memory footprint. Full notifications 455 * can be requested on-demand via {@link #getActiveNotifications(int)}. 456 * 457 * <p> 458 * Set to {@link #TRIM_FULL} initially. 459 * 460 * @hide 461 * 462 * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}. 463 * See <code>TRIM_*</code> constants. 464 */ 465 @SystemApi 466 public final void setOnNotificationPostedTrim(int trim) { 467 if (!isBound()) return; 468 try { 469 getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim); 470 } catch (RemoteException ex) { 471 Log.v(TAG, "Unable to contact notification manager", ex); 472 } 473 } 474 475 /** 476 * Request the list of outstanding notifications (that is, those that are visible to the 477 * current user). Useful when you don't know what's already been posted. 478 * 479 * @return An array of active notifications, sorted in natural order. 480 */ 481 public StatusBarNotification[] getActiveNotifications() { 482 return getActiveNotifications(null, TRIM_FULL); 483 } 484 485 /** 486 * Request the list of outstanding notifications (that is, those that are visible to the 487 * current user). Useful when you don't know what's already been posted. 488 * 489 * @hide 490 * 491 * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants. 492 * @return An array of active notifications, sorted in natural order. 493 */ 494 @SystemApi 495 public StatusBarNotification[] getActiveNotifications(int trim) { 496 return getActiveNotifications(null, trim); 497 } 498 499 /** 500 * Request one or more notifications by key. Useful if you have been keeping track of 501 * notifications but didn't want to retain the bits, and now need to go back and extract 502 * more data out of those notifications. 503 * 504 * @param keys the keys of the notifications to request 505 * @return An array of notifications corresponding to the requested keys, in the 506 * same order as the key list. 507 */ 508 public StatusBarNotification[] getActiveNotifications(String[] keys) { 509 return getActiveNotifications(keys, TRIM_FULL); 510 } 511 512 /** 513 * Request one or more notifications by key. Useful if you have been keeping track of 514 * notifications but didn't want to retain the bits, and now need to go back and extract 515 * more data out of those notifications. 516 * 517 * @hide 518 * 519 * @param keys the keys of the notifications to request 520 * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants. 521 * @return An array of notifications corresponding to the requested keys, in the 522 * same order as the key list. 523 */ 524 @SystemApi 525 public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) { 526 if (!isBound()) 527 return null; 528 try { 529 ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface() 530 .getActiveNotificationsFromListener(mWrapper, keys, trim); 531 List<StatusBarNotification> list = parceledList.getList(); 532 ArrayList<StatusBarNotification> corruptNotifications = null; 533 int N = list.size(); 534 for (int i = 0; i < N; i++) { 535 StatusBarNotification sbn = list.get(i); 536 Notification notification = sbn.getNotification(); 537 try { 538 // convert icon metadata to legacy format for older clients 539 createLegacyIconExtras(notification); 540 // populate remote views for older clients. 541 maybePopulateRemoteViews(notification); 542 } catch (IllegalArgumentException e) { 543 if (corruptNotifications == null) { 544 corruptNotifications = new ArrayList<>(N); 545 } 546 corruptNotifications.add(sbn); 547 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " + 548 sbn.getPackageName()); 549 } 550 } 551 if (corruptNotifications != null) { 552 list.removeAll(corruptNotifications); 553 } 554 return list.toArray(new StatusBarNotification[list.size()]); 555 } catch (android.os.RemoteException ex) { 556 Log.v(TAG, "Unable to contact notification manager", ex); 557 } 558 return null; 559 } 560 561 /** 562 * Gets the set of hints representing current state. 563 * 564 * <p> 565 * The current state may differ from the requested state if the hint represents state 566 * shared across all listeners or a feature the notification host does not support or refuses 567 * to grant. 568 * 569 * @return Zero or more of the HINT_ constants. 570 */ 571 public final int getCurrentListenerHints() { 572 if (!isBound()) return 0; 573 try { 574 return getNotificationInterface().getHintsFromListener(mWrapper); 575 } catch (android.os.RemoteException ex) { 576 Log.v(TAG, "Unable to contact notification manager", ex); 577 return 0; 578 } 579 } 580 581 /** 582 * Gets the current notification interruption filter active on the host. 583 * 584 * <p> 585 * The interruption filter defines which notifications are allowed to interrupt the user 586 * (e.g. via sound & vibration) and is applied globally. Listeners can find out whether 587 * a specific notification matched the interruption filter via 588 * {@link Ranking#matchesInterruptionFilter()}. 589 * <p> 590 * The current filter may differ from the previously requested filter if the notification host 591 * does not support or refuses to apply the requested filter, or if another component changed 592 * the filter in the meantime. 593 * <p> 594 * Listen for updates using {@link #onInterruptionFilterChanged(int)}. 595 * 596 * @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when 597 * unavailable. 598 */ 599 public final int getCurrentInterruptionFilter() { 600 if (!isBound()) return INTERRUPTION_FILTER_UNKNOWN; 601 try { 602 return getNotificationInterface().getInterruptionFilterFromListener(mWrapper); 603 } catch (android.os.RemoteException ex) { 604 Log.v(TAG, "Unable to contact notification manager", ex); 605 return INTERRUPTION_FILTER_UNKNOWN; 606 } 607 } 608 609 /** 610 * Sets the desired {@link #getCurrentListenerHints() listener hints}. 611 * 612 * <p> 613 * This is merely a request, the host may or may not choose to take action depending 614 * on other listener requests or other global state. 615 * <p> 616 * Listen for updates using {@link #onListenerHintsChanged(int)}. 617 * 618 * @param hints One or more of the HINT_ constants. 619 */ 620 public final void requestListenerHints(int hints) { 621 if (!isBound()) return; 622 try { 623 getNotificationInterface().requestHintsFromListener(mWrapper, hints); 624 } catch (android.os.RemoteException ex) { 625 Log.v(TAG, "Unable to contact notification manager", ex); 626 } 627 } 628 629 /** 630 * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}. 631 * 632 * <p> 633 * This is merely a request, the host may or may not choose to apply the requested 634 * interruption filter depending on other listener requests or other global state. 635 * <p> 636 * Listen for updates using {@link #onInterruptionFilterChanged(int)}. 637 * 638 * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants. 639 */ 640 public final void requestInterruptionFilter(int interruptionFilter) { 641 if (!isBound()) return; 642 try { 643 getNotificationInterface() 644 .requestInterruptionFilterFromListener(mWrapper, interruptionFilter); 645 } catch (android.os.RemoteException ex) { 646 Log.v(TAG, "Unable to contact notification manager", ex); 647 } 648 } 649 650 /** 651 * Returns current ranking information. 652 * 653 * <p> 654 * The returned object represents the current ranking snapshot and only 655 * applies for currently active notifications. 656 * <p> 657 * Generally you should use the RankingMap that is passed with events such 658 * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)}, 659 * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and 660 * so on. This method should only be used when needing access outside of 661 * such events, for example to retrieve the RankingMap right after 662 * initialization. 663 * 664 * @return A {@link RankingMap} object providing access to ranking information 665 */ 666 public RankingMap getCurrentRanking() { 667 synchronized (mLock) { 668 return mRankingMap; 669 } 670 } 671 672 @Override 673 public IBinder onBind(Intent intent) { 674 if (mWrapper == null) { 675 mWrapper = new NotificationListenerWrapper(); 676 } 677 return mWrapper; 678 } 679 680 /** @hide */ 681 protected boolean isBound() { 682 if (mWrapper == null) { 683 Log.w(TAG, "Notification listener service not yet bound."); 684 return false; 685 } 686 return true; 687 } 688 689 /** 690 * Directly register this service with the Notification Manager. 691 * 692 * <p>Only system services may use this call. It will fail for non-system callers. 693 * Apps should ask the user to add their listener in Settings. 694 * 695 * @param context Context required for accessing resources. Since this service isn't 696 * launched as a real Service when using this method, a context has to be passed in. 697 * @param componentName the component that will consume the notification information 698 * @param currentUser the user to use as the stream filter 699 * @hide 700 */ 701 @SystemApi 702 public void registerAsSystemService(Context context, ComponentName componentName, 703 int currentUser) throws RemoteException { 704 if (mWrapper == null) { 705 mWrapper = new NotificationListenerWrapper(); 706 } 707 mSystemContext = context; 708 INotificationManager noMan = getNotificationInterface(); 709 noMan.registerListener(mWrapper, componentName, currentUser); 710 mCurrentUser = currentUser; 711 mHandler = new MyHandler(context.getMainLooper()); 712 } 713 714 /** 715 * Directly unregister this service from the Notification Manager. 716 * 717 * <P>This method will fail for listeners that were not registered 718 * with (@link registerAsService). 719 * @hide 720 */ 721 @SystemApi 722 public void unregisterAsSystemService() throws RemoteException { 723 if (mWrapper != null) { 724 INotificationManager noMan = getNotificationInterface(); 725 noMan.unregisterListener(mWrapper, mCurrentUser); 726 } 727 } 728 729 /** 730 * Request that the listener be rebound, after a previous call to (@link requestUnbind). 731 * 732 * <P>This method will fail for listeners that have 733 * not been granted the permission by the user. 734 * 735 * <P>The service should wait for the {@link #onListenerConnected()} event 736 * before performing any operations. 737 */ 738 public static void requestRebind(ComponentName componentName) 739 throws RemoteException { 740 INotificationManager noMan = INotificationManager.Stub.asInterface( 741 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 742 noMan.requestBindListener(componentName); 743 } 744 745 /** 746 * Request that the service be unbound. 747 * 748 * <P>This will no longer receive updates until 749 * {@link #requestRebind(ComponentName)} is called. 750 * The service will likely be kiled by the system after this call. 751 */ 752 public final void requestUnbind() throws RemoteException { 753 if (mWrapper != null) { 754 INotificationManager noMan = getNotificationInterface(); 755 noMan.requestUnbindListener(mWrapper); 756 } 757 } 758 759 /** Convert new-style Icons to legacy representations for pre-M clients. */ 760 private void createLegacyIconExtras(Notification n) { 761 Icon smallIcon = n.getSmallIcon(); 762 Icon largeIcon = n.getLargeIcon(); 763 if (smallIcon != null && smallIcon.getType() == Icon.TYPE_RESOURCE) { 764 n.extras.putInt(Notification.EXTRA_SMALL_ICON, smallIcon.getResId()); 765 n.icon = smallIcon.getResId(); 766 } 767 if (largeIcon != null) { 768 Drawable d = largeIcon.loadDrawable(getContext()); 769 if (d != null && d instanceof BitmapDrawable) { 770 final Bitmap largeIconBits = ((BitmapDrawable) d).getBitmap(); 771 n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, largeIconBits); 772 n.largeIcon = largeIconBits; 773 } 774 } 775 } 776 777 /** 778 * Populates remote views for pre-N targeting apps. 779 */ 780 private void maybePopulateRemoteViews(Notification notification) { 781 if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 782 Builder builder = Builder.recoverBuilder(getContext(), notification); 783 784 // Some styles wrap Notification's contentView, bigContentView and headsUpContentView. 785 // First inflate them all, only then set them to avoid recursive wrapping. 786 RemoteViews content = builder.createContentView(); 787 RemoteViews big = builder.createBigContentView(); 788 RemoteViews headsUp = builder.createHeadsUpContentView(); 789 790 notification.contentView = content; 791 notification.bigContentView = big; 792 notification.headsUpContentView = headsUp; 793 } 794 } 795 796 /** @hide */ 797 protected class NotificationListenerWrapper extends INotificationListener.Stub { 798 @Override 799 public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder, 800 NotificationRankingUpdate update) { 801 StatusBarNotification sbn; 802 try { 803 sbn = sbnHolder.get(); 804 } catch (RemoteException e) { 805 Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e); 806 return; 807 } 808 809 try { 810 // convert icon metadata to legacy format for older clients 811 createLegacyIconExtras(sbn.getNotification()); 812 maybePopulateRemoteViews(sbn.getNotification()); 813 } catch (IllegalArgumentException e) { 814 // warn and drop corrupt notification 815 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " + 816 sbn.getPackageName()); 817 sbn = null; 818 } 819 820 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 821 synchronized (mLock) { 822 applyUpdateLocked(update); 823 if (sbn != null) { 824 SomeArgs args = SomeArgs.obtain(); 825 args.arg1 = sbn; 826 args.arg2 = mRankingMap; 827 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED, 828 args).sendToTarget(); 829 } else { 830 // still pass along the ranking map, it may contain other information 831 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE, 832 mRankingMap).sendToTarget(); 833 } 834 } 835 836 } 837 838 @Override 839 public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder, 840 NotificationRankingUpdate update) { 841 StatusBarNotification sbn; 842 try { 843 sbn = sbnHolder.get(); 844 } catch (RemoteException e) { 845 Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e); 846 return; 847 } 848 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 849 synchronized (mLock) { 850 applyUpdateLocked(update); 851 SomeArgs args = SomeArgs.obtain(); 852 args.arg1 = sbn; 853 args.arg2 = mRankingMap; 854 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED, 855 args).sendToTarget(); 856 } 857 858 } 859 860 @Override 861 public void onListenerConnected(NotificationRankingUpdate update) { 862 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 863 synchronized (mLock) { 864 applyUpdateLocked(update); 865 } 866 mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_CONNECTED).sendToTarget(); 867 } 868 869 @Override 870 public void onNotificationRankingUpdate(NotificationRankingUpdate update) 871 throws RemoteException { 872 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 873 synchronized (mLock) { 874 applyUpdateLocked(update); 875 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE, 876 mRankingMap).sendToTarget(); 877 } 878 879 } 880 881 @Override 882 public void onListenerHintsChanged(int hints) throws RemoteException { 883 mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_HINTS_CHANGED, 884 hints, 0).sendToTarget(); 885 } 886 887 @Override 888 public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException { 889 mHandler.obtainMessage(MyHandler.MSG_ON_INTERRUPTION_FILTER_CHANGED, 890 interruptionFilter, 0).sendToTarget(); 891 } 892 893 @Override 894 public void onNotificationEnqueued(IStatusBarNotificationHolder notificationHolder, 895 int importance, boolean user) throws RemoteException { 896 // no-op in the listener 897 } 898 899 @Override 900 public void onNotificationVisibilityChanged(String key, long time, boolean visible) 901 throws RemoteException { 902 // no-op in the listener 903 } 904 905 @Override 906 public void onNotificationClick(String key, long time) throws RemoteException { 907 // no-op in the listener 908 } 909 910 @Override 911 public void onNotificationActionClick(String key, long time, int actionIndex) 912 throws RemoteException { 913 // no-op in the listener 914 } 915 916 @Override 917 public void onNotificationRemovedReason(String key, long time, int reason) 918 throws RemoteException { 919 // no-op in the listener 920 } 921 } 922 923 private void applyUpdateLocked(NotificationRankingUpdate update) { 924 mRankingMap = new RankingMap(update); 925 } 926 927 /** @hide */ 928 protected Context getContext() { 929 if (mSystemContext != null) { 930 return mSystemContext; 931 } 932 return this; 933 } 934 935 /** 936 * Stores ranking related information on a currently active notification. 937 * 938 * <p> 939 * Ranking objects aren't automatically updated as notification events 940 * occur. Instead, ranking information has to be retrieved again via the 941 * current {@link RankingMap}. 942 */ 943 public static class Ranking { 944 945 /** @hide */ 946 @IntDef({VISIBILITY_NO_OVERRIDE, IMPORTANCE_UNSPECIFIED, IMPORTANCE_NONE, 947 IMPORTANCE_MIN, IMPORTANCE_LOW, IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, 948 IMPORTANCE_MAX}) 949 @Retention(RetentionPolicy.SOURCE) 950 public @interface Importance {} 951 952 /** Value signifying that the user has not expressed a per-app visibility override value. 953 * @hide */ 954 public static final int VISIBILITY_NO_OVERRIDE = -1000; 955 956 /** 957 * Value signifying that the user has not expressed an importance. 958 * 959 * This value is for persisting preferences, and should never be associated with 960 * an actual notification. 961 */ 962 public static final int IMPORTANCE_UNSPECIFIED = -1000; 963 964 /** 965 * A notification with no importance: shows nowhere, is blocked. 966 */ 967 public static final int IMPORTANCE_NONE = 0; 968 969 /** 970 * Min notification importance: only shows in the shade, below the fold. 971 */ 972 public static final int IMPORTANCE_MIN = 1; 973 974 /** 975 * Low notification importance: shows everywhere, but is not intrusive. 976 */ 977 public static final int IMPORTANCE_LOW = 2; 978 979 /** 980 * Default notification importance: shows everywhere, allowed to makes noise, 981 * but does not visually intrude. 982 */ 983 public static final int IMPORTANCE_DEFAULT = 3; 984 985 /** 986 * Higher notification importance: shows everywhere, allowed to makes noise and peek. 987 */ 988 public static final int IMPORTANCE_HIGH = 4; 989 990 /** 991 * Highest notification importance: shows everywhere, allowed to makes noise, peek, and 992 * use full screen intents. 993 */ 994 public static final int IMPORTANCE_MAX = 5; 995 996 private String mKey; 997 private int mRank = -1; 998 private boolean mIsAmbient; 999 private boolean mMatchesInterruptionFilter; 1000 private int mVisibilityOverride; 1001 private int mSuppressedVisualEffects; 1002 private @Importance int mImportance; 1003 private CharSequence mImportanceExplanation; 1004 1005 public Ranking() {} 1006 1007 /** 1008 * Returns the key of the notification this Ranking applies to. 1009 */ 1010 public String getKey() { 1011 return mKey; 1012 } 1013 1014 /** 1015 * Returns the rank of the notification. 1016 * 1017 * @return the rank of the notification, that is the 0-based index in 1018 * the list of active notifications. 1019 */ 1020 public int getRank() { 1021 return mRank; 1022 } 1023 1024 /** 1025 * Returns whether the notification is an ambient notification, that is 1026 * a notification that doesn't require the user's immediate attention. 1027 */ 1028 public boolean isAmbient() { 1029 return mIsAmbient; 1030 } 1031 1032 /** 1033 * Returns the user specificed visibility for the package that posted 1034 * this notification, or 1035 * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if 1036 * no such preference has been expressed. 1037 * @hide 1038 */ 1039 public int getVisibilityOverride() { 1040 return mVisibilityOverride; 1041 } 1042 1043 /** 1044 * Returns the type(s) of visual effects that should be suppressed for this notification. 1045 * See {@link #SUPPRESSED_EFFECT_SCREEN_OFF}, {@link #SUPPRESSED_EFFECT_SCREEN_ON}. 1046 */ 1047 public int getSuppressedVisualEffects() { 1048 return mSuppressedVisualEffects; 1049 } 1050 1051 /** 1052 * Returns whether the notification matches the user's interruption 1053 * filter. 1054 * 1055 * @return {@code true} if the notification is allowed by the filter, or 1056 * {@code false} if it is blocked. 1057 */ 1058 public boolean matchesInterruptionFilter() { 1059 return mMatchesInterruptionFilter; 1060 } 1061 1062 /** 1063 * Returns the importance of the notification, which dictates its 1064 * modes of presentation, see: {@link #IMPORTANCE_DEFAULT}, etc. 1065 * 1066 * @return the rank of the notification 1067 */ 1068 public @Importance int getImportance() { 1069 return mImportance; 1070 } 1071 1072 /** 1073 * If the importance has been overriden by user preference, then this will be non-null, 1074 * and should be displayed to the user. 1075 * 1076 * @return the explanation for the importance, or null if it is the natural importance 1077 */ 1078 public CharSequence getImportanceExplanation() { 1079 return mImportanceExplanation; 1080 } 1081 1082 private void populate(String key, int rank, boolean matchesInterruptionFilter, 1083 int visibilityOverride, int suppressedVisualEffects, int importance, 1084 CharSequence explanation) { 1085 mKey = key; 1086 mRank = rank; 1087 mIsAmbient = importance < IMPORTANCE_LOW; 1088 mMatchesInterruptionFilter = matchesInterruptionFilter; 1089 mVisibilityOverride = visibilityOverride; 1090 mSuppressedVisualEffects = suppressedVisualEffects; 1091 mImportance = importance; 1092 mImportanceExplanation = explanation; 1093 } 1094 1095 /** 1096 * {@hide} 1097 */ 1098 public static String importanceToString(int importance) { 1099 switch (importance) { 1100 case IMPORTANCE_UNSPECIFIED: 1101 return "UNSPECIFIED"; 1102 case IMPORTANCE_NONE: 1103 return "NONE"; 1104 case IMPORTANCE_MIN: 1105 return "MIN"; 1106 case IMPORTANCE_LOW: 1107 return "LOW"; 1108 case IMPORTANCE_DEFAULT: 1109 return "DEFAULT"; 1110 case IMPORTANCE_HIGH: 1111 return "HIGH"; 1112 case IMPORTANCE_MAX: 1113 return "MAX"; 1114 default: 1115 return "UNKNOWN(" + String.valueOf(importance) + ")"; 1116 } 1117 } 1118 } 1119 1120 /** 1121 * Provides access to ranking information on currently active 1122 * notifications. 1123 * 1124 * <p> 1125 * Note that this object represents a ranking snapshot that only applies to 1126 * notifications active at the time of retrieval. 1127 */ 1128 public static class RankingMap implements Parcelable { 1129 private final NotificationRankingUpdate mRankingUpdate; 1130 private ArrayMap<String,Integer> mRanks; 1131 private ArraySet<Object> mIntercepted; 1132 private ArrayMap<String, Integer> mVisibilityOverrides; 1133 private ArrayMap<String, Integer> mSuppressedVisualEffects; 1134 private ArrayMap<String, Integer> mImportance; 1135 private ArrayMap<String, String> mImportanceExplanation; 1136 1137 private RankingMap(NotificationRankingUpdate rankingUpdate) { 1138 mRankingUpdate = rankingUpdate; 1139 } 1140 1141 /** 1142 * Request the list of notification keys in their current ranking 1143 * order. 1144 * 1145 * @return An array of active notification keys, in their ranking order. 1146 */ 1147 public String[] getOrderedKeys() { 1148 return mRankingUpdate.getOrderedKeys(); 1149 } 1150 1151 /** 1152 * Populates outRanking with ranking information for the notification 1153 * with the given key. 1154 * 1155 * @return true if a valid key has been passed and outRanking has 1156 * been populated; false otherwise 1157 */ 1158 public boolean getRanking(String key, Ranking outRanking) { 1159 int rank = getRank(key); 1160 outRanking.populate(key, rank, !isIntercepted(key), 1161 getVisibilityOverride(key), getSuppressedVisualEffects(key), 1162 getImportance(key), getImportanceExplanation(key)); 1163 return rank >= 0; 1164 } 1165 1166 private int getRank(String key) { 1167 synchronized (this) { 1168 if (mRanks == null) { 1169 buildRanksLocked(); 1170 } 1171 } 1172 Integer rank = mRanks.get(key); 1173 return rank != null ? rank : -1; 1174 } 1175 1176 private boolean isIntercepted(String key) { 1177 synchronized (this) { 1178 if (mIntercepted == null) { 1179 buildInterceptedSetLocked(); 1180 } 1181 } 1182 return mIntercepted.contains(key); 1183 } 1184 1185 private int getVisibilityOverride(String key) { 1186 synchronized (this) { 1187 if (mVisibilityOverrides == null) { 1188 buildVisibilityOverridesLocked(); 1189 } 1190 } 1191 Integer override = mVisibilityOverrides.get(key); 1192 if (override == null) { 1193 return Ranking.VISIBILITY_NO_OVERRIDE; 1194 } 1195 return override.intValue(); 1196 } 1197 1198 private int getSuppressedVisualEffects(String key) { 1199 synchronized (this) { 1200 if (mSuppressedVisualEffects == null) { 1201 buildSuppressedVisualEffectsLocked(); 1202 } 1203 } 1204 Integer suppressed = mSuppressedVisualEffects.get(key); 1205 if (suppressed == null) { 1206 return 0; 1207 } 1208 return suppressed.intValue(); 1209 } 1210 1211 private int getImportance(String key) { 1212 synchronized (this) { 1213 if (mImportance == null) { 1214 buildImportanceLocked(); 1215 } 1216 } 1217 Integer importance = mImportance.get(key); 1218 if (importance == null) { 1219 return Ranking.IMPORTANCE_DEFAULT; 1220 } 1221 return importance.intValue(); 1222 } 1223 1224 private String getImportanceExplanation(String key) { 1225 synchronized (this) { 1226 if (mImportanceExplanation == null) { 1227 buildImportanceExplanationLocked(); 1228 } 1229 } 1230 return mImportanceExplanation.get(key); 1231 } 1232 1233 // Locked by 'this' 1234 private void buildRanksLocked() { 1235 String[] orderedKeys = mRankingUpdate.getOrderedKeys(); 1236 mRanks = new ArrayMap<>(orderedKeys.length); 1237 for (int i = 0; i < orderedKeys.length; i++) { 1238 String key = orderedKeys[i]; 1239 mRanks.put(key, i); 1240 } 1241 } 1242 1243 // Locked by 'this' 1244 private void buildInterceptedSetLocked() { 1245 String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys(); 1246 mIntercepted = new ArraySet<>(dndInterceptedKeys.length); 1247 Collections.addAll(mIntercepted, dndInterceptedKeys); 1248 } 1249 1250 // Locked by 'this' 1251 private void buildVisibilityOverridesLocked() { 1252 Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides(); 1253 mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size()); 1254 for (String key: visibilityBundle.keySet()) { 1255 mVisibilityOverrides.put(key, visibilityBundle.getInt(key)); 1256 } 1257 } 1258 1259 // Locked by 'this' 1260 private void buildSuppressedVisualEffectsLocked() { 1261 Bundle suppressedBundle = mRankingUpdate.getSuppressedVisualEffects(); 1262 mSuppressedVisualEffects = new ArrayMap<>(suppressedBundle.size()); 1263 for (String key: suppressedBundle.keySet()) { 1264 mSuppressedVisualEffects.put(key, suppressedBundle.getInt(key)); 1265 } 1266 } 1267 // Locked by 'this' 1268 private void buildImportanceLocked() { 1269 String[] orderedKeys = mRankingUpdate.getOrderedKeys(); 1270 int[] importance = mRankingUpdate.getImportance(); 1271 mImportance = new ArrayMap<>(orderedKeys.length); 1272 for (int i = 0; i < orderedKeys.length; i++) { 1273 String key = orderedKeys[i]; 1274 mImportance.put(key, importance[i]); 1275 } 1276 } 1277 1278 // Locked by 'this' 1279 private void buildImportanceExplanationLocked() { 1280 Bundle explanationBundle = mRankingUpdate.getImportanceExplanation(); 1281 mImportanceExplanation = new ArrayMap<>(explanationBundle.size()); 1282 for (String key: explanationBundle.keySet()) { 1283 mImportanceExplanation.put(key, explanationBundle.getString(key)); 1284 } 1285 } 1286 1287 // ----------- Parcelable 1288 1289 @Override 1290 public int describeContents() { 1291 return 0; 1292 } 1293 1294 @Override 1295 public void writeToParcel(Parcel dest, int flags) { 1296 dest.writeParcelable(mRankingUpdate, flags); 1297 } 1298 1299 public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() { 1300 @Override 1301 public RankingMap createFromParcel(Parcel source) { 1302 NotificationRankingUpdate rankingUpdate = source.readParcelable(null); 1303 return new RankingMap(rankingUpdate); 1304 } 1305 1306 @Override 1307 public RankingMap[] newArray(int size) { 1308 return new RankingMap[size]; 1309 } 1310 }; 1311 } 1312 1313 private final class MyHandler extends Handler { 1314 public static final int MSG_ON_NOTIFICATION_POSTED = 1; 1315 public static final int MSG_ON_NOTIFICATION_REMOVED = 2; 1316 public static final int MSG_ON_LISTENER_CONNECTED = 3; 1317 public static final int MSG_ON_NOTIFICATION_RANKING_UPDATE = 4; 1318 public static final int MSG_ON_LISTENER_HINTS_CHANGED = 5; 1319 public static final int MSG_ON_INTERRUPTION_FILTER_CHANGED = 6; 1320 1321 public MyHandler(Looper looper) { 1322 super(looper, null, false); 1323 } 1324 1325 @Override 1326 public void handleMessage(Message msg) { 1327 switch (msg.what) { 1328 case MSG_ON_NOTIFICATION_POSTED: { 1329 SomeArgs args = (SomeArgs) msg.obj; 1330 StatusBarNotification sbn = (StatusBarNotification) args.arg1; 1331 RankingMap rankingMap = (RankingMap) args.arg2; 1332 args.recycle(); 1333 onNotificationPosted(sbn, rankingMap); 1334 } break; 1335 1336 case MSG_ON_NOTIFICATION_REMOVED: { 1337 SomeArgs args = (SomeArgs) msg.obj; 1338 StatusBarNotification sbn = (StatusBarNotification) args.arg1; 1339 RankingMap rankingMap = (RankingMap) args.arg2; 1340 args.recycle(); 1341 onNotificationRemoved(sbn, rankingMap); 1342 } break; 1343 1344 case MSG_ON_LISTENER_CONNECTED: { 1345 onListenerConnected(); 1346 } break; 1347 1348 case MSG_ON_NOTIFICATION_RANKING_UPDATE: { 1349 RankingMap rankingMap = (RankingMap) msg.obj; 1350 onNotificationRankingUpdate(rankingMap); 1351 } break; 1352 1353 case MSG_ON_LISTENER_HINTS_CHANGED: { 1354 final int hints = msg.arg1; 1355 onListenerHintsChanged(hints); 1356 } break; 1357 1358 case MSG_ON_INTERRUPTION_FILTER_CHANGED: { 1359 final int interruptionFilter = msg.arg1; 1360 onInterruptionFilterChanged(interruptionFilter); 1361 } break; 1362 } 1363 } 1364 } 1365} 1366