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