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