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