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