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