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