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