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