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