NotificationListenerService.java revision 7967230de20aeb6993d8332347752c8e508769e4
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 {@link SnoozeCriterion}. It should be called after the user snoozes a single 523 * notification using your UI; upon being informed, the notification manager will actually 524 * remove the notification and you will get an 525 * {@link #onNotificationRemoved(StatusBarNotification)} callback. When the snoozing period 526 * expires, you will get a {@link #onNotificationPosted(StatusBarNotification, RankingMap)} 527 * callback for the notification. 528 * @param key The key of the notification to snooze 529 * @param snoozeCriterionId The{@link SnoozeCriterion#getId()} of a context to snooze the 530 * notification until. 531 */ 532 public final void snoozeNotification(String key, String snoozeCriterionId) { 533 if (!isBound()) return; 534 try { 535 getNotificationInterface().snoozeNotificationUntilContextFromListener( 536 mWrapper, key, snoozeCriterionId); 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 if your listener has a user interface that allows the user to snooze a notification 546 * until a given time. It should be called after the user snoozes a single notification using 547 * your UI; upon being informed, the notification manager will actually remove the notification 548 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. When the 549 * snoozing period expires, you will get a 550 * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the 551 * notification. 552 * @param key The key of the notification to snooze 553 * @param snoozeUntil A time in the future, in milliseconds. 554 */ 555 public final void snoozeNotification(String key, long snoozeUntil) { 556 if (!isBound()) return; 557 try { 558 getNotificationInterface().snoozeNotificationUntilFromListener( 559 mWrapper, key, snoozeUntil); 560 } catch (android.os.RemoteException ex) { 561 Log.v(TAG, "Unable to contact notification manager", ex); 562 } 563 } 564 565 /** 566 * Inform the notification manager about snoozing a specific notification. 567 * <p> 568 * Use this to snooze a notification for an indeterminate time. Upon being informed, the 569 * notification manager will actually remove the notification and you will get an 570 * {@link #onNotificationRemoved(StatusBarNotification)} callback. When the 571 * snoozing period expires, you will get a 572 * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the 573 * notification. Use {@link #unsnoozeNotification(String)} to restore the notification. 574 * @param key The key of the notification to snooze 575 */ 576 public final void snoozeNotification(String key) { 577 if (!isBound()) return; 578 try { 579 getNotificationInterface().snoozeNotificationFromListener(mWrapper, key); 580 } catch (android.os.RemoteException ex) { 581 Log.v(TAG, "Unable to contact notification manager", ex); 582 } 583 } 584 585 /** 586 * Inform the notification manager about un-snoozing a specific notification. 587 * <p> 588 * This should only be used for notifications snoozed by this listener using 589 * {@link #snoozeNotification(String)}. Once un-snoozed, you will get a 590 * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the 591 * notification. 592 * @param key The key of the notification to snooze 593 */ 594 public final void unsnoozeNotification(String key) { 595 if (!isBound()) return; 596 try { 597 getNotificationInterface().unsnoozeNotificationFromListener(mWrapper, key); 598 } catch (android.os.RemoteException ex) { 599 Log.v(TAG, "Unable to contact notification manager", ex); 600 } 601 } 602 603 /** 604 * Inform the notification manager that these notifications have been viewed by the 605 * user. This should only be called when there is sufficient confidence that the user is 606 * looking at the notifications, such as when the notifications appear on the screen due to 607 * an explicit user interaction. 608 * 609 * <p>The service should wait for the {@link #onListenerConnected()} event 610 * before performing this operation. 611 * 612 * @param keys Notifications to mark as seen. 613 */ 614 public final void setNotificationsShown(String[] keys) { 615 if (!isBound()) return; 616 try { 617 getNotificationInterface().setNotificationsShownFromListener(mWrapper, keys); 618 } catch (android.os.RemoteException ex) { 619 Log.v(TAG, "Unable to contact notification manager", ex); 620 } 621 } 622 623 /** 624 * Sets the notification trim that will be received via {@link #onNotificationPosted}. 625 * 626 * <p> 627 * Setting a trim other than {@link #TRIM_FULL} enables listeners that don't need access to the 628 * full notification features right away to reduce their memory footprint. Full notifications 629 * can be requested on-demand via {@link #getActiveNotifications(int)}. 630 * 631 * <p> 632 * Set to {@link #TRIM_FULL} initially. 633 * 634 * <p>The service should wait for the {@link #onListenerConnected()} event 635 * before performing this operation. 636 * 637 * @hide 638 * 639 * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}. 640 * See <code>TRIM_*</code> constants. 641 */ 642 @SystemApi 643 public final void setOnNotificationPostedTrim(int trim) { 644 if (!isBound()) return; 645 try { 646 getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim); 647 } catch (RemoteException ex) { 648 Log.v(TAG, "Unable to contact notification manager", ex); 649 } 650 } 651 652 /** 653 * Request the list of outstanding notifications (that is, those that are visible to the 654 * current user). Useful when you don't know what's already been posted. 655 * 656 * <p>The service should wait for the {@link #onListenerConnected()} event 657 * before performing this operation. 658 * 659 * @return An array of active notifications, sorted in natural order. 660 */ 661 public StatusBarNotification[] getActiveNotifications() { 662 return getActiveNotifications(null, TRIM_FULL); 663 } 664 665 /** 666 * Request the list of outstanding notifications (that is, those that are visible to the 667 * current user). Useful when you don't know what's already been posted. 668 * 669 * @hide 670 * 671 * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants. 672 * @return An array of active notifications, sorted in natural order. 673 */ 674 @SystemApi 675 public StatusBarNotification[] getActiveNotifications(int trim) { 676 return getActiveNotifications(null, trim); 677 } 678 679 /** 680 * Request one or more notifications by key. Useful if you have been keeping track of 681 * notifications but didn't want to retain the bits, and now need to go back and extract 682 * more data out of those notifications. 683 * 684 * <p>The service should wait for the {@link #onListenerConnected()} event 685 * before performing this operation. 686 * 687 * @param keys the keys of the notifications to request 688 * @return An array of notifications corresponding to the requested keys, in the 689 * same order as the key list. 690 */ 691 public StatusBarNotification[] getActiveNotifications(String[] keys) { 692 return getActiveNotifications(keys, TRIM_FULL); 693 } 694 695 /** 696 * Request one or more notifications by key. Useful if you have been keeping track of 697 * notifications but didn't want to retain the bits, and now need to go back and extract 698 * more data out of those notifications. 699 * 700 * @hide 701 * 702 * @param keys the keys of the notifications to request 703 * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants. 704 * @return An array of notifications corresponding to the requested keys, in the 705 * same order as the key list. 706 */ 707 @SystemApi 708 public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) { 709 if (!isBound()) 710 return null; 711 try { 712 ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface() 713 .getActiveNotificationsFromListener(mWrapper, keys, trim); 714 List<StatusBarNotification> list = parceledList.getList(); 715 ArrayList<StatusBarNotification> corruptNotifications = null; 716 int N = list.size(); 717 for (int i = 0; i < N; i++) { 718 StatusBarNotification sbn = list.get(i); 719 Notification notification = sbn.getNotification(); 720 try { 721 // convert icon metadata to legacy format for older clients 722 createLegacyIconExtras(notification); 723 // populate remote views for older clients. 724 maybePopulateRemoteViews(notification); 725 } catch (IllegalArgumentException e) { 726 if (corruptNotifications == null) { 727 corruptNotifications = new ArrayList<>(N); 728 } 729 corruptNotifications.add(sbn); 730 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " + 731 sbn.getPackageName()); 732 } 733 } 734 if (corruptNotifications != null) { 735 list.removeAll(corruptNotifications); 736 } 737 return list.toArray(new StatusBarNotification[list.size()]); 738 } catch (android.os.RemoteException ex) { 739 Log.v(TAG, "Unable to contact notification manager", ex); 740 } 741 return null; 742 } 743 744 /** 745 * Gets the set of hints representing current state. 746 * 747 * <p> 748 * The current state may differ from the requested state if the hint represents state 749 * shared across all listeners or a feature the notification host does not support or refuses 750 * to grant. 751 * 752 * <p>The service should wait for the {@link #onListenerConnected()} event 753 * before performing this operation. 754 * 755 * @return Zero or more of the HINT_ constants. 756 */ 757 public final int getCurrentListenerHints() { 758 if (!isBound()) return 0; 759 try { 760 return getNotificationInterface().getHintsFromListener(mWrapper); 761 } catch (android.os.RemoteException ex) { 762 Log.v(TAG, "Unable to contact notification manager", ex); 763 return 0; 764 } 765 } 766 767 /** 768 * Gets the current notification interruption filter active on the host. 769 * 770 * <p> 771 * The interruption filter defines which notifications are allowed to interrupt the user 772 * (e.g. via sound & vibration) and is applied globally. Listeners can find out whether 773 * a specific notification matched the interruption filter via 774 * {@link Ranking#matchesInterruptionFilter()}. 775 * <p> 776 * The current filter may differ from the previously requested filter if the notification host 777 * does not support or refuses to apply the requested filter, or if another component changed 778 * the filter in the meantime. 779 * <p> 780 * Listen for updates using {@link #onInterruptionFilterChanged(int)}. 781 * 782 * <p>The service should wait for the {@link #onListenerConnected()} event 783 * before performing this operation. 784 * 785 * @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when 786 * unavailable. 787 */ 788 public final int getCurrentInterruptionFilter() { 789 if (!isBound()) return INTERRUPTION_FILTER_UNKNOWN; 790 try { 791 return getNotificationInterface().getInterruptionFilterFromListener(mWrapper); 792 } catch (android.os.RemoteException ex) { 793 Log.v(TAG, "Unable to contact notification manager", ex); 794 return INTERRUPTION_FILTER_UNKNOWN; 795 } 796 } 797 798 /** 799 * Sets the desired {@link #getCurrentListenerHints() listener hints}. 800 * 801 * <p> 802 * This is merely a request, the host may or may not choose to take action depending 803 * on other listener requests or other global state. 804 * <p> 805 * Listen for updates using {@link #onListenerHintsChanged(int)}. 806 * 807 * <p>The service should wait for the {@link #onListenerConnected()} event 808 * before performing this operation. 809 * 810 * @param hints One or more of the HINT_ constants. 811 */ 812 public final void requestListenerHints(int hints) { 813 if (!isBound()) return; 814 try { 815 getNotificationInterface().requestHintsFromListener(mWrapper, hints); 816 } catch (android.os.RemoteException ex) { 817 Log.v(TAG, "Unable to contact notification manager", ex); 818 } 819 } 820 821 /** 822 * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}. 823 * 824 * <p> 825 * This is merely a request, the host may or may not choose to apply the requested 826 * interruption filter depending on other listener requests or other global state. 827 * <p> 828 * Listen for updates using {@link #onInterruptionFilterChanged(int)}. 829 * 830 * <p>The service should wait for the {@link #onListenerConnected()} event 831 * before performing this operation. 832 * 833 * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants. 834 */ 835 public final void requestInterruptionFilter(int interruptionFilter) { 836 if (!isBound()) return; 837 try { 838 getNotificationInterface() 839 .requestInterruptionFilterFromListener(mWrapper, interruptionFilter); 840 } catch (android.os.RemoteException ex) { 841 Log.v(TAG, "Unable to contact notification manager", ex); 842 } 843 } 844 845 /** 846 * Returns current ranking information. 847 * 848 * <p> 849 * The returned object represents the current ranking snapshot and only 850 * applies for currently active notifications. 851 * <p> 852 * Generally you should use the RankingMap that is passed with events such 853 * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)}, 854 * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and 855 * so on. This method should only be used when needing access outside of 856 * such events, for example to retrieve the RankingMap right after 857 * initialization. 858 * 859 * <p>The service should wait for the {@link #onListenerConnected()} event 860 * before performing this operation. 861 * 862 * @return A {@link RankingMap} object providing access to ranking information 863 */ 864 public RankingMap getCurrentRanking() { 865 synchronized (mLock) { 866 return mRankingMap; 867 } 868 } 869 870 /** 871 * This is not the lifecycle event you are looking for. 872 * 873 * <p>The service should wait for the {@link #onListenerConnected()} event 874 * before performing any operations. 875 */ 876 @Override 877 public IBinder onBind(Intent intent) { 878 if (mWrapper == null) { 879 mWrapper = new NotificationListenerWrapper(); 880 } 881 return mWrapper; 882 } 883 884 /** @hide */ 885 protected boolean isBound() { 886 if (mWrapper == null) { 887 Log.w(TAG, "Notification listener service not yet bound."); 888 return false; 889 } 890 return true; 891 } 892 893 @Override 894 public void onDestroy() { 895 onListenerDisconnected(); 896 super.onDestroy(); 897 } 898 899 /** 900 * Directly register this service with the Notification Manager. 901 * 902 * <p>Only system services may use this call. It will fail for non-system callers. 903 * Apps should ask the user to add their listener in Settings. 904 * 905 * @param context Context required for accessing resources. Since this service isn't 906 * launched as a real Service when using this method, a context has to be passed in. 907 * @param componentName the component that will consume the notification information 908 * @param currentUser the user to use as the stream filter 909 * @hide 910 */ 911 @SystemApi 912 public void registerAsSystemService(Context context, ComponentName componentName, 913 int currentUser) throws RemoteException { 914 if (mWrapper == null) { 915 mWrapper = new NotificationListenerWrapper(); 916 } 917 mSystemContext = context; 918 INotificationManager noMan = getNotificationInterface(); 919 mHandler = new MyHandler(context.getMainLooper()); 920 mCurrentUser = currentUser; 921 noMan.registerListener(mWrapper, componentName, currentUser); 922 } 923 924 /** 925 * Directly unregister this service from the Notification Manager. 926 * 927 * <p>This method will fail for listeners that were not registered 928 * with (@link registerAsService). 929 * @hide 930 */ 931 @SystemApi 932 public void unregisterAsSystemService() throws RemoteException { 933 if (mWrapper != null) { 934 INotificationManager noMan = getNotificationInterface(); 935 noMan.unregisterListener(mWrapper, mCurrentUser); 936 } 937 } 938 939 /** 940 * Request that the listener be rebound, after a previous call to (@link requestUnbind). 941 * 942 * <p>This method will fail for listeners that have 943 * not been granted the permission by the user. 944 */ 945 public static void requestRebind(ComponentName componentName) { 946 INotificationManager noMan = INotificationManager.Stub.asInterface( 947 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 948 try { 949 noMan.requestBindListener(componentName); 950 } catch (RemoteException ex) { 951 throw ex.rethrowFromSystemServer(); 952 } 953 } 954 955 /** 956 * Request that the service be unbound. 957 * 958 * <p>This will no longer receive updates until 959 * {@link #requestRebind(ComponentName)} is called. 960 * The service will likely be kiled by the system after this call. 961 * 962 * <p>The service should wait for the {@link #onListenerConnected()} event 963 * before performing this operation. I know it's tempting, but you must wait. 964 */ 965 public final void requestUnbind() { 966 if (mWrapper != null) { 967 INotificationManager noMan = getNotificationInterface(); 968 try { 969 noMan.requestUnbindListener(mWrapper); 970 // Disable future messages. 971 isConnected = false; 972 } catch (RemoteException ex) { 973 throw ex.rethrowFromSystemServer(); 974 } 975 } 976 } 977 978 /** Convert new-style Icons to legacy representations for pre-M clients. */ 979 private void createLegacyIconExtras(Notification n) { 980 Icon smallIcon = n.getSmallIcon(); 981 Icon largeIcon = n.getLargeIcon(); 982 if (smallIcon != null && smallIcon.getType() == Icon.TYPE_RESOURCE) { 983 n.extras.putInt(Notification.EXTRA_SMALL_ICON, smallIcon.getResId()); 984 n.icon = smallIcon.getResId(); 985 } 986 if (largeIcon != null) { 987 Drawable d = largeIcon.loadDrawable(getContext()); 988 if (d != null && d instanceof BitmapDrawable) { 989 final Bitmap largeIconBits = ((BitmapDrawable) d).getBitmap(); 990 n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, largeIconBits); 991 n.largeIcon = largeIconBits; 992 } 993 } 994 } 995 996 /** 997 * Populates remote views for pre-N targeting apps. 998 */ 999 private void maybePopulateRemoteViews(Notification notification) { 1000 if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 1001 Builder builder = Builder.recoverBuilder(getContext(), notification); 1002 1003 // Some styles wrap Notification's contentView, bigContentView and headsUpContentView. 1004 // First inflate them all, only then set them to avoid recursive wrapping. 1005 RemoteViews content = builder.createContentView(); 1006 RemoteViews big = builder.createBigContentView(); 1007 RemoteViews headsUp = builder.createHeadsUpContentView(); 1008 1009 notification.contentView = content; 1010 notification.bigContentView = big; 1011 notification.headsUpContentView = headsUp; 1012 } 1013 } 1014 1015 /** @hide */ 1016 protected class NotificationListenerWrapper extends INotificationListener.Stub { 1017 @Override 1018 public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder, 1019 NotificationRankingUpdate update) { 1020 StatusBarNotification sbn; 1021 try { 1022 sbn = sbnHolder.get(); 1023 } catch (RemoteException e) { 1024 Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e); 1025 return; 1026 } 1027 1028 try { 1029 // convert icon metadata to legacy format for older clients 1030 createLegacyIconExtras(sbn.getNotification()); 1031 maybePopulateRemoteViews(sbn.getNotification()); 1032 } catch (IllegalArgumentException e) { 1033 // warn and drop corrupt notification 1034 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " + 1035 sbn.getPackageName()); 1036 sbn = null; 1037 } 1038 1039 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 1040 synchronized (mLock) { 1041 applyUpdateLocked(update); 1042 if (sbn != null) { 1043 SomeArgs args = SomeArgs.obtain(); 1044 args.arg1 = sbn; 1045 args.arg2 = mRankingMap; 1046 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED, 1047 args).sendToTarget(); 1048 } else { 1049 // still pass along the ranking map, it may contain other information 1050 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE, 1051 mRankingMap).sendToTarget(); 1052 } 1053 } 1054 1055 } 1056 1057 @Override 1058 public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder, 1059 NotificationRankingUpdate update, int reason) { 1060 StatusBarNotification sbn; 1061 try { 1062 sbn = sbnHolder.get(); 1063 } catch (RemoteException e) { 1064 Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e); 1065 return; 1066 } 1067 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 1068 synchronized (mLock) { 1069 applyUpdateLocked(update); 1070 SomeArgs args = SomeArgs.obtain(); 1071 args.arg1 = sbn; 1072 args.arg2 = mRankingMap; 1073 args.arg3 = reason; 1074 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED, 1075 args).sendToTarget(); 1076 } 1077 1078 } 1079 1080 @Override 1081 public void onListenerConnected(NotificationRankingUpdate update) { 1082 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 1083 synchronized (mLock) { 1084 applyUpdateLocked(update); 1085 } 1086 isConnected = true; 1087 mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_CONNECTED).sendToTarget(); 1088 } 1089 1090 @Override 1091 public void onNotificationRankingUpdate(NotificationRankingUpdate update) 1092 throws RemoteException { 1093 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 1094 synchronized (mLock) { 1095 applyUpdateLocked(update); 1096 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE, 1097 mRankingMap).sendToTarget(); 1098 } 1099 1100 } 1101 1102 @Override 1103 public void onListenerHintsChanged(int hints) throws RemoteException { 1104 mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_HINTS_CHANGED, 1105 hints, 0).sendToTarget(); 1106 } 1107 1108 @Override 1109 public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException { 1110 mHandler.obtainMessage(MyHandler.MSG_ON_INTERRUPTION_FILTER_CHANGED, 1111 interruptionFilter, 0).sendToTarget(); 1112 } 1113 1114 @Override 1115 public void onNotificationEnqueued(IStatusBarNotificationHolder notificationHolder, 1116 int importance, boolean user) throws RemoteException { 1117 // no-op in the listener 1118 } 1119 1120 @Override 1121 public void onNotificationSnoozedUntilContext( 1122 IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId) 1123 throws RemoteException { 1124 // no-op in the listener 1125 } 1126 1127 } 1128 1129 /** 1130 * @hide 1131 */ 1132 public final void applyUpdateLocked(NotificationRankingUpdate update) { 1133 mRankingMap = new RankingMap(update); 1134 } 1135 1136 /** @hide */ 1137 protected Context getContext() { 1138 if (mSystemContext != null) { 1139 return mSystemContext; 1140 } 1141 return this; 1142 } 1143 1144 /** 1145 * Stores ranking related information on a currently active notification. 1146 * 1147 * <p> 1148 * Ranking objects aren't automatically updated as notification events 1149 * occur. Instead, ranking information has to be retrieved again via the 1150 * current {@link RankingMap}. 1151 */ 1152 public static class Ranking { 1153 1154 /** Value signifying that the user has not expressed a per-app visibility override value. 1155 * @hide */ 1156 public static final int VISIBILITY_NO_OVERRIDE = NotificationManager.VISIBILITY_NO_OVERRIDE; 1157 1158 private String mKey; 1159 private int mRank = -1; 1160 private boolean mIsAmbient; 1161 private boolean mMatchesInterruptionFilter; 1162 private int mVisibilityOverride; 1163 private int mSuppressedVisualEffects; 1164 private @NotificationManager.Importance int mImportance; 1165 private CharSequence mImportanceExplanation; 1166 // System specified group key. 1167 private String mOverrideGroupKey; 1168 // Notification assistant channel override. 1169 private NotificationChannel mOverrideChannel; 1170 // Notification assistant people override. 1171 private ArrayList<String> mOverridePeople; 1172 // Notification assistant snooze criteria. 1173 private ArrayList<SnoozeCriterion> mSnoozeCriteria; 1174 1175 public Ranking() {} 1176 1177 /** 1178 * Returns the key of the notification this Ranking applies to. 1179 */ 1180 public String getKey() { 1181 return mKey; 1182 } 1183 1184 /** 1185 * Returns the rank of the notification. 1186 * 1187 * @return the rank of the notification, that is the 0-based index in 1188 * the list of active notifications. 1189 */ 1190 public int getRank() { 1191 return mRank; 1192 } 1193 1194 /** 1195 * Returns whether the notification is an ambient notification, that is 1196 * a notification that doesn't require the user's immediate attention. 1197 */ 1198 public boolean isAmbient() { 1199 return mIsAmbient; 1200 } 1201 1202 /** 1203 * Returns the user specificed visibility for the package that posted 1204 * this notification, or 1205 * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if 1206 * no such preference has been expressed. 1207 * @hide 1208 */ 1209 public int getVisibilityOverride() { 1210 return mVisibilityOverride; 1211 } 1212 1213 /** 1214 * Returns the type(s) of visual effects that should be suppressed for this notification. 1215 * See {@link #SUPPRESSED_EFFECT_SCREEN_OFF}, {@link #SUPPRESSED_EFFECT_SCREEN_ON}. 1216 */ 1217 public int getSuppressedVisualEffects() { 1218 return mSuppressedVisualEffects; 1219 } 1220 1221 /** 1222 * Returns whether the notification matches the user's interruption 1223 * filter. 1224 * 1225 * @return {@code true} if the notification is allowed by the filter, or 1226 * {@code false} if it is blocked. 1227 */ 1228 public boolean matchesInterruptionFilter() { 1229 return mMatchesInterruptionFilter; 1230 } 1231 1232 /** 1233 * Returns the importance of the notification, which dictates its 1234 * modes of presentation, see: {@link NotificationManager#IMPORTANCE_DEFAULT}, etc. 1235 * 1236 * @return the rank of the notification 1237 */ 1238 public @NotificationManager.Importance int getImportance() { 1239 return mImportance; 1240 } 1241 1242 /** 1243 * If the importance has been overridden by user preference, then this will be non-null, 1244 * and should be displayed to the user. 1245 * 1246 * @return the explanation for the importance, or null if it is the natural importance 1247 */ 1248 public CharSequence getImportanceExplanation() { 1249 return mImportanceExplanation; 1250 } 1251 1252 /** 1253 * If the system has overridden the group key, then this will be non-null, and this 1254 * key should be used to bundle notifications. 1255 */ 1256 public String getOverrideGroupKey() { 1257 return mOverrideGroupKey; 1258 } 1259 1260 /** 1261 * If the {@link NotificationAssistantService} has overridden the channel this notification 1262 * was posted to, then this will not match the channel provided by the posting application 1263 * and this should be used to determine the interruptiveness of the notification instead. 1264 */ 1265 public NotificationChannel getChannel() { 1266 return mOverrideChannel; 1267 } 1268 1269 /** 1270 * If the {@link NotificationAssistantService} has added people to this notification, then 1271 * this will be non-null. 1272 */ 1273 public List<String> getAdditionalPeople() { 1274 return mOverridePeople; 1275 } 1276 1277 /** 1278 * Returns snooze criteria provided by the {@link NotificationAssistantService}. If your 1279 * user interface displays options for snoozing notifications these criteria should be 1280 * displayed as well. 1281 */ 1282 public List<SnoozeCriterion> getSnoozeCriteria() { 1283 return mSnoozeCriteria; 1284 } 1285 1286 private void populate(String key, int rank, boolean matchesInterruptionFilter, 1287 int visibilityOverride, int suppressedVisualEffects, int importance, 1288 CharSequence explanation, String overrideGroupKey, 1289 NotificationChannel overrideChannel, ArrayList<String> overridePeople, 1290 ArrayList<SnoozeCriterion> snoozeCriteria) { 1291 mKey = key; 1292 mRank = rank; 1293 mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW; 1294 mMatchesInterruptionFilter = matchesInterruptionFilter; 1295 mVisibilityOverride = visibilityOverride; 1296 mSuppressedVisualEffects = suppressedVisualEffects; 1297 mImportance = importance; 1298 mImportanceExplanation = explanation; 1299 mOverrideGroupKey = overrideGroupKey; 1300 mOverrideChannel = overrideChannel; 1301 mOverridePeople = overridePeople; 1302 mSnoozeCriteria = snoozeCriteria; 1303 } 1304 1305 /** 1306 * {@hide} 1307 */ 1308 public static String importanceToString(int importance) { 1309 switch (importance) { 1310 case NotificationManager.IMPORTANCE_UNSPECIFIED: 1311 return "UNSPECIFIED"; 1312 case NotificationManager.IMPORTANCE_NONE: 1313 return "NONE"; 1314 case NotificationManager.IMPORTANCE_MIN: 1315 return "MIN"; 1316 case NotificationManager.IMPORTANCE_LOW: 1317 return "LOW"; 1318 case NotificationManager.IMPORTANCE_DEFAULT: 1319 return "DEFAULT"; 1320 case NotificationManager.IMPORTANCE_HIGH: 1321 case NotificationManager.IMPORTANCE_MAX: 1322 return "HIGH"; 1323 default: 1324 return "UNKNOWN(" + String.valueOf(importance) + ")"; 1325 } 1326 } 1327 } 1328 1329 /** 1330 * Provides access to ranking information on currently active 1331 * notifications. 1332 * 1333 * <p> 1334 * Note that this object represents a ranking snapshot that only applies to 1335 * notifications active at the time of retrieval. 1336 */ 1337 public static class RankingMap implements Parcelable { 1338 private final NotificationRankingUpdate mRankingUpdate; 1339 private ArrayMap<String,Integer> mRanks; 1340 private ArraySet<Object> mIntercepted; 1341 private ArrayMap<String, Integer> mVisibilityOverrides; 1342 private ArrayMap<String, Integer> mSuppressedVisualEffects; 1343 private ArrayMap<String, Integer> mImportance; 1344 private ArrayMap<String, String> mImportanceExplanation; 1345 private ArrayMap<String, String> mOverrideGroupKeys; 1346 private ArrayMap<String, NotificationChannel> mOverrideChannels; 1347 private ArrayMap<String, ArrayList<String>> mOverridePeople; 1348 private ArrayMap<String, ArrayList<SnoozeCriterion>> mSnoozeCriteria; 1349 1350 private RankingMap(NotificationRankingUpdate rankingUpdate) { 1351 mRankingUpdate = rankingUpdate; 1352 } 1353 1354 /** 1355 * Request the list of notification keys in their current ranking 1356 * order. 1357 * 1358 * @return An array of active notification keys, in their ranking order. 1359 */ 1360 public String[] getOrderedKeys() { 1361 return mRankingUpdate.getOrderedKeys(); 1362 } 1363 1364 /** 1365 * Populates outRanking with ranking information for the notification 1366 * with the given key. 1367 * 1368 * @return true if a valid key has been passed and outRanking has 1369 * been populated; false otherwise 1370 */ 1371 public boolean getRanking(String key, Ranking outRanking) { 1372 int rank = getRank(key); 1373 outRanking.populate(key, rank, !isIntercepted(key), 1374 getVisibilityOverride(key), getSuppressedVisualEffects(key), 1375 getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key), 1376 getOverrideChannel(key), getOverridePeople(key), getSnoozeCriteria(key)); 1377 return rank >= 0; 1378 } 1379 1380 private int getRank(String key) { 1381 synchronized (this) { 1382 if (mRanks == null) { 1383 buildRanksLocked(); 1384 } 1385 } 1386 Integer rank = mRanks.get(key); 1387 return rank != null ? rank : -1; 1388 } 1389 1390 private boolean isIntercepted(String key) { 1391 synchronized (this) { 1392 if (mIntercepted == null) { 1393 buildInterceptedSetLocked(); 1394 } 1395 } 1396 return mIntercepted.contains(key); 1397 } 1398 1399 private int getVisibilityOverride(String key) { 1400 synchronized (this) { 1401 if (mVisibilityOverrides == null) { 1402 buildVisibilityOverridesLocked(); 1403 } 1404 } 1405 Integer override = mVisibilityOverrides.get(key); 1406 if (override == null) { 1407 return Ranking.VISIBILITY_NO_OVERRIDE; 1408 } 1409 return override.intValue(); 1410 } 1411 1412 private int getSuppressedVisualEffects(String key) { 1413 synchronized (this) { 1414 if (mSuppressedVisualEffects == null) { 1415 buildSuppressedVisualEffectsLocked(); 1416 } 1417 } 1418 Integer suppressed = mSuppressedVisualEffects.get(key); 1419 if (suppressed == null) { 1420 return 0; 1421 } 1422 return suppressed.intValue(); 1423 } 1424 1425 private int getImportance(String key) { 1426 synchronized (this) { 1427 if (mImportance == null) { 1428 buildImportanceLocked(); 1429 } 1430 } 1431 Integer importance = mImportance.get(key); 1432 if (importance == null) { 1433 return NotificationManager.IMPORTANCE_DEFAULT; 1434 } 1435 return importance.intValue(); 1436 } 1437 1438 private String getImportanceExplanation(String key) { 1439 synchronized (this) { 1440 if (mImportanceExplanation == null) { 1441 buildImportanceExplanationLocked(); 1442 } 1443 } 1444 return mImportanceExplanation.get(key); 1445 } 1446 1447 private String getOverrideGroupKey(String key) { 1448 synchronized (this) { 1449 if (mOverrideGroupKeys == null) { 1450 buildOverrideGroupKeys(); 1451 } 1452 } 1453 return mOverrideGroupKeys.get(key); 1454 } 1455 1456 private NotificationChannel getOverrideChannel(String key) { 1457 synchronized (this) { 1458 if (mOverrideChannels == null) { 1459 buildOverrideChannelsLocked(); 1460 } 1461 } 1462 return mOverrideChannels.get(key); 1463 } 1464 1465 private ArrayList<String> getOverridePeople(String key) { 1466 synchronized (this) { 1467 if (mOverridePeople == null) { 1468 buildOverridePeopleLocked(); 1469 } 1470 } 1471 return mOverridePeople.get(key); 1472 } 1473 1474 private ArrayList<SnoozeCriterion> getSnoozeCriteria(String key) { 1475 synchronized (this) { 1476 if (mSnoozeCriteria == null) { 1477 buildSnoozeCriteriaLocked(); 1478 } 1479 } 1480 return mSnoozeCriteria.get(key); 1481 } 1482 1483 // Locked by 'this' 1484 private void buildRanksLocked() { 1485 String[] orderedKeys = mRankingUpdate.getOrderedKeys(); 1486 mRanks = new ArrayMap<>(orderedKeys.length); 1487 for (int i = 0; i < orderedKeys.length; i++) { 1488 String key = orderedKeys[i]; 1489 mRanks.put(key, i); 1490 } 1491 } 1492 1493 // Locked by 'this' 1494 private void buildInterceptedSetLocked() { 1495 String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys(); 1496 mIntercepted = new ArraySet<>(dndInterceptedKeys.length); 1497 Collections.addAll(mIntercepted, dndInterceptedKeys); 1498 } 1499 1500 // Locked by 'this' 1501 private void buildVisibilityOverridesLocked() { 1502 Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides(); 1503 mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size()); 1504 for (String key: visibilityBundle.keySet()) { 1505 mVisibilityOverrides.put(key, visibilityBundle.getInt(key)); 1506 } 1507 } 1508 1509 // Locked by 'this' 1510 private void buildSuppressedVisualEffectsLocked() { 1511 Bundle suppressedBundle = mRankingUpdate.getSuppressedVisualEffects(); 1512 mSuppressedVisualEffects = new ArrayMap<>(suppressedBundle.size()); 1513 for (String key: suppressedBundle.keySet()) { 1514 mSuppressedVisualEffects.put(key, suppressedBundle.getInt(key)); 1515 } 1516 } 1517 // Locked by 'this' 1518 private void buildImportanceLocked() { 1519 String[] orderedKeys = mRankingUpdate.getOrderedKeys(); 1520 int[] importance = mRankingUpdate.getImportance(); 1521 mImportance = new ArrayMap<>(orderedKeys.length); 1522 for (int i = 0; i < orderedKeys.length; i++) { 1523 String key = orderedKeys[i]; 1524 mImportance.put(key, importance[i]); 1525 } 1526 } 1527 1528 // Locked by 'this' 1529 private void buildImportanceExplanationLocked() { 1530 Bundle explanationBundle = mRankingUpdate.getImportanceExplanation(); 1531 mImportanceExplanation = new ArrayMap<>(explanationBundle.size()); 1532 for (String key: explanationBundle.keySet()) { 1533 mImportanceExplanation.put(key, explanationBundle.getString(key)); 1534 } 1535 } 1536 1537 // Locked by 'this' 1538 private void buildOverrideGroupKeys() { 1539 Bundle overrideGroupKeys = mRankingUpdate.getOverrideGroupKeys(); 1540 mOverrideGroupKeys = new ArrayMap<>(overrideGroupKeys.size()); 1541 for (String key: overrideGroupKeys.keySet()) { 1542 mOverrideGroupKeys.put(key, overrideGroupKeys.getString(key)); 1543 } 1544 } 1545 1546 // Locked by 'this' 1547 private void buildOverrideChannelsLocked() { 1548 Bundle overrideChannels = mRankingUpdate.getOverrideChannels(); 1549 mOverrideChannels = new ArrayMap<>(overrideChannels.size()); 1550 for (String key : overrideChannels.keySet()) { 1551 mOverrideChannels.put(key, overrideChannels.getParcelable(key)); 1552 } 1553 } 1554 1555 // Locked by 'this' 1556 private void buildOverridePeopleLocked() { 1557 Bundle overridePeople = mRankingUpdate.getOverridePeople(); 1558 mOverridePeople = new ArrayMap<>(overridePeople.size()); 1559 for (String key : overridePeople.keySet()) { 1560 mOverridePeople.put(key, overridePeople.getStringArrayList(key)); 1561 } 1562 } 1563 1564 // Locked by 'this' 1565 private void buildSnoozeCriteriaLocked() { 1566 Bundle snoozeCriteria = mRankingUpdate.getSnoozeCriteria(); 1567 mSnoozeCriteria = new ArrayMap<>(snoozeCriteria.size()); 1568 for (String key : snoozeCriteria.keySet()) { 1569 mSnoozeCriteria.put(key, snoozeCriteria.getParcelableArrayList(key)); 1570 } 1571 } 1572 1573 // ----------- Parcelable 1574 1575 @Override 1576 public int describeContents() { 1577 return 0; 1578 } 1579 1580 @Override 1581 public void writeToParcel(Parcel dest, int flags) { 1582 dest.writeParcelable(mRankingUpdate, flags); 1583 } 1584 1585 public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() { 1586 @Override 1587 public RankingMap createFromParcel(Parcel source) { 1588 NotificationRankingUpdate rankingUpdate = source.readParcelable(null); 1589 return new RankingMap(rankingUpdate); 1590 } 1591 1592 @Override 1593 public RankingMap[] newArray(int size) { 1594 return new RankingMap[size]; 1595 } 1596 }; 1597 } 1598 1599 private final class MyHandler extends Handler { 1600 public static final int MSG_ON_NOTIFICATION_POSTED = 1; 1601 public static final int MSG_ON_NOTIFICATION_REMOVED = 2; 1602 public static final int MSG_ON_LISTENER_CONNECTED = 3; 1603 public static final int MSG_ON_NOTIFICATION_RANKING_UPDATE = 4; 1604 public static final int MSG_ON_LISTENER_HINTS_CHANGED = 5; 1605 public static final int MSG_ON_INTERRUPTION_FILTER_CHANGED = 6; 1606 1607 public MyHandler(Looper looper) { 1608 super(looper, null, false); 1609 } 1610 1611 @Override 1612 public void handleMessage(Message msg) { 1613 if (!isConnected) { 1614 return; 1615 } 1616 switch (msg.what) { 1617 case MSG_ON_NOTIFICATION_POSTED: { 1618 SomeArgs args = (SomeArgs) msg.obj; 1619 StatusBarNotification sbn = (StatusBarNotification) args.arg1; 1620 RankingMap rankingMap = (RankingMap) args.arg2; 1621 args.recycle(); 1622 onNotificationPosted(sbn, rankingMap); 1623 } break; 1624 1625 case MSG_ON_NOTIFICATION_REMOVED: { 1626 SomeArgs args = (SomeArgs) msg.obj; 1627 StatusBarNotification sbn = (StatusBarNotification) args.arg1; 1628 RankingMap rankingMap = (RankingMap) args.arg2; 1629 int reason = (int) args.arg3; 1630 args.recycle(); 1631 onNotificationRemoved(sbn, rankingMap, reason); 1632 } break; 1633 1634 case MSG_ON_LISTENER_CONNECTED: { 1635 onListenerConnected(); 1636 } break; 1637 1638 case MSG_ON_NOTIFICATION_RANKING_UPDATE: { 1639 RankingMap rankingMap = (RankingMap) msg.obj; 1640 onNotificationRankingUpdate(rankingMap); 1641 } break; 1642 1643 case MSG_ON_LISTENER_HINTS_CHANGED: { 1644 final int hints = msg.arg1; 1645 onListenerHintsChanged(hints); 1646 } break; 1647 1648 case MSG_ON_INTERRUPTION_FILTER_CHANGED: { 1649 final int interruptionFilter = msg.arg1; 1650 onInterruptionFilterChanged(interruptionFilter); 1651 } break; 1652 } 1653 } 1654 } 1655} 1656