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