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