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