NotificationListenerService.java revision e0ba7eb365ac19fdc11c3f820349f0352761b9f4
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 registerAsSystemServiceImpl(context, componentName, currentUser, false /* asRanker */); 705 } 706 707 /** @hide */ 708 protected void registerAsSystemServiceImpl(Context context, ComponentName componentName, 709 int currentUser, boolean asRanker) throws RemoteException { 710 mSystemContext = context; 711 if (mWrapper == null) { 712 mWrapper = new NotificationListenerWrapper(); 713 } 714 INotificationManager noMan = getNotificationInterface(); 715 noMan.registerListener(mWrapper, componentName, currentUser, asRanker); 716 mCurrentUser = currentUser; 717 mHandler = new MyHandler(context.getMainLooper()); 718 } 719 720 /** 721 * Directly unregister this service from the Notification Manager. 722 * 723 * <P>This method will fail for listeners that were not registered 724 * with (@link registerAsService). 725 * @hide 726 */ 727 @SystemApi 728 public void unregisterAsSystemService() throws RemoteException { 729 if (mWrapper != null) { 730 INotificationManager noMan = getNotificationInterface(); 731 noMan.unregisterListener(mWrapper, mCurrentUser); 732 } 733 } 734 735 /** 736 * Request that the listener be rebound, after a previous call to (@link requestUnbind). 737 * 738 * <P>This method will fail for listeners that have 739 * not been granted the permission by the user. 740 * 741 * <P>The service should wait for the {@link #onListenerConnected()} event 742 * before performing any operations. 743 */ 744 public static void requestRebind(ComponentName componentName) 745 throws RemoteException { 746 INotificationManager noMan = INotificationManager.Stub.asInterface( 747 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 748 noMan.requestBindListener(componentName); 749 } 750 751 /** 752 * Request that the service be unbound. 753 * 754 * <P>This will no longer receive updates until 755 * {@link #requestRebind(ComponentName)} is called. 756 * The service will likely be kiled by the system after this call. 757 */ 758 public final void requestUnbind() throws RemoteException { 759 if (mWrapper != null) { 760 INotificationManager noMan = getNotificationInterface(); 761 noMan.requestUnbindListener(mWrapper); 762 } 763 } 764 765 /** Convert new-style Icons to legacy representations for pre-M clients. */ 766 private void createLegacyIconExtras(Notification n) { 767 Icon smallIcon = n.getSmallIcon(); 768 Icon largeIcon = n.getLargeIcon(); 769 if (smallIcon != null && smallIcon.getType() == Icon.TYPE_RESOURCE) { 770 n.extras.putInt(Notification.EXTRA_SMALL_ICON, smallIcon.getResId()); 771 n.icon = smallIcon.getResId(); 772 } 773 if (largeIcon != null) { 774 Drawable d = largeIcon.loadDrawable(getContext()); 775 if (d != null && d instanceof BitmapDrawable) { 776 final Bitmap largeIconBits = ((BitmapDrawable) d).getBitmap(); 777 n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, largeIconBits); 778 n.largeIcon = largeIconBits; 779 } 780 } 781 } 782 783 /** 784 * Populates remote views for pre-N targeting apps. 785 */ 786 private void maybePopulateRemoteViews(Notification notification) { 787 if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 788 Builder builder = Builder.recoverBuilder(getContext(), notification); 789 790 // Some styles wrap Notification's contentView, bigContentView and headsUpContentView. 791 // First inflate them all, only then set them to avoid recursive wrapping. 792 RemoteViews content = builder.createContentView(); 793 RemoteViews big = builder.createBigContentView(); 794 RemoteViews headsUp = builder.createHeadsUpContentView(); 795 796 notification.contentView = content; 797 notification.bigContentView = big; 798 notification.headsUpContentView = headsUp; 799 } 800 } 801 802 /** @hide */ 803 protected class NotificationListenerWrapper extends INotificationListener.Stub { 804 @Override 805 public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder, 806 NotificationRankingUpdate update) { 807 StatusBarNotification sbn; 808 try { 809 sbn = sbnHolder.get(); 810 } catch (RemoteException e) { 811 Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e); 812 return; 813 } 814 815 try { 816 // convert icon metadata to legacy format for older clients 817 createLegacyIconExtras(sbn.getNotification()); 818 maybePopulateRemoteViews(sbn.getNotification()); 819 } catch (IllegalArgumentException e) { 820 // warn and drop corrupt notification 821 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " + 822 sbn.getPackageName()); 823 sbn = null; 824 } 825 826 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 827 synchronized (mLock) { 828 applyUpdateLocked(update); 829 if (sbn != null) { 830 SomeArgs args = SomeArgs.obtain(); 831 args.arg1 = sbn; 832 args.arg2 = mRankingMap; 833 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED, 834 args).sendToTarget(); 835 } else { 836 // still pass along the ranking map, it may contain other information 837 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE, 838 mRankingMap).sendToTarget(); 839 } 840 } 841 842 } 843 844 @Override 845 public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder, 846 NotificationRankingUpdate update) { 847 StatusBarNotification sbn; 848 try { 849 sbn = sbnHolder.get(); 850 } catch (RemoteException e) { 851 Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e); 852 return; 853 } 854 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 855 synchronized (mLock) { 856 applyUpdateLocked(update); 857 SomeArgs args = SomeArgs.obtain(); 858 args.arg1 = sbn; 859 args.arg2 = mRankingMap; 860 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED, 861 args).sendToTarget(); 862 } 863 864 } 865 866 @Override 867 public void onListenerConnected(NotificationRankingUpdate update) { 868 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 869 synchronized (mLock) { 870 applyUpdateLocked(update); 871 } 872 mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_CONNECTED).sendToTarget(); 873 } 874 875 @Override 876 public void onNotificationRankingUpdate(NotificationRankingUpdate update) 877 throws RemoteException { 878 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 879 synchronized (mLock) { 880 applyUpdateLocked(update); 881 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE, 882 mRankingMap).sendToTarget(); 883 } 884 885 } 886 887 @Override 888 public void onListenerHintsChanged(int hints) throws RemoteException { 889 mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_HINTS_CHANGED, 890 hints, 0).sendToTarget(); 891 } 892 893 @Override 894 public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException { 895 mHandler.obtainMessage(MyHandler.MSG_ON_INTERRUPTION_FILTER_CHANGED, 896 interruptionFilter, 0).sendToTarget(); 897 } 898 899 @Override 900 public void onNotificationEnqueued(IStatusBarNotificationHolder notificationHolder, 901 int importance, boolean user) throws RemoteException { 902 // no-op in the listener 903 } 904 905 @Override 906 public void onNotificationVisibilityChanged(String key, long time, boolean visible) 907 throws RemoteException { 908 // no-op in the listener 909 } 910 911 @Override 912 public void onNotificationClick(String key, long time) throws RemoteException { 913 // no-op in the listener 914 } 915 916 @Override 917 public void onNotificationActionClick(String key, long time, int actionIndex) 918 throws RemoteException { 919 // no-op in the listener 920 } 921 922 @Override 923 public void onNotificationRemovedReason(String key, long time, int reason) 924 throws RemoteException { 925 // no-op in the listener 926 } 927 } 928 929 private void applyUpdateLocked(NotificationRankingUpdate update) { 930 mRankingMap = new RankingMap(update); 931 } 932 933 /** @hide */ 934 protected Context getContext() { 935 if (mSystemContext != null) { 936 return mSystemContext; 937 } 938 return this; 939 } 940 941 /** 942 * Stores ranking related information on a currently active notification. 943 * 944 * <p> 945 * Ranking objects aren't automatically updated as notification events 946 * occur. Instead, ranking information has to be retrieved again via the 947 * current {@link RankingMap}. 948 */ 949 public static class Ranking { 950 951 /** @hide */ 952 @IntDef({VISIBILITY_NO_OVERRIDE, IMPORTANCE_UNSPECIFIED, IMPORTANCE_NONE, 953 IMPORTANCE_MIN, IMPORTANCE_LOW, IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, 954 IMPORTANCE_MAX}) 955 @Retention(RetentionPolicy.SOURCE) 956 public @interface Importance {} 957 958 /** Value signifying that the user has not expressed a per-app visibility override value. 959 * @hide */ 960 public static final int VISIBILITY_NO_OVERRIDE = -1000; 961 962 /** 963 * Value signifying that the user has not expressed an importance. 964 * 965 * This value is for persisting preferences, and should never be associated with 966 * an actual notification. 967 */ 968 public static final int IMPORTANCE_UNSPECIFIED = -1000; 969 970 /** 971 * A notification with no importance: shows nowhere, is blocked. 972 */ 973 public static final int IMPORTANCE_NONE = 0; 974 975 /** 976 * Min notification importance: only shows in the shade, below the fold. 977 */ 978 public static final int IMPORTANCE_MIN = 1; 979 980 /** 981 * Low notification importance: shows everywhere, but is not intrusive. 982 */ 983 public static final int IMPORTANCE_LOW = 2; 984 985 /** 986 * Default notification importance: shows everywhere, allowed to makes noise, 987 * but does not visually intrude. 988 */ 989 public static final int IMPORTANCE_DEFAULT = 3; 990 991 /** 992 * Higher notification importance: shows everywhere, allowed to makes noise and peek. 993 */ 994 public static final int IMPORTANCE_HIGH = 4; 995 996 /** 997 * Highest notification importance: shows everywhere, allowed to makes noise, peek, and 998 * use full screen intents. 999 */ 1000 public static final int IMPORTANCE_MAX = 5; 1001 1002 private String mKey; 1003 private int mRank = -1; 1004 private boolean mIsAmbient; 1005 private boolean mMatchesInterruptionFilter; 1006 private int mVisibilityOverride; 1007 private int mSuppressedVisualEffects; 1008 private @Importance int mImportance; 1009 private CharSequence mImportanceExplanation; 1010 1011 public Ranking() {} 1012 1013 /** 1014 * Returns the key of the notification this Ranking applies to. 1015 */ 1016 public String getKey() { 1017 return mKey; 1018 } 1019 1020 /** 1021 * Returns the rank of the notification. 1022 * 1023 * @return the rank of the notification, that is the 0-based index in 1024 * the list of active notifications. 1025 */ 1026 public int getRank() { 1027 return mRank; 1028 } 1029 1030 /** 1031 * Returns whether the notification is an ambient notification, that is 1032 * a notification that doesn't require the user's immediate attention. 1033 */ 1034 public boolean isAmbient() { 1035 return mIsAmbient; 1036 } 1037 1038 /** 1039 * Returns the user specificed visibility for the package that posted 1040 * this notification, or 1041 * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if 1042 * no such preference has been expressed. 1043 * @hide 1044 */ 1045 public int getVisibilityOverride() { 1046 return mVisibilityOverride; 1047 } 1048 1049 /** 1050 * Returns the type(s) of visual effects that should be suppressed for this notification. 1051 * See {@link #SUPPRESSED_EFFECT_SCREEN_OFF}, {@link #SUPPRESSED_EFFECT_SCREEN_ON}. 1052 */ 1053 public int getSuppressedVisualEffects() { 1054 return mSuppressedVisualEffects; 1055 } 1056 1057 /** 1058 * Returns whether the notification matches the user's interruption 1059 * filter. 1060 * 1061 * @return {@code true} if the notification is allowed by the filter, or 1062 * {@code false} if it is blocked. 1063 */ 1064 public boolean matchesInterruptionFilter() { 1065 return mMatchesInterruptionFilter; 1066 } 1067 1068 /** 1069 * Returns the importance of the notification, which dictates its 1070 * modes of presentation, see: {@link #IMPORTANCE_DEFAULT}, etc. 1071 * 1072 * @return the rank of the notification 1073 */ 1074 public @Importance int getImportance() { 1075 return mImportance; 1076 } 1077 1078 /** 1079 * If the importance has been overriden by user preference, then this will be non-null, 1080 * and should be displayed to the user. 1081 * 1082 * @return the explanation for the importance, or null if it is the natural importance 1083 */ 1084 public CharSequence getImportanceExplanation() { 1085 return mImportanceExplanation; 1086 } 1087 1088 private void populate(String key, int rank, boolean matchesInterruptionFilter, 1089 int visibilityOverride, int suppressedVisualEffects, int importance, 1090 CharSequence explanation) { 1091 mKey = key; 1092 mRank = rank; 1093 mIsAmbient = importance < IMPORTANCE_LOW; 1094 mMatchesInterruptionFilter = matchesInterruptionFilter; 1095 mVisibilityOverride = visibilityOverride; 1096 mSuppressedVisualEffects = suppressedVisualEffects; 1097 mImportance = importance; 1098 mImportanceExplanation = explanation; 1099 } 1100 1101 /** 1102 * {@hide} 1103 */ 1104 public static String importanceToString(int importance) { 1105 switch (importance) { 1106 case IMPORTANCE_UNSPECIFIED: 1107 return "UNSPECIFIED"; 1108 case IMPORTANCE_NONE: 1109 return "NONE"; 1110 case IMPORTANCE_MIN: 1111 return "MIN"; 1112 case IMPORTANCE_LOW: 1113 return "LOW"; 1114 case IMPORTANCE_DEFAULT: 1115 return "DEFAULT"; 1116 case IMPORTANCE_HIGH: 1117 return "HIGH"; 1118 case IMPORTANCE_MAX: 1119 return "MAX"; 1120 default: 1121 return "UNKNOWN(" + String.valueOf(importance) + ")"; 1122 } 1123 } 1124 } 1125 1126 /** 1127 * Provides access to ranking information on currently active 1128 * notifications. 1129 * 1130 * <p> 1131 * Note that this object represents a ranking snapshot that only applies to 1132 * notifications active at the time of retrieval. 1133 */ 1134 public static class RankingMap implements Parcelable { 1135 private final NotificationRankingUpdate mRankingUpdate; 1136 private ArrayMap<String,Integer> mRanks; 1137 private ArraySet<Object> mIntercepted; 1138 private ArrayMap<String, Integer> mVisibilityOverrides; 1139 private ArrayMap<String, Integer> mSuppressedVisualEffects; 1140 private ArrayMap<String, Integer> mImportance; 1141 private ArrayMap<String, String> mImportanceExplanation; 1142 1143 private RankingMap(NotificationRankingUpdate rankingUpdate) { 1144 mRankingUpdate = rankingUpdate; 1145 } 1146 1147 /** 1148 * Request the list of notification keys in their current ranking 1149 * order. 1150 * 1151 * @return An array of active notification keys, in their ranking order. 1152 */ 1153 public String[] getOrderedKeys() { 1154 return mRankingUpdate.getOrderedKeys(); 1155 } 1156 1157 /** 1158 * Populates outRanking with ranking information for the notification 1159 * with the given key. 1160 * 1161 * @return true if a valid key has been passed and outRanking has 1162 * been populated; false otherwise 1163 */ 1164 public boolean getRanking(String key, Ranking outRanking) { 1165 int rank = getRank(key); 1166 outRanking.populate(key, rank, !isIntercepted(key), 1167 getVisibilityOverride(key), getSuppressedVisualEffects(key), 1168 getImportance(key), getImportanceExplanation(key)); 1169 return rank >= 0; 1170 } 1171 1172 private int getRank(String key) { 1173 synchronized (this) { 1174 if (mRanks == null) { 1175 buildRanksLocked(); 1176 } 1177 } 1178 Integer rank = mRanks.get(key); 1179 return rank != null ? rank : -1; 1180 } 1181 1182 private boolean isIntercepted(String key) { 1183 synchronized (this) { 1184 if (mIntercepted == null) { 1185 buildInterceptedSetLocked(); 1186 } 1187 } 1188 return mIntercepted.contains(key); 1189 } 1190 1191 private int getVisibilityOverride(String key) { 1192 synchronized (this) { 1193 if (mVisibilityOverrides == null) { 1194 buildVisibilityOverridesLocked(); 1195 } 1196 } 1197 Integer override = mVisibilityOverrides.get(key); 1198 if (override == null) { 1199 return Ranking.VISIBILITY_NO_OVERRIDE; 1200 } 1201 return override.intValue(); 1202 } 1203 1204 private int getSuppressedVisualEffects(String key) { 1205 synchronized (this) { 1206 if (mSuppressedVisualEffects == null) { 1207 buildSuppressedVisualEffectsLocked(); 1208 } 1209 } 1210 Integer suppressed = mSuppressedVisualEffects.get(key); 1211 if (suppressed == null) { 1212 return 0; 1213 } 1214 return suppressed.intValue(); 1215 } 1216 1217 private int getImportance(String key) { 1218 synchronized (this) { 1219 if (mImportance == null) { 1220 buildImportanceLocked(); 1221 } 1222 } 1223 Integer importance = mImportance.get(key); 1224 if (importance == null) { 1225 return Ranking.IMPORTANCE_DEFAULT; 1226 } 1227 return importance.intValue(); 1228 } 1229 1230 private String getImportanceExplanation(String key) { 1231 synchronized (this) { 1232 if (mImportanceExplanation == null) { 1233 buildImportanceExplanationLocked(); 1234 } 1235 } 1236 return mImportanceExplanation.get(key); 1237 } 1238 1239 // Locked by 'this' 1240 private void buildRanksLocked() { 1241 String[] orderedKeys = mRankingUpdate.getOrderedKeys(); 1242 mRanks = new ArrayMap<>(orderedKeys.length); 1243 for (int i = 0; i < orderedKeys.length; i++) { 1244 String key = orderedKeys[i]; 1245 mRanks.put(key, i); 1246 } 1247 } 1248 1249 // Locked by 'this' 1250 private void buildInterceptedSetLocked() { 1251 String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys(); 1252 mIntercepted = new ArraySet<>(dndInterceptedKeys.length); 1253 Collections.addAll(mIntercepted, dndInterceptedKeys); 1254 } 1255 1256 // Locked by 'this' 1257 private void buildVisibilityOverridesLocked() { 1258 Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides(); 1259 mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size()); 1260 for (String key: visibilityBundle.keySet()) { 1261 mVisibilityOverrides.put(key, visibilityBundle.getInt(key)); 1262 } 1263 } 1264 1265 // Locked by 'this' 1266 private void buildSuppressedVisualEffectsLocked() { 1267 Bundle suppressedBundle = mRankingUpdate.getSuppressedVisualEffects(); 1268 mSuppressedVisualEffects = new ArrayMap<>(suppressedBundle.size()); 1269 for (String key: suppressedBundle.keySet()) { 1270 mSuppressedVisualEffects.put(key, suppressedBundle.getInt(key)); 1271 } 1272 } 1273 // Locked by 'this' 1274 private void buildImportanceLocked() { 1275 String[] orderedKeys = mRankingUpdate.getOrderedKeys(); 1276 int[] importance = mRankingUpdate.getImportance(); 1277 mImportance = new ArrayMap<>(orderedKeys.length); 1278 for (int i = 0; i < orderedKeys.length; i++) { 1279 String key = orderedKeys[i]; 1280 mImportance.put(key, importance[i]); 1281 } 1282 } 1283 1284 // Locked by 'this' 1285 private void buildImportanceExplanationLocked() { 1286 Bundle explanationBundle = mRankingUpdate.getImportanceExplanation(); 1287 mImportanceExplanation = new ArrayMap<>(explanationBundle.size()); 1288 for (String key: explanationBundle.keySet()) { 1289 mImportanceExplanation.put(key, explanationBundle.getString(key)); 1290 } 1291 } 1292 1293 // ----------- Parcelable 1294 1295 @Override 1296 public int describeContents() { 1297 return 0; 1298 } 1299 1300 @Override 1301 public void writeToParcel(Parcel dest, int flags) { 1302 dest.writeParcelable(mRankingUpdate, flags); 1303 } 1304 1305 public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() { 1306 @Override 1307 public RankingMap createFromParcel(Parcel source) { 1308 NotificationRankingUpdate rankingUpdate = source.readParcelable(null); 1309 return new RankingMap(rankingUpdate); 1310 } 1311 1312 @Override 1313 public RankingMap[] newArray(int size) { 1314 return new RankingMap[size]; 1315 } 1316 }; 1317 } 1318 1319 private final class MyHandler extends Handler { 1320 public static final int MSG_ON_NOTIFICATION_POSTED = 1; 1321 public static final int MSG_ON_NOTIFICATION_REMOVED = 2; 1322 public static final int MSG_ON_LISTENER_CONNECTED = 3; 1323 public static final int MSG_ON_NOTIFICATION_RANKING_UPDATE = 4; 1324 public static final int MSG_ON_LISTENER_HINTS_CHANGED = 5; 1325 public static final int MSG_ON_INTERRUPTION_FILTER_CHANGED = 6; 1326 1327 public MyHandler(Looper looper) { 1328 super(looper, null, false); 1329 } 1330 1331 @Override 1332 public void handleMessage(Message msg) { 1333 switch (msg.what) { 1334 case MSG_ON_NOTIFICATION_POSTED: { 1335 SomeArgs args = (SomeArgs) msg.obj; 1336 StatusBarNotification sbn = (StatusBarNotification) args.arg1; 1337 RankingMap rankingMap = (RankingMap) args.arg2; 1338 args.recycle(); 1339 onNotificationPosted(sbn, rankingMap); 1340 } break; 1341 1342 case MSG_ON_NOTIFICATION_REMOVED: { 1343 SomeArgs args = (SomeArgs) msg.obj; 1344 StatusBarNotification sbn = (StatusBarNotification) args.arg1; 1345 RankingMap rankingMap = (RankingMap) args.arg2; 1346 args.recycle(); 1347 onNotificationRemoved(sbn, rankingMap); 1348 } break; 1349 1350 case MSG_ON_LISTENER_CONNECTED: { 1351 onListenerConnected(); 1352 } break; 1353 1354 case MSG_ON_NOTIFICATION_RANKING_UPDATE: { 1355 RankingMap rankingMap = (RankingMap) msg.obj; 1356 onNotificationRankingUpdate(rankingMap); 1357 } break; 1358 1359 case MSG_ON_LISTENER_HINTS_CHANGED: { 1360 final int hints = msg.arg1; 1361 onListenerHintsChanged(hints); 1362 } break; 1363 1364 case MSG_ON_INTERRUPTION_FILTER_CHANGED: { 1365 final int interruptionFilter = msg.arg1; 1366 onInterruptionFilterChanged(interruptionFilter); 1367 } break; 1368 } 1369 } 1370 } 1371} 1372