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